我正在寻找在Flutter中重新创建Snapchat的背靠背视频格式。由于video_player缺少视频播放结束时的回调(否则容易发生回调地狱),我想知道是否有人可以使用一些指针来构建类似的内容。
video_player
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() { runApp(MaterialApp( title: 'My app', // used by the OS task switcher home: MyHomePage(), )); } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { List<VideoPlayerController> _controllers = []; VoidCallback listener; bool _isPlaying = false; int _current = 0; @override void initState() { super.initState(); // Add some sample videos _controllers.add(VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', )); _controllers.add(VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', )); _controllers.add(VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4', )); this.tick(); // Try refreshing by brute force (this isn't going too well) new Timer.periodic(Duration(milliseconds: 100), (Timer t) { int delta = 99999999; if(_controllers[_current].value != null) { delta = (_controllers[_current].value.duration.inMilliseconds - _controllers[_current].value.position.inMilliseconds); } print("Tick " + delta.toString()); if(delta < 500) { _current += 1; this.tick(); } }); } void tick() async { print("Current: " + _current.toString()); await _controllers[_current].initialize(); await _controllers[_current].play(); print("Ready"); setState((){ _current = _current; }); } @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: _controllers[_current].value.aspectRatio, // Use the VideoPlayer widget to display the video child: VideoPlayer(_controllers[_current]), ); } }
我现在播放的是第一部视频,但是第一部和第二部之间的延迟很长。我认为这与我无法摆脱附加到第0个项目的侦听器有关。
这是一个按顺序播放视频的小部件。它缓存上一个和下一个视频,以实现流畅的用户体验。它将侦听器附加到,VideoPlayerController以获取视频的当前位置。当前视频结束时,它也会跳过下一个视频。
VideoPlayerController
class MyHomePage extends StatefulWidget { MyHomePage({Key key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int index = 0; double _progress = 0; bool _changeLock = false; List<VideoPlayerController> _controllers = []; List<String> _urls = [ 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1', 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1', 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1', 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1', 'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4#1', ]; @override void initState() { super.initState(); _initControllers(); } _initControllers() { _controllers.add(null); for (int i = 0; i < _urls.length; i++) { if (i == 2) { break; } _controllers.add(VideoPlayerController.network(_urls[i])); } attachListenerAndInit(_controllers[1]).then((_) { _controllers[1].play().then((_) { setState(() {}); }); }); if (_controllers.length > 2) { attachListenerAndInit(_controllers[2]); } } Future<void> attachListenerAndInit(VideoPlayerController controller) async { if (!controller.hasListeners) { controller.addListener(() { int dur = controller.value.duration.inMilliseconds; int pos = controller.value.position.inMilliseconds; setState(() { if (dur <= pos) { _progress = 0; } else { _progress = (dur - (dur - pos)) / dur; } }); if (dur - pos < 1) { controller.seekTo(Duration(milliseconds: 0)); nextVideo(); } }); } await controller.initialize().then((_) {}); return; } void previousVideo() { if (_changeLock) { return; } _changeLock = true; if (index == 0) { _changeLock = false; return; } _controllers[1]?.pause(); index--; if (index != _urls.length - 2) { _controllers.last?.dispose(); _controllers.removeLast(); } if (index != 0) { _controllers.insert(0, VideoPlayerController.network(_urls[index - 1])); attachListenerAndInit(_controllers.first); } else { _controllers.insert(0, null); } _controllers[1].play().then((_) { setState(() { _changeLock = false; }); }); } void nextVideo() { if (_changeLock) { return; } _changeLock = true; if (index == _urls.length - 1) { _changeLock = false; return; } _controllers[1]?.pause(); index++; _controllers.first?.dispose(); _controllers.removeAt(0); if (index != _urls.length - 1) { _controllers.add(VideoPlayerController.network(_urls[index + 1])); attachListenerAndInit(_controllers.last); } _controllers[1].play().then((_) { setState(() { _changeLock = false; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("${index + 1} of ${_urls.length}"), ), body: Stack( children: <Widget>[ SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: Center(child: VideoPlayer(_controllers[1]))), Positioned( child: Container( height: 10, width: MediaQuery.of(context).size.width * _progress, color: Colors.white, ), ) ], ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ FloatingActionButton( onPressed: previousVideo, child: Icon(Icons.arrow_back), ), SizedBox( width: 24, ), FloatingActionButton( onPressed: nextVideo, child: Icon(Icons.arrow_forward), ) ], ), ); } }
我编写的缓存算法使用的a List具有3个值。VideoPlayer使用中间(第二)值。第一个和第三个值用于缓存。此列表上有三种可能性。
List
VideoPlayer
当我们在第一个网址上时:
null VideoPlayerController <- Currently playing VideoPlayerController <- Cached for next
当我们在最后一个URL上时:
VideoPlayerController <- Cached for previous VideoPlayerController <- Currently playing null
其余条件:
VideoPlayerController <- Cached for previous VideoPlayerController <- Currently playing VideoPlayerController <- Cached for next