플러터로 동영상 업로더 만들기 (with Riverpod)

2023. 4. 12. 23:39Flutter

반응형

이미지 업로더에 이어 동영상 업로더도 만들어보자

 

이미지 업로더와 동일하게 image_picker도 사용하고, 미리 보기 출력을 위해 video_player도 pubspec.yaml에 추가해 준다

https://pub.dev/packages/image_picker

 

image_picker | Flutter Package

Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.

pub.dev

https://pub.dev/packages/video_player

 

video_player | Flutter Package

Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, and web.

pub.dev

 

이미지 업로드때와 동일하게 동영상을 저장하기 위한 provider를 설정한다.

class SelectedVideoNotifier extends StateNotifier<List<XFile>> {
  SelectedVideoNotifier() : super([]);

  void insertVideo(XFile file) {
    state = [file];
  }

  removeVideo() {
    state = [];
  }
}

final selectedVideoProvider = StateNotifierProvider<SelectedVideoNotifier, List<XFile>>((ref) {
  return SelectedVideoNotifier();
});

final videoStateProvider = StateProvider((ref) => false);

insertVideo를 통해 동영상을 추가, removeVideo를 통해 동영상을 제거한다.

비디오 재생 상태 저장을 위한 videoStateProvider도 작성해 두었다.

 

 

동영상 선택과 ImagePicker와 동영상 재생을 위한 controller를 선언한다.

  final ImagePicker _picker = ImagePicker();
  late VideoPlayerController _controller;

 

비디오를 선택하는 함수를 작성하자

Future pickVideo(ImageSource source) async {
    final XFile? file = await _picker.pickVideo(source: source, maxDuration: const Duration(seconds: 10));
    if (file != null) {
      ref.watch(selectedVideoProvider.notifier).insertVideo(file);
      _controller = VideoPlayerController.network(file.path)
        ..initialize().then((_) {
          _controller.addListener(() {
            setState(() {
              ref.watch(videoStateProvider.notifier).update((state) => _controller.value.isPlaying);
            });
          });
          setState(() {});
        });
    }
  }

동영상을 선택하면 insertVideo를 통해 동영상을 리스트에 담고 동영상의 path를 controller에 전달하여 동영상 재생이 가능하도록 만들어준다.

addListener로 비디오의 재생상태를 확인하며 값을 수정하도록 해주었다.

 

void dispose() {
    super.dispose();
    _controller.dispose();
  }

dispose시 controller도 dispose 시켜준다.

 

 

동영상 업로드 및 미리 보기를 위한 간략한 위젯을 작성했다

 @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        InkWell(
          onTap: () => pickVideo(ImageSource.gallery),
          child: Icon(Icons.add_a_photo_outlined),
        ),
        SizedBox(width: 5.w),
        if (ref.watch(selectedVideoProvider).isNotEmpty && _controller.value.isInitialized)
          Stack(
            children: [
              Container(
                margin: EdgeInsets.symmetric(horizontal: 2.w),
                width: 80.w,
                height: 80.w,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(8.w),
                  child: AspectRatio(
                    aspectRatio: _controller.value.aspectRatio,
                    child: VideoPlayer(_controller),
                  ),
                ),
              ),
              Positioned(
                right: 0,
                child: InkWell(
                  onTap: () => ref.watch(selectedVideoProvider.notifier).removeVideo(),
                  child: Icon(Icons.close),
                ),
              ),
              Positioned(
                left: 0,
                child: InkWell(
                  onTap: () => setState(() {
                    ref.watch(videoStateProvider) ? _controller.pause() : _controller.play();
                  }),
                  child: Icon(ref.watch(videoStateProvider) ? Icons.pause : Icons.play_arrow),
                ),
              )
            ],
          )
      ],
    );
  }

아이콘을 누르면 갤러리를 통해 동영상을 선택할 수 있다

x버튼을 누르면 동영상 리스트를 clear 시키며

재생 버튼을 누르면 버튼이 일시정지 모양으로 바뀌며 동영상이 재생되고, 재생이 끝나면 버튼이 다시 재생 모양으로 바뀌게 된다.

 

 

 

pick_video 전체코드

class PickVideo extends ConsumerStatefulWidget {
  const PickVideo({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _PickVideoState();
}

class _PickVideoState extends ConsumerState<PickVideo> {
  final ImagePicker _picker = ImagePicker();
  late VideoPlayerController _controller;

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  Future pickVideo(ImageSource source) async {
    final XFile? file = await _picker.pickVideo(source: source, maxDuration: const Duration(seconds: 10));
    if (file != null) {
      ref.watch(selectedVideoProvider.notifier).insertVideo(file);
      _controller = VideoPlayerController.network(file.path)
        ..initialize().then((_) {
          _controller.addListener(() {
            setState(() {
              ref.watch(videoStateProvider.notifier).update((state) => _controller.value.isPlaying);
            });
          });
          setState(() {});
        });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        InkWell(
          onTap: () => pickVideo(ImageSource.gallery),
          child: Icon(Icons.add_a_photo_outlined),
        ),
        SizedBox(width: 5.w),
        if (ref.watch(selectedVideoProvider).isNotEmpty && _controller.value.isInitialized)
          Stack(
            children: [
              Container(
                margin: EdgeInsets.symmetric(horizontal: 2.w),
                width: 80.w,
                height: 80.w,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(8.w),
                  child: AspectRatio(
                    aspectRatio: _controller.value.aspectRatio,
                    child: VideoPlayer(_controller),
                  ),
                ),
              ),
              Positioned(
                right: 0,
                child: InkWell(
                  onTap: () => ref.watch(selectedVideoProvider.notifier).removeVideo(),
                  child: Icon(Icons.close),
                ),
              ),
              Positioned(
                left: 0,
                child: InkWell(
                  onTap: () => setState(() {
                    ref.watch(videoStateProvider) ? _controller.pause() : _controller.play();
                  }),
                  child: Icon(ref.watch(videoStateProvider) ? Icons.pause : Icons.play_arrow),
                ),
              )
            ],
          )
      ],
    );
  }
}
반응형