我希望在Flutter中重新创建Snapchat的背靠背视频格式。由于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个项目的侦听器有关。
答案 0 :(得分:0)
这是一个按顺序播放视频的小部件。它缓存上一个和下一个视频,以实现流畅的用户体验。它将侦听器附加到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),
)
],
),
);
}
}
我编写的缓存算法使用的List
具有3个值。 VideoPlayer
使用中间(第二)值。第一个和第三个值用于缓存。此列表上有三种可能性。
当我们使用第一个网址时:
null
VideoPlayerController <- Currently playing
VideoPlayerController <- Cached for next
当我们使用最后一个网址时:
VideoPlayerController <- Cached for previous
VideoPlayerController <- Currently playing
null
其他条件:
VideoPlayerController <- Cached for previous
VideoPlayerController <- Currently playing
VideoPlayerController <- Cached for next