코딩 일기장/Flutter

[Flutter] Camera/Gallery/Preview/Select Video/카메라/갤러리/미리보기/동영상 선택

minWachya 2023. 7. 17. 12:14
반응형

결과 화면

- 바텀 시트: 바텀 시트에서 갤러리에서 가져올 건지, 직접 찍을 건지 선택

- 갤러리: 갤러리에서 영상 선택, 카메리: 카메라로 영상 촬영

- 미리보기: 선택한/촬영한 영상 미리보기(무한 재생) + 제목, 태그 입력

 

1. 의존성 추가

dependencies:
	video_player: ^2.6.1
	image_picker: ^1.0.0

 

2.  바텀 시트(갤러리, 카메라 촬영)

2-1. 갤러리 선택 시 갤러리에서 영상 선택하는 코드

// 갤러리 선택 시 동작하는 코드
onTap: () {
	getVideo(ImageSource.gallery);	// 갤러리에서 영상 가져오는 함수
	Navigator.of(context).pop();	// 바텀시트 내리기
}

2-2. 카메라 선택 시 카메라로 영상 촬영하는 코드

// 카메라 선택 시 동작하는 코드
onTap: () {
	getVideo(ImageSource.camera);	// 카메라에서 영상 촬영하기
	Navigator.of(context).pop();	// 바텀시트 내리기
}

 

2-3. getVideo 함수

 Future getVideo(
      ImageSource img,
    ) async {
      final pickedFile = await picker.pickVideo(
          source: img,
          preferredCameraDevice: CameraDevice.front,
          maxDuration: const Duration(minutes: 10));	// 10초 제한
      XFile? xfilePick = pickedFile;
      setState(
        () {
        // 파일 선택 시
          if (xfilePick != null) {
            videoFile = File(pickedFile!.path);
            Navigator.push(
              context,
              MaterialPageRoute(
              	// UploadScreen: 미리보기 화면으로 이동
                  builder: (context) => UploadScreen(uploadFile: videoFile!)),
            );
          } else {
          // 파일 미 선택 시 스낵바 띄우기
            ScaffoldMessenger.of(widget.context).showSnackBar(
                const SnackBar(content: Text('Nothing is selected')));
          }
        },
      );
    }

2-4. 전체 코드

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pocket_pose/config/app_color.dart';
import 'package:pocket_pose/ui/screen/upload_screen.dart';

class UploadButtonWidget extends StatefulWidget {
  const UploadButtonWidget({
    super.key,
    required this.context,
  });

  final BuildContext context;

  @override
  State<UploadButtonWidget> createState() => _UploadButtonWidgetState();
}

class _UploadButtonWidgetState extends State<UploadButtonWidget> {
  @override
  Widget build(BuildContext context) {
    File? videoFile;
    final picker = ImagePicker();

    Future getVideo(
      ImageSource img,
    ) async {
      final pickedFile = await picker.pickVideo(
          source: img,
          preferredCameraDevice: CameraDevice.front,
          maxDuration: const Duration(minutes: 10));
      XFile? xfilePick = pickedFile;
      setState(
        () {
          if (xfilePick != null) {
            videoFile = File(pickedFile!.path);
            Navigator.push(
              context,
              MaterialPageRoute(
                  builder: (context) => UploadScreen(uploadFile: videoFile!)),
            );
          } else {
            ScaffoldMessenger.of(widget.context).showSnackBar(
                const SnackBar(content: Text('Nothing is selected')));
          }
        },
      );
    }

    return InkWell(
      onTap: () => {
        showModalBottomSheet(
          context: context,
          shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.vertical(
              top: Radius.circular(15.0),
            ),
          ),
          backgroundColor: Colors.black,
          builder: (context) {
            return SizedBox(
              height: 160,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  const Padding(
                    padding: EdgeInsets.fromLTRB(20, 23, 20, 8),
                    child: Text(
                      "만들기",
                      style: TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ),
                  InkWell(
                    onTap: () {
                      getVideo(ImageSource.gallery);
                      Navigator.of(context).pop();
                    },
                    child: Padding(
                      padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
                      child: Row(
                        children: [
                          Container(
                            width: 36,
                            height: 36,
                            decoration: BoxDecoration(
                              color: AppColor.grayColor,
                              shape: BoxShape.circle,
                            ),
                            child: SvgPicture.asset(
                              'assets/icons/ic_home_upload_gallery.svg',
                              width: 16,
                              height: 16,
                              fit: BoxFit.scaleDown,
                            ),
                          ),
                          const SizedBox(
                            width: 20,
                          ),
                          const Text(
                            "갤러리에서 가져오기",
                            style: TextStyle(color: Colors.white, fontSize: 14),
                          ),
                        ],
                      ),
                    ),
                  ),
                  InkWell(
                    onTap: () {
                      getVideo(ImageSource.camera);
                      Navigator.of(context).pop();
                    },
                    child: Padding(
                      padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
                      child: Row(
                        children: [
                          Container(
                            width: 36,
                            height: 36,
                            decoration: BoxDecoration(
                              color: AppColor.grayColor,
                              shape: BoxShape.circle,
                            ),
                            child: SvgPicture.asset(
                              'assets/icons/ic_home_upload_camera.svg',
                              width: 16,
                              height: 16,
                              fit: BoxFit.scaleDown,
                            ),
                          ),
                          const SizedBox(
                            width: 20,
                          ),
                          const Text(
                            "직접 촬영하기",
                            style: TextStyle(color: Colors.white, fontSize: 14),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            );
          },
        ),
      },
      borderRadius: const BorderRadius.all(
        Radius.circular(90.0),
      ),
      child: Container(
          padding: const EdgeInsets.all(14),
          child: SvgPicture.asset('assets/icons/ic_home_upload.svg')),
    );
  }
}

 

3. 동영상 미리보기+무한재생 코드

// ... 생략

class UploadScreen extends StatefulWidget {
  const UploadScreen({super.key, required this.uploadFile});
  final File uploadFile;

  @override
  State<UploadScreen> createState() => _UploadScreenState();
}

class _UploadScreenState extends State<UploadScreen> {
  VideoPlayerController? _videoPlayerController;
  // ...

  @override
  void initState() {
    _initVideoPlayer();
    super.initState();
  }

  @override
  void dispose() {
    _videoPlayerController?.dispose();
    super.dispose();
  }

  Future _initVideoPlayer() async {
    if (_videoPlayerController == null) {
    // 선택한 영상을
      _videoPlayerController = VideoPlayerController.file(widget.uploadFile);
      await _videoPlayerController!.initialize();
      await _videoPlayerController!.setLooping(true);	// 무한 재생
      await _videoPlayerController!.play();				// 재생 시작
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: _buildAppBar(context),
      body: SafeArea(
        child: Stack(
          children: [
            Container(
              color: AppColor.purpleColor,
              height: 3,
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(0, 3, 0, 134),
              child: VideoPlayer(_videoPlayerController!),
            ),
          ],
        ),
      ),
    );
  }


  AppBar _buildAppBar(BuildContext context) {
    //...
  }
}

 

4. 최종 결과 화면

바텀 시트
갤러리 선택
동영상 촬영
미리보기 화면


참고


갤러리에서 동영상 선택/카메라로 동영상 촬영 코드를 하나의 함수로 만들 수 있다는 게 너무 대박적...

너무 깔끔하다 덜덜

반응형