实现效果
依赖库
bloc: ^8.0.0
flutter_bloc: ^8.0.0
video_player: ^2.4.0
实现思路
视频的播放是固定套路就不说了,主要是播放控制。 主要是用stack将控制蒙版放在播放器上层,处理模版的点击逻辑,另外主要是进度条的状态处理起来比较麻烦。我在这里将蒙版的显示状态与进度条的状态分成了两个bloc来处理,放在一块没办法处理。因为进度条要一直刷新,会一直更新状态,与显示状态放在一起处理会导致显示混乱。进度条更新通过VideoPlayerController添加listener,在回调中一直发送视频position事件来进行更新。蒙版显示四秒后自动消失,有任何操作时都重置时间。蒙板消失时屏蔽点击事件,通过在点击回调中判断显示状态来return后面的点击逻辑。进度条由slider实现,在onChanged回调中调用seekTo方法控制视频进度。
进度条事件
class ProgressEvent {
int position;
ProgressEvent(this.position);
}
class ProgressState{
int position;
ProgressState({this.position = 1});
}
class ProgressBloc extends Bloc<ProgressEvent, ProgressState>{
ProgressBloc() : super(ProgressState()){
on<ProgressEvent>((event, emit){
emit(ProgressState(position: event.position));
});
}
}
控制蒙版事件
class VideoEvent {
bool isShow;
bool isPlaying;
VideoEvent(this.isShow, this.isPlaying);
}
class VideoState {
final bool isShow;
final bool isPlaying;
const VideoState({this.isShow = true, this.isPlaying = false});
VideoState copyWith({bool? isShow, bool? isPlaying}){
return VideoState(
isShow: isShow ?? this.isShow,
isPlaying: isPlaying ?? this.isPlaying,
);
}
}
class VideoBloc extends Bloc<VideoEvent, VideoState>{
BuildContext context;
VideoBloc(this.context) : super(VideoState()){
on<VideoEvent>(_deal);
}
_deal(VideoEvent event, Emitter<VideoState> emitter) {
emit(state.copyWith(isShow: event.isShow, isPlaying: event.isPlaying));
}
}
播放器实现
class VideoWidget extends StatefulWidget {
String url;
VideoWidget(this.url, {Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() {
return VideoWidgetState();
}
}
class VideoWidgetState extends State<VideoWidget> {
late VideoPlayerController _controller;
late Future<void> _initializeVideoPlayerFuture;
Timer? timer;
int time = 4;
bool totalPlay = false;
@override
void initState() {
super.initState();
_controller = VideoPlayerController.network(widget.url);
_initializeVideoPlayerFuture = _controller.initialize();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context){
return VideoBloc(context);
},
child: FutureBuilder(
future: _initializeVideoPlayerFuture,
builder: (context, state) {
if(state.connectionState == ConnectionState.done) {
return Center(
child: Stack(
children: [
// 播放器
Container(
width: 300,
height: 120,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
shape: BoxShape.rectangle
),
child: AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
),
),
// 控制器蒙板
_playController(),
],
),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
),
);
}
Widget _playController(){
return BlocBuilder<VideoBloc, VideoState>(
builder: (context, state) {
return Opacity(
opacity: state.isShow ? 1 : 0,
// 点击显示和消失
child: GestureDetector(
onTap: (){
if(state.isShow){
BlocProvider.of<VideoBloc>(context).add(VideoEvent(false, state.isPlaying));
} else {
BlocProvider.of<VideoBloc>(context).add(VideoEvent(true, state.isPlaying));
_resetTimer() ? null : _autoHide(context, isPlaying: state.isPlaying);
}
},
child: Container(
color: Colors.black45,
width: 300,
height: 120,
child: Stack(
children: [
// 播放与暂停
Center(
child: IconButton(
onPressed: (){
if(!state.isShow){
return;
}
bool playing = false;
if(state.isPlaying) {
playing = false;
BlocProvider.of<VideoBloc>(context).add(VideoEvent(state.isShow, false));
_controller.pause();
} else {
playing = true;
BlocProvider.of<VideoBloc>(context).add(VideoEvent(state.isShow, true));
_controller.play();
}
_resetTimer() ? null : _autoHide(context, isPlaying: playing);
},
icon: Icon(state.isPlaying ? Icons.pause_circle_filled : Icons.play_circle_fill),
),
),
// 进度控制
ProgressWidget(_controller, _resetTimer, _autoHide)
],
),
),
),
);
}
);
}
// 4秒后消失
_autoHide(BuildContext context,{bool isShow = false, bool isPlaying = false}) {
totalPlay = isPlaying;
Timer.periodic(Duration(seconds: 1), (timer) {
time = time - 1;
this.timer = timer;
if (time == 0){
BlocProvider.of<VideoBloc>(context).add(VideoEvent(isShow, isPlaying));
}
});
}
// 重置消失时间
bool _resetTimer(){
if(timer == null){
return false;
} else {
if(timer!.isActive) {
time = 4;
return true;
} else {
return false;
}
}
}
}
class ProgressWidget extends StatelessWidget{
VideoPlayerController _controller;
bool Function() _resetTimer;
Function(BuildContext context,{bool isShow, bool isPlaying}) _autoHide;
ProgressWidget(this._controller, this._resetTimer, this._autoHide);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context){
return ProgressBloc();
},
child: BlocBuilder<ProgressBloc, ProgressState>(
builder: (context, state) {
_controller.addListener(() {
BlocProvider.of<ProgressBloc>(context).add(ProgressEvent(_controller.value.position.inSeconds));
});
return Positioned(
bottom: 0,
child: Row(
children: [
Padding(
padding: EdgeInsets.only(left: 5),
child: MyText.color(durationTransform(_controller.value.position.inSeconds), fontSize: 8, color: Colors.white,),
),
Container(
width: 250,
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6),
thumbColor: Colors.white,
activeTrackColor: Colors.red,
inactiveTrackColor: Colors.white,
trackHeight: 1
),
child: Slider(
value: _controller.value.position.inSeconds.toDouble(),
min: 0,
max: _controller.value.duration.inSeconds.toDouble(),
// 进度条拖拽控制
onChanged: (v) {
if(BlocProvider.of<VideoBloc>(context).state.isShow){
return;
}
_controller.seekTo(Duration(seconds: v.toInt()));
_controller.play();
BlocProvider.of<VideoBloc>(context).add(VideoEvent(true, true));
_resetTimer() ? null : _autoHide(context, isPlaying: true);
},
),
)
),
Padding(
padding: EdgeInsets.only(right: 5),
child: MyText.color(durationTransform(_controller.value.duration.inSeconds), fontSize: 8, color: Colors.white,),
),
],
),
);
}
),
);
}
}