ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Flutter] 플러터로 비디오 리스트 플레이하기
    프로그래밍 2019.04.19 11:39

    요새, 스키니요가 iOS 버전을 플러터로 개발 하고 있습니다.

    플러터로 비디오를 재생하려면,, 아직은 VideoPlayerController를 사용하는 수 밖에 없는데, 어서 더 좋은 플러그인이 나오면 좋겠습니다.
    특히, 여러 개의 비디오 파일을 연속으로 재생하는 방법을 아직 완전히 이해가 되지 않습니다. 다른 비디오를 재생할 때마다 dispose를 불러야 할지 말지... 테스트 해보니 구지 dispose하지 않아도 메모리가 올라간다는 일은 벌어지지 않더라구요. 그렇지만 클래스 인스턴스가 따로 놓는 건.. 꺼림찍해서 비디오를 새로 재생할 때마다 소멸시키도록 했습니다.

    저의 경우 FutureBuilder를 활용했습니다. 아래 코드 실행 결과, 안드로이드에서는 오류가 없는데, iOS 에서는 여전히 이벤트 리스너에서 Unhandled Exception: Bad state: Future already completed 오류가 있습니다. ㅠㅠ 그래도 재생은 잘 되고 있네요.

    완전히 정리되면 다시 글을 업데이트 해야 겠습니다.

    dependencies

    video_player: ^0.10.0+4
    chewie: ^0.9.7

    code

    VideoPlayerController _controller;
    ChewieController _chewieController; // custom ui
    Future<void> _initializeVideoPlayerFuture;
    
    var _clips = List<PoseClip>(); // video list
    int _playingIndex = -1;
    bool _disposed = false;
    var _isPlaying = false;
    var _isEndPlaying = false;
    
    @override
    void dispose() {
        _disposed = true;
        // Future 가 널이면, 비디오 컨트롤러를 소멸하기전에 위젯에서 비디오가 사용되고 있다는 오류를 피할 수 있다.
        _initializeVideoPlayerFuture = null;
        // 종료하고도 소리가 들려서, 정지시키고 소멸시킴.
        _controller?.pause()?.then((_){
        // dispose VideoPlayerController.
        _controller?.dispose();
        });
        super.dispose();
    }
    
    Future<bool> _clearPrevious() async {
        await _controller?.pause();
        _controller?.removeListener(_controllerListener);
        _controller?.dispose();
        return true;
    }
    
    Future<void> _startPlay(int index) async {
        print("play ---------> $index");
        setState(() {
        _initializeVideoPlayerFuture = null;
        });
        Future.delayed(const Duration(milliseconds: 200), () {
        _clearPrevious().then((_){
            _initializePlay(index);
        });
        });
    }
    
    Future<void> _initializePlay(int index) async {
        final file = await _localStorage.localFile(_clips[index].filePath());
        print("file.exists: ${file.existsSync()}");
        print("file.path: ${file.path}");
        _controller = VideoPlayerController.file(file);
        _controller.addListener(_controllerListener);
        _chewieController = ChewieController(videoPlayerController: _controller);
        _initializeVideoPlayerFuture = _controller.initialize();
    
        setState(() {
        _playingIndex = index;
        });
    }
    
    // tracking status
    Future<void> _controllerListener() async {
      if (_controller == null || _disposed) {
        return;
      }
      if (!_controller.value.initialized) {
        return;
      }
      final position = await _controller.position;
      final duration = _controller.value.duration;
      final isPlaying = position.inMilliseconds < duration.inMilliseconds;
      final isEndPlaying = position.inMilliseconds > 0 && position.inSeconds == duration.inSeconds;
    
      if (_isPlaying != isPlaying || _isEndPlaying != isEndPlaying) {
        _isPlaying = isPlaying;
        _isEndPlaying = isEndPlaying;
        print("$_playingIndex -----> isPlaying=$isPlaying / isCompletePlaying=$isEndPlaying");
        if (isEndPlaying) {
          final isComplete = _playingIndex == _clips.length - 1;
          if (isComplete) {
            print("played all!!");
          } else {
            _startPlay(_playingIndex + 1);
          }
        }
      }
    }
    
    // 위젯 바디 부분 생략
    
    // play view area
    Widget _playView() {
        // FutureBuilder to display a loading spinner until finishes initializing
        return FutureBuilder(
        future: _initializeVideoPlayerFuture,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
            _chewieController.play();
            return AspectRatio(
                aspectRatio: _controller.value.aspectRatio,
                child: Chewie(controller: _chewieController),
            );
            } else {
            return SizedBox(
                height: 300,
                child: Center(child: CircularProgressIndicator()),
            );
            }
        },
        );
    }

    댓글 0

Designed by Tistory.