2023-11-18 10:38:13 +08:00
|
|
|
|
import 'dart:async';
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:auto_orientation/auto_orientation.dart';
|
|
|
|
|
|
import 'package:common_utils/common_utils.dart';
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
|
|
|
|
|
|
|
|
import 'controller_widget.dart';
|
|
|
|
|
|
import 'video_player_slider.dart';
|
|
|
|
|
|
|
|
|
|
|
|
class VideoPlayerControl extends StatefulWidget {
|
2024-08-21 14:12:15 +08:00
|
|
|
|
const VideoPlayerControl({
|
2023-11-18 10:38:13 +08:00
|
|
|
|
Key? key,
|
|
|
|
|
|
}) : super(key: key);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
VideoPlayerControlState createState() => VideoPlayerControlState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class VideoPlayerControlState extends State<VideoPlayerControl> {
|
|
|
|
|
|
VideoPlayerController get controller => ControllerWidget.of(context)!.controller;
|
|
|
|
|
|
bool get videoInit => ControllerWidget.of(context)!.videoInit;
|
|
|
|
|
|
String get title=>ControllerWidget.of(context)!.title;
|
|
|
|
|
|
// 记录video播放进度
|
2024-08-21 14:12:15 +08:00
|
|
|
|
Duration _position = const Duration(seconds: 0);
|
|
|
|
|
|
Duration _totalDuration = const Duration(seconds: 0);
|
2023-11-18 10:38:13 +08:00
|
|
|
|
late Timer _timer; // 计时器,用于延迟隐藏控件ui
|
|
|
|
|
|
bool _hidePlayControl = true; // 控制是否隐藏控件ui
|
|
|
|
|
|
double _playControlOpacity = 0; // 通过透明度动画显示/隐藏控件ui
|
|
|
|
|
|
/// 记录是否全屏
|
|
|
|
|
|
bool get _isFullScreen =>
|
|
|
|
|
|
MediaQuery.of(context).orientation == Orientation.landscape;
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
void dispose() {
|
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
if (_timer != null) {
|
|
|
|
|
|
_timer.cancel();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
|
return GestureDetector(
|
|
|
|
|
|
onDoubleTap: _playOrPause,
|
|
|
|
|
|
onTap: _togglePlayControl,
|
|
|
|
|
|
child: Container(
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
height: double.infinity,
|
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
|
child: WillPopScope(
|
|
|
|
|
|
child: Offstage(
|
|
|
|
|
|
offstage: _hidePlayControl,
|
|
|
|
|
|
child: AnimatedOpacity(
|
|
|
|
|
|
// 加入透明度动画
|
|
|
|
|
|
opacity: _playControlOpacity,
|
2024-08-21 14:12:15 +08:00
|
|
|
|
duration: const Duration(milliseconds: 300),
|
2023-11-18 10:38:13 +08:00
|
|
|
|
child: Column(
|
|
|
|
|
|
children: <Widget>[_top(), _middle(), _bottom(context)],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
onWillPop: _onWillPop,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拦截返回键
|
|
|
|
|
|
Future<bool> _onWillPop() async {
|
|
|
|
|
|
if (_isFullScreen) {
|
|
|
|
|
|
_toggleFullScreen();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 供父组件调用刷新页面,减少父组件的build
|
|
|
|
|
|
void setPosition({position, totalDuration}) {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_position = position;
|
|
|
|
|
|
_totalDuration = totalDuration;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _bottom(BuildContext context) {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
// 底部控件的容器
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
height: 40,
|
2024-08-21 14:12:15 +08:00
|
|
|
|
decoration: const BoxDecoration(
|
2023-11-18 10:38:13 +08:00
|
|
|
|
gradient: LinearGradient(
|
|
|
|
|
|
// 来点黑色到透明的渐变优雅一下
|
|
|
|
|
|
begin: Alignment.bottomCenter,
|
|
|
|
|
|
end: Alignment.topCenter,
|
|
|
|
|
|
colors: [Color.fromRGBO(0, 0, 0, .7), Color.fromRGBO(0, 0, 0, .1)],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
// 加载完成时才渲染,flex布局
|
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
// 播放按钮
|
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
|
iconSize: 26,
|
|
|
|
|
|
icon: Icon(
|
|
|
|
|
|
// 根据控制器动态变化播放图标还是暂停
|
|
|
|
|
|
controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
),
|
|
|
|
|
|
onPressed: _playOrPause,
|
|
|
|
|
|
),
|
|
|
|
|
|
Expanded(
|
|
|
|
|
|
// 相当于前端的flex: 1
|
|
|
|
|
|
child: VideoPlayerSlider(
|
|
|
|
|
|
startPlayControlTimer: _startPlayControlTimer,
|
|
|
|
|
|
timer: _timer,
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
Container(
|
|
|
|
|
|
// 播放时间
|
2024-08-21 14:12:15 +08:00
|
|
|
|
margin: const EdgeInsets.only(left: 10),
|
2023-11-18 10:38:13 +08:00
|
|
|
|
child: Text(
|
|
|
|
|
|
'${DateUtil.formatDateMs(
|
|
|
|
|
|
_position!.inMilliseconds,
|
|
|
|
|
|
format: 'mm:ss',
|
|
|
|
|
|
)}/${DateUtil.formatDateMs(
|
|
|
|
|
|
_totalDuration!.inMilliseconds,
|
|
|
|
|
|
format: 'mm:ss',
|
|
|
|
|
|
)}',
|
2024-08-21 14:12:15 +08:00
|
|
|
|
style: const TextStyle(color: Colors.white),
|
2023-11-18 10:38:13 +08:00
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
IconButton(
|
|
|
|
|
|
// 全屏/横屏按钮
|
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
|
iconSize: 26,
|
|
|
|
|
|
icon: Icon(
|
|
|
|
|
|
// 根据当前屏幕方向切换图标
|
|
|
|
|
|
_isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
),
|
2024-08-21 14:12:15 +08:00
|
|
|
|
onPressed: _toggleFullScreen,
|
2023-11-18 10:38:13 +08:00
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _middle() {
|
|
|
|
|
|
return Expanded(
|
|
|
|
|
|
child: Container(),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Widget _top() {
|
|
|
|
|
|
return Container(
|
|
|
|
|
|
width: double.infinity,
|
|
|
|
|
|
height: 40,
|
2024-08-21 14:12:15 +08:00
|
|
|
|
decoration: const BoxDecoration(
|
2023-11-18 10:38:13 +08:00
|
|
|
|
gradient: LinearGradient(
|
|
|
|
|
|
// 来点黑色到透明的渐变优雅一下
|
|
|
|
|
|
begin: Alignment.bottomCenter,
|
|
|
|
|
|
end: Alignment.topCenter,
|
|
|
|
|
|
colors: [Color.fromRGBO(0, 0, 0, .7), Color.fromRGBO(0, 0, 0, .1)],
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
child: Row(
|
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
|
children: <Widget>[
|
|
|
|
|
|
//在最上层或者不是横屏则隐藏按钮
|
2024-08-21 14:12:15 +08:00
|
|
|
|
if (ModalRoute.of(context)!.isFirst && !_isFullScreen) Container() else IconButton(
|
|
|
|
|
|
icon: const Icon(
|
2023-11-18 10:38:13 +08:00
|
|
|
|
Icons.arrow_back,
|
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
|
),
|
|
|
|
|
|
onPressed: backPress),
|
|
|
|
|
|
Text(
|
|
|
|
|
|
title,
|
2024-08-21 14:12:15 +08:00
|
|
|
|
style: const TextStyle(color: Colors.white),
|
2023-11-18 10:38:13 +08:00
|
|
|
|
),
|
|
|
|
|
|
//在最上层或者不是横屏则隐藏按钮
|
2024-08-21 14:12:15 +08:00
|
|
|
|
if (ModalRoute.of(context)!.isFirst && !_isFullScreen) Container() else IconButton(
|
|
|
|
|
|
icon: const Icon(
|
2023-11-18 10:38:13 +08:00
|
|
|
|
Icons.arrow_back,
|
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
|
),
|
|
|
|
|
|
onPressed: () {},
|
|
|
|
|
|
),
|
|
|
|
|
|
],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void backPress() {
|
|
|
|
|
|
// 如果是全屏,点击返回键则关闭全屏,如果不是,则系统返回键
|
|
|
|
|
|
if (_isFullScreen) {
|
|
|
|
|
|
_toggleFullScreen();
|
|
|
|
|
|
} else if(ModalRoute.of(context)!.isFirst) {
|
|
|
|
|
|
SystemNavigator.pop();
|
|
|
|
|
|
}else{
|
|
|
|
|
|
Navigator.pop(context);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _playOrPause() {
|
|
|
|
|
|
/// 同样的,点击动态播放或者暂停
|
|
|
|
|
|
if (videoInit) {
|
|
|
|
|
|
controller.value.isPlaying ? controller.pause() : controller.play();
|
|
|
|
|
|
_startPlayControlTimer(); // 操作控件后,重置延迟隐藏控件的timer
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _togglePlayControl() {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
if (_hidePlayControl) {
|
|
|
|
|
|
/// 如果隐藏则显示
|
|
|
|
|
|
_hidePlayControl = false;
|
|
|
|
|
|
_playControlOpacity = 1;
|
|
|
|
|
|
_startPlayControlTimer(); // 开始计时器,计时后隐藏
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/// 如果显示就隐藏
|
|
|
|
|
|
if (_timer != null) _timer.cancel(); // 有计时器先移除计时器
|
|
|
|
|
|
_playControlOpacity = 0;
|
2024-08-21 14:12:15 +08:00
|
|
|
|
Future.delayed(const Duration(milliseconds: 500)).whenComplete(() {
|
2023-11-18 10:38:13 +08:00
|
|
|
|
_hidePlayControl = true; // 延迟500ms(透明度动画结束)后,隐藏
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _startPlayControlTimer() {
|
|
|
|
|
|
/// 计时器,用法和前端js的大同小异
|
|
|
|
|
|
if (_timer != null) _timer.cancel();
|
2024-08-21 14:12:15 +08:00
|
|
|
|
_timer = Timer(const Duration(seconds: 3), () {
|
2023-11-18 10:38:13 +08:00
|
|
|
|
/// 延迟3s后隐藏
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
_playControlOpacity = 0;
|
2024-08-21 14:12:15 +08:00
|
|
|
|
Future.delayed(const Duration(milliseconds: 500)).whenComplete(() {
|
2023-11-18 10:38:13 +08:00
|
|
|
|
_hidePlayControl = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void _toggleFullScreen() {
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
if (_isFullScreen) {
|
|
|
|
|
|
/// 如果是全屏就切换竖屏
|
|
|
|
|
|
AutoOrientation.portraitAutoMode();
|
|
|
|
|
|
|
|
|
|
|
|
///显示状态栏,与底部虚拟操作按钮
|
|
|
|
|
|
SystemChrome.setEnabledSystemUIMode(
|
|
|
|
|
|
SystemUiMode.manual, overlays: [SystemUiOverlay.top, SystemUiOverlay.bottom]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
AutoOrientation.landscapeAutoMode();
|
|
|
|
|
|
|
|
|
|
|
|
///关闭状态栏,与底部虚拟操作按钮
|
|
|
|
|
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
|
|
|
|
|
}
|
|
|
|
|
|
_startPlayControlTimer(); // 操作完控件开始计时隐藏
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|