728x90
비디오 플레이어에 사용되는 패키지
video_player
Flutter에서 동영상 재생 기능을 구현할 수 있게 해주는 공식 패키지입니다.
video_player 자체에는 전체화면 기능이 없고, 전체화면/회전/시스템 UI 숨김은 Flutter의 SystemChrome, MediaQuery 등을 통해 직접 구현해야 합니다.
1. 주요 기능
- 파일 동영상 재생: 로컬 디바이스에 저장된 mp4 등
- 네트워크 동영상 재생: HTTP/HTTPS URL (YouTube 스트림 등은 불가)
- 재생 / 일시정지 / 탐색(seek) 제어
- 음소거 / 볼륨 / 반복재생 지원
- 전체화면 모드 직접 구현 가능
- 비디오 재생 위치 추적 가능
- 기본적인 UI는 제공 안 함 → 사용자가 직접 구성해야 함
항목 | 내용 |
패키지명 | video_player |
버전 | ^2.9.5 (2024년 기준 최신 중 하나) |
지원 플랫폼 | Android, iOS, Web, macOS, Windows, Linux |
개발 주체 | Flutter 공식 팀 (Google) |
주요 용도 | 앱 내에서 동영상 파일 또는 네트워크 스트리밍 재생 가능 |
video_player 2.9.5의 특징
- 안정성과 성능 향상 (이전 버전 대비)
- 더 넓은 플랫폼 지원: Android, iOS, Web, macOS 등
- iOS AVPlayer 기반, Android ExoPlayer 기반의 네이티브 성능
- Web에서도 작동하지만 Web에서는 제한사항이 있음
2. 지원하는 동영상 소스 타입
// 네트워크 URL
VideoPlayerController.network('https://example.com/video.mp4');
// 앱에 포함된 asset
VideoPlayerController.asset('assets/videos/sample.mp4');
// 로컬 파일
VideoPlayerController.file(File('/storage/emulated/0/Movies/video.mp4'));
3. 기본 사용 예시 코드
late VideoPlayerController _controller;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network('https://...')
..initialize().then((_) {
setState(() {}); // 준비되면 화면 갱신
});
}
4. 언제 video_player를 쓰면 좋을까?
상황 | 적합도 |
앱에서 간단한 동영상 보여주기 | 아주 적합 |
커스텀 제어 UI 만들고 싶을 때 | 강력함 |
YouTube 등 보호된 영상 스트리밍 | 다른 API 필요 (예: youtube_player_flutter) |
file_picker
Flutter 앱에서 파일 탐색기(File Explorer)를 열어 사용자가 파일을 선택할 수 있게 해주는 대표적인 패키지입니다. Flutter 공식 패키지는 아니지만, 널리 사용되는 신뢰성 높은 패키지 다양한 플랫폼에서 사용자가 원하는 파일을 자유롭게 선택하게 해주는
강력하고 유연한 Flutter 파일 선택기 라이브러리입니다.
항목 | 내용 |
패키지명 | file_picker |
최신 안정 버전 (2024년 기준) | ^10.0.0 |
지원 플랫폼 | Android, iOS, macOS, Windows, Linux, Web |
주요 기능 | 파일 선택, 다중 선택, 폴더 선택, 저장 경로 선택 등 |
1. 주요 기능 요약
- 단일 파일 선택 하나의 파일 선택 (예: 이미지, 동영상, 문서 등)
- 폴더 선택 전체 폴더 선택 (지원되는 플랫폼에서)
- 다중 파일 선택 여러 파일 한꺼번에 선택 가능
- 파일 타입 필터 이미지, 동영상, 문서 등 특정 타입만 선택 가능
- 저장 경로 선택 파일을 저장할 디렉토리 경로 선택 가능
- 웹 지원 Flutter Web에서도 사용 가능 (제한 있음)
2. 사용 예시
2.1 단일 파일 선택 (예: 동영상)
final result = await FilePicker.platform.pickFiles(
type: FileType.video,
);
if (result != null && result.files.single.path != null) {
File file = File(result.files.single.path!);
// 사용: file.path 등
}
2.2. 이미지 여러 개 선택
final result = await FilePicker.platform.pickFiles(
allowMultiple: true,
type: FileType.image,
);
2.3. 모든 형식 허용
final result = await FilePicker.platform.pickFiles(
type: FileType.any,
);
2.4. 확장자 직접 지정 (예: .pdf, .docx 등)
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf', 'docx', 'xlsx'],
);
3. 주요 클래스 및 속성
항목 | 설명 |
FilePickerResult | 선택된 파일 정보가 들어있는 객체 |
PlatformFile | 각 파일의 정보 (이름, 경로, 크기 등) |
FileType | 선택할 파일 형식 지정 (video, image, custom 등) |
FilePicker.platform | 현재 플랫폼에서 동작하는 파일 피커 인스턴스 |
4. Android & iOS 설정
4.1. Android: AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
Android 13(API 33)+부터는 Storage 권한 변경됨 → Flutter 문서 참조
4.2. iOS: Info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>사진 접근을 허용해주세요</string>
<key>NSDocumentsFolderUsageDescription</key>
<string>문서에 접근합니다</string>
4.3. Web 지원 참고
- Web에서는 파일을 선택할 수 있지만, 경로 정보(file.path)는 제공되지 않음
- 대신 Uint8List 로 파일 데이터를 받아야 함
비디오 플레이어 코드
전체 코드
1. pubspec.yaml
dependencies:
flutter:
sdk: flutter
video_player: ^2.9.5
file_picker: ^10.0.0
2. /screen/video-player-screen.dart
import 'dart:async';
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter/services.dart';
// 동영상 플레이어 화면을 위한 Stateful 위젯
class VideoPlayerScreen extends StatefulWidget {
const VideoPlayerScreen({super.key});
@override
_VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}
class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
VideoPlayerController? _controller; // 동영상 제어 컨트롤러
bool _showControls = false; // 제어 버튼 UI 표시 여부
Timer? _hideTimer; // 제어 버튼 자동 숨김 타이머
bool _isFullScreen = false; // 전체화면 모드 여부
@override
void dispose() {
// 상태 종료 시 컨트롤러 및 타이머 해제
_controller?.dispose();
_hideTimer?.cancel();
// 전체화면 설정 복원 (세로 모드로)
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
super.dispose();
}
// 사용자가 동영상을 선택할 수 있도록 파일 피커 호출
void _pickVideo() async {
final result = await FilePicker.platform.pickFiles(type: FileType.video);
if (result != null && result.files.single.path != null) {
// 이전 컨트롤러 제거
_controller?.dispose();
// 새 동영상 컨트롤러 생성 및 초기화
_controller = VideoPlayerController.file(
File(result.files.single.path!),
)..initialize().then((_) {
setState(() {}); // UI 업데이트
_controller?.play(); // 자동 재생
});
}
}
// 화면 탭 시 제어 버튼을 보여주고 5초 뒤에 자동 숨김
void _toggleControls() {
setState(() {
_showControls = true;
});
_hideTimer?.cancel();
_hideTimer = Timer(Duration(seconds: 5), () {
setState(() {
_showControls = false;
});
});
}
// 전체화면 모드 전환
void _toggleFullScreen() async {
if (_isFullScreen) {
// 전체화면 종료: 세로 모드 및 시스템 UI 복원
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
} else {
// 전체화면 진입: 가로 모드 및 시스템 UI 숨김
await SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
setState(() {
_isFullScreen = !_isFullScreen;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
onTap: _toggleControls, // 화면 탭 시 제어 UI 토글
child: SizedBox.expand( // 전체 화면을 차지
child: _controller == null
? _buildLogoScreen()
: _controller!.value.isInitialized
? Stack(
alignment: Alignment.bottomCenter,
children: [
Center(
child: FittedBox(
fit: BoxFit.contain, // 비율 유지하면서 화면에 맞춤
child: SizedBox(
width: _controller!.value.size.width,
height: _controller!.value.size.height,
child: VideoPlayer(_controller!),
),
),
),
if (_showControls) _buildControls(),
],
)
: Center(child: CircularProgressIndicator()),
),
),
);
}
// 초기 로고 화면: 동영상이 선택되지 않았을 때 표시
Widget _buildLogoScreen() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: _pickVideo, // 로고 클릭 시 동영상 선택
child: Icon(Icons.play_circle_fill, size: 100, color: Colors.white),
),
SizedBox(height: 20),
Text(
'Andrew Player',
style: TextStyle(color: Colors.white, fontSize: 24),
),
],
);
}
// 동영상 재생 제어 버튼 UI
Widget _buildControls() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 하단 동영상 진행 막대
VideoProgressIndicator(_controller!, allowScrubbing: true),
// 버튼 그룹: 뒤로가기, 10초 이전, 재생/일시정지, 10초 이후, 전체화면
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 앱 홈으로 돌아가기
IconButton(
icon: Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
setState(() {
_controller?.pause(); // 재생 중지
_controller?.dispose(); // 메모리 해제
_controller = null; // 초기화
});
// 전체화면 상태 복원
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
_isFullScreen = false;
},
),
// 10초 이전으로 이동
IconButton(
icon: Icon(Icons.replay_10, color: Colors.white),
onPressed: () {
final newPos = _controller!.value.position - Duration(seconds: 10);
_controller!.seekTo(newPos > Duration.zero ? newPos : Duration.zero);
},
),
// 재생/일시정지 토글
IconButton(
icon: Icon(
_controller!.value.isPlaying ? Icons.pause : Icons.play_arrow,
color: Colors.white,
),
onPressed: () {
setState(() {
_controller!.value.isPlaying
? _controller!.pause()
: _controller!.play();
});
},
),
// 10초 이후로 이동
IconButton(
icon: Icon(Icons.forward_10, color: Colors.white),
onPressed: () {
final newPos = _controller!.value.position + Duration(seconds: 10);
if (newPos < _controller!.value.duration) {
_controller!.seekTo(newPos);
}
},
),
// 전체화면 토글 버튼
IconButton(
icon: Icon(
_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
color: Colors.white,
),
onPressed: _toggleFullScreen,
),
],
),
SizedBox(height: 20),
],
);
}
}
3. main.dart
import 'package:flutter/material.dart';
import 'package:andrew_player/screen/video_player_screen.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Andrew Player',
home: VideoPlayerScreen(),
debugShowCheckedModeBanner: false,
);
}
}
추가: 동영상이 찌그러지지 않고 자연스럽게 화면에 표시되도록 하기
- AspectRatio: 비율 유지에는 좋지만, 부모의 제약을 무시하면 찌그러질 수 있음.
- FittedBox + SizedBox: 부모 제약 안에서 비율 유지 + 크기 자동 조절, 더 안정적이고 실용적.
1. AspectRatio란?
AspectRatio(
aspectRatio: 16 / 9,
child: ...
)
- child 위젯의 비율(aspect ratio) 을 강제로 유지하게 해주는 위젯.
- 비율만 맞추면 되기 때문에 부모 컨테이너의 크기와 맞지 않으면:
- 여백이 생기거나
- 강제로 늘어나거나 해서 찌그러질 수 있음.
2. FittedBox + SizedBox 조합이란?
FittedBox(
fit: BoxFit.contain,
child: SizedBox(
width: videoWidth,
height: videoHeight,
child: VideoPlayer(_controller!),
),
)
- FittedBox 는 자식(SizedBox)의 크기를 부모에 맞게 비율 유지하며 조절해줌
- BoxFit.contain 은 "가능한 한 꽉 채우되, 찌그러지지 않게!" 라는 뜻
- SizedBox 에 실제 동영상의 해상도(videoWidth, videoHeight)를 지정하면, FittedBox 가 그 크기를 기준으로 화면 안에 알맞게 보여줌
항목 | AspectRatio | FittedBox + SizedBox |
사용 목적 | 비율 고정 | 부모 안에서 콘텐츠 맞추기 |
실제 크기 기준 사용 | 단순 비율 | 실제 영상의 width/height 사용 |
화면 찌그러짐 가능성 | 있음 (부모 크기와 맞지 않으면) | 없음 (fit 옵션으로 대응 가능) |
비율 유지 | O | O (BoxFit.contain일 때) |
전체화면 대응 | 제한적 | 우수 |
728x90
'Flutter for Beginners' 카테고리의 다른 글
Flutter Google Map (0) | 2025.04.18 |
---|---|
Flutter 영상통화 (0) | 2025.04.15 |
Flutter 디지털 시계에 주사위 굴리기 추가 (0) | 2025.04.03 |
Flutter 디지털 시계 + 나침반 + 날씨정보 (0) | 2025.03.25 |
Flutter PageView을 이용한 자동 슬라이드 이미지 겔러리 (0) | 2025.03.24 |