1.配置
pubspec.yaml 加入
fijkplayer: ^0.10.1
2.直接使用
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:oil_enterprise/common/utils/utils.dart';
import 'package:oil_enterprise/common/widgets/widgets.dart';
class IjkplayerPage extends StatefulWidget {
const IjkplayerPage({Key? key}) : super(key: key);
@override
_IjkplayerPageState createState() => _IjkplayerPageState();
}
class _IjkplayerPageState extends State {
FijkPlayer player = FijkPlayer();
@override
void initState() {
super.initState();
// http://47.104.13.61:7086/live/cameraid/1000036%246/substream/1.m3u8
// https://sample-videos.com/video123/flv/240/big_buck_bunny_240p_10mb.flv
player.setDataSource(
"http://47.104.13.61:7086/live/cameraid/1000036%246/substream/1.m3u8",
autoPlay: true);
}
@override
void dispose() {
super.dispose();
player.release();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: transparentAppBar(context: context),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: FijkView(
player: player,
// panelBuilder: (FijkPlayer player, FijkData data, BuildContext context,
// Size viewSize, Rect texturePos) {
// return CustomFijkPanel(
// player: player,
// buildContext: context,
// viewSize: viewSize,
// texturePos: texturePos);
// },
),
),
);
}
}
class CustomFijkPanel extends StatefulWidget {
final FijkPlayer player;
final BuildContext buildContext;
final Size viewSize;
final Rect texturePos;
const CustomFijkPanel({
required this.player,
required this.buildContext,
required this.viewSize,
required this.texturePos,
});
@override
_CustomFijkPanelState createState() => _CustomFijkPanelState();
}
class _CustomFijkPanelState extends State {
FijkPlayer get player => widget.player;
bool _playing = false;
@override
void initState() {
super.initState();
widget.player.addListener(_playerValueChanged);
}
void _playerValueChanged() {
FijkValue value = player.value;
bool playing = (value.state == FijkState.started);
if (playing != _playing) {
setState(() {
_playing = playing;
});
}
}
@override
Widget build(BuildContext context) {
Rect rect = Rect.fromLTRB(
max(0.0, widget.texturePos.left),
max(0.0, widget.texturePos.top),
min(widget.viewSize.width, widget.texturePos.right),
min(widget.viewSize.height, widget.texturePos.bottom));
return Positioned.fromRect(
rect: rect,
child: Container(
alignment: Alignment.bottomLeft,
child: Container(
width: Screen.width,
height: 40,
color: Colors.grey,
alignment: Alignment.bottomLeft,
child: Row(
children: [
IconButton(
icon: Icon(
_playing ? Icons.pause : Icons.play_arrow,
color: Colors.white,
),
onPressed: () {
_playing ? widget.player.pause() : widget.player.start();
},
),
const SizedBox(width: 20),
IconButton(
icon: Icon(
widget.player.value.fullScreen
? Icons.fullscreen_exit
: Icons.fullscreen,
color: Colors.white,
),
onPressed: () {
widget.player.value.fullScreen
? widget.player.exitFullScreen()
: widget.player.enterFullScreen();
},
)
],
),
),
),
);
}
@override
void dispose() {
super.dispose();
player.removeListener(_playerValueChanged);
}
}
3.自定义UI
需要加入下面两个插件
volume_controller: ^2.0.2
screen_brightness: ^0.0.2
import 'dart:async';
import 'dart:math';
import 'package:fijkplayer/fijkplayer.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'fijkPanelCenterController.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:volume_controller/volume_controller.dart';
class CustomFijkPanel extends StatefulWidget {
final FijkPlayer player;
final BuildContext buildContext;
final Size viewSize;
final Rect texturePos;
final String videoTitle;
final bool isNextNumber;
final bool isPlayAd;
final void Function()? onPlayAd;
final void Function()? onBack;
final void Function()? onError;
final void Function()? onVideoEnd;
final void Function()? onVideoPrepared;
final void Function()? onVideoTimeChange;
/// 播放器控制器具体到源代码目录查看参考fijkplayer\lib\ui\panel.dart
/// ```
/// @param {FijkPlayer} player -
/// @param {BuildContext} buildContext -
/// @param {Size} viewSize -
/// @param {Rect} texturePos -
/// @param {String} videoTitle -
/// @param {bool} isNextNumber - 全屏后是否显示下一集按钮
/// @param {bool} isPlayAd - 是否显示广告按钮
/// @param {void Function()?} onPlayAd - 播放广告
/// @param {void Function()?} onBack - 返回按钮
/// @param {void Function()?} onError - 视频错误点击刷新
/// @param {void Function()?} onVideoEnd - 视频结束
/// @param {void Function()?} onVideoPrepared - 视频完成后台任务到稳定期
/// @param {void Function()?} onVideoTimeChange - 视频时间更新
/// ```
const CustomFijkPanel({
Key? key,
required this.player,
required this.buildContext,
required this.viewSize,
required this.texturePos,
required this.videoTitle,
this.isNextNumber = false,
this.isPlayAd = false,
this.onPlayAd,
this.onBack,
this.onError,
this.onVideoEnd,
this.onVideoPrepared,
this.onVideoTimeChange,
}) : super(key: key);
@override
State createState() {
return _CustomFijkPanelState();
}
}
class _CustomFijkPanelState extends State {
FijkPlayer get player => widget.player;
bool get isFullScreen => player.value.fullScreen;
/// 总时间
Duration _duration = Duration();
/// 动画时间
Duration get _animatedTime => Duration(milliseconds: 400);
/// 是否在播放
bool _playing = false;
/// 后台任务是否初步执行完成是用于正在加载中的状态
bool _prepared = false;
/// 视频状态是否执行完成成为稳定状态与_prepared不一致
bool _playStatePrepared = false;
bool _isPlayCompleted = false;
/// 是否在加载中
bool _buffering = false;
int _bufferingPro = 0;
late StreamSubscription _bufferingSubs;
/// 拖动快进的时间 -1不显示
double _seekPos = -1;
/// 当前时间
Duration _currentPos = Duration();
late StreamSubscription _currentPosSubs;
/// 预加载时间
Duration _bufferPos = Duration();
late StreamSubscription _bufferPosSubs;
late StreamSubscription _bufferPercunt;
/// 控制器隐藏
Timer? _hideTimer;
bool _hideStuff = true;
/// 视频错误
bool _isPlayError = false;
/// 声音 0-1范围
double _currentVolume = 0;
bool _showVolume = false;
/// 屏幕亮度 0-1范围
double _currentBrightness = 0;
bool _showBrightness = false;
int sendCount = 0;
@override
void initState() {
super.initState();
_duration = player.value.duration;
_currentPos = player.currentPos;
_bufferPos = player.bufferPos;
_prepared = player.value.prepared;
var playerState = player.state;
_playing = playerState == FijkState.started;
_isPlayError = playerState == FijkState.error;
_isPlayCompleted = playerState == FijkState.completed;
_playStatePrepared = playerState == FijkState.prepared;
_buffering = player.isBuffering;
initScreenBrightness();
// FijkVolume.setUIMode(FijkVolume.hideUIWhenPlayable);
VolumeController().getVolume().then((volume) {
print("多媒体声音$volume");
_currentVolume = volume;
});
/// 由于变化太小无法监听到基本监听物理按键调整的情况
VolumeController().listener((volume) {
print("多媒体声音变化$volume");
_currentVolume = volume;
});
player.addListener(_playerValueChanged);
/// 当前进度
_currentPosSubs = player.onCurrentPosUpdate.listen((value) {
setState(() {
_currentPos = value;
if (_buffering == true) {
_buffering = false; // 避免有可能出现已经播放时还在显示缓冲中
}
if (_playing == false) {
_playing = true; // 避免播放在false时导致bug
}
});
// 每n次才进入一次不然太频繁发送处理业务太复杂则会增加消耗
if (sendCount % 50 == 0) {
widget.onVideoTimeChange?.call();
}
sendCount++;
});
/// 视频预加载进度
_bufferPosSubs = player.onBufferPosUpdate.listen((value) {
setState(() {
_bufferPos = value;
});
});
/// 视频卡顿回调
_bufferingSubs = player.onBufferStateUpdate.listen((value) {
print("视频加载中$value");
if (value == false && _playing == false) {
_playOrPause();
}
setState(() {
_buffering = value;
});
});
/// 视频卡顿当缓冲量回调
_bufferPercunt = player.onBufferPercentUpdate.listen((value) {
setState(() {
_bufferingPro = value;
});
});
}
@override
void dispose() {
player.removeListener(_playerValueChanged);
VolumeController().removeListener();
_hideTimer?.cancel();
_currentPosSubs.cancel();
_bufferPosSubs.cancel();
_bufferingSubs.cancel();
_bufferPercunt.cancel();
ScreenBrightness.resetScreenBrightness().catchError((error) {
print("重置亮度-异常$error");
});
super.dispose();
}
Future initScreenBrightness() async {
double _brightness = 0.5;
try {
_brightness = await ScreenBrightness.initial;
// print("获取屏幕亮度$_brightness");
} catch (error) {
print("获取屏幕亮度-异常$error");
}
setState(() {
_currentBrightness = _brightness;
});
}
void _playerValueChanged() {
var value = player.value;
if (value.duration != _duration) {
setState(() {
_duration = value.duration;
});
}
var valueState = value.state;
var playing = (valueState == FijkState.started);
var prepared = value.prepared;
var isPlayError = valueState == FijkState.error;
var completed = valueState == FijkState.completed;
if (isPlayError != _isPlayError ||
playing != _playing ||
prepared != _prepared ||
completed != _isPlayCompleted) {
setState(() {
_isPlayError = isPlayError;
_playing = playing;
_prepared = prepared;
_isPlayCompleted = completed;
});
}
/// [value.prepared]不会等于[playStatePrepared]所以单独判断
bool playStatePrepared = valueState == FijkState.prepared;
if (_playStatePrepared != playStatePrepared) {
if (playStatePrepared) {
widget.onVideoPrepared?.call();
}
_playStatePrepared = playStatePrepared;
}
bool isPlayCompleted = valueState == FijkState.completed;
if (isPlayCompleted) {
print("视频状态结束是否还有下一集${widget.isNextNumber}");
if (widget.isNextNumber) {
widget.onVideoEnd?.call();
} else {
_isPlayCompleted = isPlayCompleted;
}
}
}
/// 播放开始
void _playOrPause() {
if (_playing == true) {
player.pause();
} else {
player.start();
}
}
void _startHideTimer() {
_hideTimer?.cancel();
_hideTimer = Timer(const Duration(seconds: 10), () {
setState(() {
_hideStuff = true;
});
});
}
/// 控制器显示隐藏
void _cancelAndRestartTimer() {
if (_hideStuff == true) {
_startHideTimer();
}
setState(() {
_hideStuff = !_hideStuff;
});
}
/// 时间转换显示
String _duration2String(Duration duration) {
if (duration.inMilliseconds < 0) {
return "00:00";
}
String twoDigits(int n) {
if (n >= 10) {
return "$n";
} else {
return "0$n";
}
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
int inHours = duration.inHours;
if (inHours > 0) {
return "$inHours:$twoDigitMinutes:$twoDigitSeconds";
} else {
return "$twoDigitMinutes:$twoDigitSeconds";
}
}
/// 快进视频时间
void _onVideoTimeChangeUpdate(double value) {
if (_duration.inMilliseconds < 0 ||
value < 0 ||
value > _duration.inMilliseconds) {
return;
}
_startHideTimer();
setState(() {
_seekPos = value;
});
}
/// 快进视频松手开始跳时间
void _onVideoTimeChangeEnd(double value) {
var time = _seekPos.toInt();
_currentPos = Duration(milliseconds: time);
print("跳转时间$time ${_duration.inMilliseconds}");
player.seekTo(time).then((value) {
if (!_playing) {
player.start();
}
});
setState(() {
_seekPos = -1;
});
}
/// 获取视频当前时间, 如拖动快进时间则显示快进的时间
double getCurrentVideoValue() {
double duration = _duration.inMilliseconds.toDouble();
double currentValue;
if (_seekPos > 0) {
currentValue = _seekPos;
} else {
currentValue = _currentPos.inMilliseconds.toDouble();
}
currentValue = min(currentValue, duration);
currentValue = max(currentValue, 0);
return currentValue;
}
/// 顶部栏
Widget _buildTopmBar() {
return Stack(
children: [
AnimatedOpacity(
opacity: _hideStuff ? 0 : 1,
duration: _animatedTime,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
// 渐变位置
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.0, 1.0], // [渐变起始点, 渐变结束点]
// 渐变颜色[始点颜色, 结束颜色]
colors: [
Color.fromRGBO(0, 0, 0, 1),
Color.fromRGBO(0, 0, 0, 0),
],
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
!isFullScreen ? Container(width: 40) : _backBtn(),
Expanded(
child: Text(
widget.videoTitle,
style: TextStyle(
color: Colors.white,
fontSize: 14,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
/// 返回按钮 小屏幕状态下显示 或者错误播放等情况
Positioned(
top: 0,
left: 0,
right: 0,
child: Row(
children: [
isFullScreen ? Container() : _backBtn(),
],
),
),
],
);
}
/// 中间区域
Widget _buildCentetContext() {
double currentValue = getCurrentVideoValue();
return FijkPanelCenterController(
size: Size(double.infinity, double.infinity),
onTap: _cancelAndRestartTimer,
onDoubleTap: _playOrPause,
currentTime: currentValue,
onHorizontalStart: _onVideoTimeChangeUpdate,
onHorizontalChange: _onVideoTimeChangeUpdate,
onHorizontalEnd: _onVideoTimeChangeEnd,
currentBrightness: _currentBrightness,
onLeftVerticalStart: (value) {
setState(() {
_showBrightness = true;
});
},
onLeftVerticalChange: (value) {
ScreenBrightness.setScreenBrightness(value);
setState(() {
_currentBrightness = value;
});
},
currentVolume: _currentVolume,
onLeftVerticalEnd: (value) {
setState(() {
_showBrightness = false;
});
},
onRightVerticalStart: (value) {
setState(() {
_showVolume = true;
});
},
onRightVerticalChange: (value) {
VolumeController().setVolume(value, showSystemUI: false);
// FijkVolume.setVol(value);
setState(() {
_currentVolume = value;
});
},
onRightVerticalEnd: (value) {
setState(() {
_showVolume = false;
});
},
builderChild: (context) {
Widget videoLoading = Container(); // 视频缓冲
if (_buffering) {
videoLoading = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 25,
height: 25,
margin: EdgeInsets.only(bottom: 10),
child: CircularProgressIndicator(
backgroundColor: Color.fromRGBO(250, 250, 250, 0.5),
valueColor: AlwaysStoppedAnimation(Colors.white70),
),
),
Text(
"缓冲中 $_bufferingPro %",
style: TextStyle(
color: Colors.white70,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
);
}
return Stack(
children: [
/// 中间内容目前没有东西展示
AnimatedOpacity(
opacity: _hideStuff ? 0 : 1,
duration: _animatedTime,
),
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: videoLoading,
),
/// 快进时间
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: Offstage(
offstage: _seekPos == -1,
child: Center(
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Color.fromRGBO(0, 0, 0, 0.5),
borderRadius: BorderRadius.circular(5),
),
child: Text(
"${_duration2String(
Duration(milliseconds: _seekPos.toInt()),
)} / ${_duration2String(_duration)}",
style: TextStyle(color: Colors.white),
),
),
),
),
),
/// 声音
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: _showVolume ? 1 : 0,
duration: _animatedTime,
child: _buildVolumeOrBrightnessProgress(
type: 1,
value: _currentVolume,
maxValue: 1,
),
),
),
/// 亮度
Positioned(
left: 0,
right: 0,
top: 0,
bottom: 0,
child: AnimatedOpacity(
opacity: _showBrightness ? 1 : 0,
duration: _animatedTime,
child: _buildVolumeOrBrightnessProgress(
type: 2,
value: _currentBrightness,
maxValue: 1,
),
),
),
],
);
},
);
}
/// 声音或亮度进度
Widget _buildVolumeOrBrightnessProgress({
required int type,
required double value,
required double maxValue,
}) {
IconData? iconData;
if (type == 1) {
iconData = value <= 0 ? Icons.volume_off_sharp : Icons.volume_up;
} else {
iconData = Icons.brightness_4;
}
double maxProgressWidth = 90;
return Center(
child: Container(
width: 130,
padding: EdgeInsets.only(top: 5, bottom: 5, right: 10),
decoration: BoxDecoration(
color: Color.fromRGBO(0, 0, 0, 0.5),
borderRadius: BorderRadius.circular(5),
),
child: Row(
children: [
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 5),
child: Icon(
iconData,
size: 20,
color: Colors.white,
),
),
),
Container(
width: maxProgressWidth,
height: 3,
decoration: BoxDecoration(
color: Color.fromRGBO(250, 250, 250, 0.5),
borderRadius: BorderRadius.circular(5),
),
child: Row(
children: [
Container(
width: value / maxValue * maxProgressWidth,
height: 3,
color: Colors.white,
),
],
),
),
],
),
),
);
}
/// 视频时间进度条
Widget _buildVideoTimeBar() {
double currentValue = getCurrentVideoValue();
return FijkSlider(
min: 0,
max: _duration.inMilliseconds.toDouble(),
value: currentValue,
cacheValue: _bufferPos.inMilliseconds.toDouble(),
colors: FijkSliderColors(
playedColor: Color(0xff4075d1),
cursorColor: Colors.white,
baselineColor: Color(0xff807e7c),
bufferedColor: Color(0xff6494e6),
),
// onChangeStart: _onVideoTimeChangeUpdate,
onChanged: _onVideoTimeChangeUpdate,
onChangeEnd: _onVideoTimeChangeEnd,
);
}
/// 底部栏
AnimatedOpacity _buildBottomBar() {
return AnimatedOpacity(
opacity: _hideStuff ? 0 : 1,
duration: _animatedTime,
child: Container(
height: isFullScreen ? 80 : 50,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter, // 渐变位置
end: Alignment.bottomCenter,
stops: [0, 1], // [渐变起始点, 渐变结束点]
colors: [
Color.fromRGBO(0, 0, 0, 0),
Color.fromRGBO(0, 0, 0, 1),
], // 渐变颜色[始点颜色, 结束颜色]
),
),
child: Column(
children: [
Container(
height: isFullScreen ? 25 : 0,
padding: EdgeInsets.symmetric(horizontal: 10),
child: isFullScreen ? _buildVideoTimeBar() : null,
),
Expanded(
child: Row(
children: [
/// 播放按钮
GestureDetector(
onTap: _playOrPause,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15),
color: Colors.transparent,
height: double.infinity,
child: Icon(
_playing ? Icons.pause : Icons.play_arrow,
color: Colors.white,
size: 18,
),
),
),
/// 下一集按钮(全屏下可以看到)
Offstage(
offstage: !widget.isNextNumber || !isFullScreen,
child: GestureDetector(
onTap: widget.onVideoEnd,
child: Container(
padding: EdgeInsets.only(right: 15),
color: Colors.transparent,
height: double.infinity,
child: Icon(
Icons.skip_next_sharp,
color: Colors.white,
size: 18,
),
),
),
),
/// 当前时长
Text(
_duration2String(_currentPos),
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: !isFullScreen ? _buildVideoTimeBar() : null,
),
),
/// 总时长
Text(
_duration2String(_duration),
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
/// 全屏按钮
isFullScreen
? SizedBox(width: 30)
: GestureDetector(
onTap: () {
player.enterFullScreen();
Future.delayed(Duration(seconds: 1), () {
setViewStatusBar(true);
});
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 13),
color: Colors.transparent,
height: double.infinity,
child: Icon(
isFullScreen
? Icons.fullscreen_exit
: Icons.fullscreen,
color: Colors.white,
size: 25,
),
),
),
],
),
),
],
),
),
);
}
/// 返回按钮
Widget _backBtn() {
return GestureDetector(
onTap: widget.onBack,
child: Container(
padding: EdgeInsets.all(8),
child: Icon(
Icons.chevron_left,
size: 34,
color: Colors.white,
),
),
);
}
/// 视频异常状态
Widget _renderVideoStatusView() {
var bgImg = BoxDecoration(
color: Colors.black,
// image: DecorationImage(
// fit: BoxFit.cover,
// image: AssetImage(
// "xxx.jpg", // 可以设置一个背景图
// ),
// ),
);
if (_isPlayError) {
/// 错误
return GestureDetector(
onTap: widget.onError,
child: Container(
width: double.infinity,
height: double.infinity,
decoration: bgImg,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(bottom: 15),
child: Icon(
Icons.error_rounded,
color: Colors.white70,
size: 70,
),
),
RichText(
text: TextSpan(
text: "播放异常!",
style: TextStyle(
color: Colors.white70,
fontSize: 14,
fontWeight: FontWeight.w600,
),
children: [
TextSpan(
text: "刷新",
style: TextStyle(
color: Color(0xff79b0ff),
),
)
],
),
),
],
),
),
);
} else if (widget.isPlayAd) {
return Container(
width: double.infinity,
height: double.infinity,
decoration: bgImg,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"要看广告",
style: TextStyle(
color: Colors.white,
fontSize: 14,
),
),
SizedBox(height: 10),
Text(
"播放一段视频广告",
style: TextStyle(
color: Colors.white70,
fontSize: 12.5,
),
),
SizedBox(height: 20),
GestureDetector(
onTap: widget.onPlayAd,
child: Container(
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 20,
),
decoration: BoxDecoration(
color: Color(0xff2d73ed),
borderRadius: BorderRadius.circular(5),
),
child: Text(
"点击广告",
style: TextStyle(
color: Colors.white,
fontSize: 13,
),
),
),
),
],
),
);
} else if (!_prepared) {
/// 加载中
return Container(
width: double.infinity,
height: double.infinity,
decoration: bgImg,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50,
height: 50,
margin: EdgeInsets.only(bottom: 20),
child: CircularProgressIndicator(
backgroundColor: Colors.white70,
valueColor: AlwaysStoppedAnimation(Color(0xff79b0ff)),
),
),
Text(
"努力加载中...",
style: TextStyle(
color: Colors.white70,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
);
} else if (_isPlayCompleted) {
/// 是否显示播放完
return GestureDetector(
onTap: () {
player.start();
setState(() {
_isPlayCompleted = false;
});
},
child: Container(
width: double.infinity,
height: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.play_circle_outline_outlined,
size: 30,
color: Colors.white70,
),
SizedBox(height: 10),
Text(
"重新播放",
style: TextStyle(
color: Colors.white70,
fontSize: 12.5,
),
),
],
),
),
);
} else {
return Container();
}
}
/// 设置页面全屏化显示隐藏状态栏和虚拟按键
setViewStatusBar(bool isHide) {
if (isHide) {
SystemChrome.setEnabledSystemUIOverlays([]);
} else {
SystemChrome.setEnabledSystemUIOverlays([
SystemUiOverlay.top,
SystemUiOverlay.bottom,
]);
}
}
@override
Widget build(BuildContext context) {
if (_isPlayError || !_prepared || _isPlayCompleted || widget.isPlayAd) {
/// 错误播放 | 没加载好 | 播放完成没有下一集
return Stack(
children: [
_renderVideoStatusView(),
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
width: double.infinity,
color: Colors.transparent,
alignment: Alignment.centerLeft,
child: _backBtn(),
),
),
],
);
} else {
var viewSize = widget.viewSize;
return Positioned.fromRect(
rect: Rect.fromLTWH(0, 0, viewSize.width, viewSize.height),
child: Column(
children: [
_buildTopmBar(),
Expanded(
child: _buildCentetContext(),
),
_buildBottomBar(),
],
),
);
}
}
}
// ignore_for_file: file_names
import 'package:flutter/material.dart';
class FijkPanelCenterController extends StatefulWidget {
final Size size;
final void Function()? onTap;
final void Function()? onDoubleTap;
final double currentTime;
final void Function(double)? onHorizontalStart;
final void Function(double)? onHorizontalChange;
final void Function(double)? onHorizontalEnd;
final double currentBrightness;
final void Function(double)? onLeftVerticalStart;
final void Function(double)? onLeftVerticalChange;
final void Function(double)? onLeftVerticalEnd;
final double currentVolume;
final void Function(double)? onRightVerticalStart;
final void Function(double)? onRightVerticalChange;
final void Function(double)? onRightVerticalEnd;
final Widget Function(BuildContext context) builderChild;
/// 自定义的触摸控制器
/// ```
/// @param {Size} size - 框大小
/// @param {void Function()?} onTap -
/// @param {void Function()?} onDoubleTap -
/// @param {double} currentTime - 传入当前视频时间onHorizontal时计算用得到
/// @param {void Function(double)?} onHorizontalStart -
/// @param {void Function(double)?} onHorizontalChange -
/// @param {void Function(double)?} onHorizontalEnd -
/// @param {double} currentBrightness - 传入当前系统亮度onLeftVertical时计算用得到
/// @param {void Function(double)?} onLeftVerticalStart - 左边上下拖动(亮度)
/// @param {void Function(double)?} onLeftVerticalChange -
/// @param {void Function(double)?} onLeftVerticalEnd -
/// @param {double} currentVolume - 传入当前系统声音onRightVertical时计算用得到
/// @param {void Function(double)?} onRightVerticalStart - 右边上下拖动(声音)
/// @param {void Function(double)?} onRightVerticalChange -
/// @param {void Function(double)?} onRightVerticalEnd -
/// @param {Widget Function(BuildContext context)} builderChild - 子节点内容直接由外界传入
/// ```
const FijkPanelCenterController({
Key? key,
required this.size,
this.onTap,
this.onDoubleTap,
required this.currentTime,
this.onHorizontalStart,
this.onHorizontalChange,
this.onHorizontalEnd,
required this.currentBrightness,
this.onLeftVerticalStart,
this.onLeftVerticalChange,
this.onLeftVerticalEnd,
required this.currentVolume,
this.onRightVerticalStart,
this.onRightVerticalChange,
this.onRightVerticalEnd,
required this.builderChild,
}) : super(key: key);
@override
State createState() {
return _FijkPanelCenterController();
}
}
class _FijkPanelCenterController extends State {
/// 上下滑动时在开始的时候记录 0-左边 1-右边
int verticalType = 0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
onDoubleTap: widget.onDoubleTap,
onHorizontalDragStart: (details) {
widget.onHorizontalStart?.call(widget.currentTime);
},
onHorizontalDragUpdate: (details) {
// 来自上次更新以来,指针在事件接收器的坐标空间中沿主轴移动的量。
double deltaDx = details.delta.dx;
if (deltaDx == 0) {
return; // 避免某些手机会返回0.0
}
var dragValue = (deltaDx * 4000) + widget.currentTime;
widget.onHorizontalChange?.call(dragValue);
},
onHorizontalDragEnd: (details) {
widget.onHorizontalEnd?.call(widget.currentTime);
},
onVerticalDragStart: (details) {
double dx = details.localPosition.dx;
var winWidth = context.size?.width ?? 0;
if (dx < winWidth / 2) {
verticalType = 0;
widget.onLeftVerticalStart?.call(widget.currentBrightness);
} else {
verticalType = 1;
widget.onRightVerticalStart?.call(widget.currentVolume);
}
},
onVerticalDragUpdate: (details) {
double deltaDy = details.delta.dy;
if (deltaDy == 0) {
return; // 避免某些手机会返回0.0
}
double moveTo = 0;
if (deltaDy > 0) {
moveTo = -0.01;
} else {
moveTo = 0.01;
}
double dragValue = 0;
switch (verticalType) {
case 0:
dragValue = moveTo + widget.currentBrightness;
if (dragValue > 1) {
dragValue = 1;
} else if (dragValue < 0) {
dragValue = 0;
}
print("设置亮度$dragValue");
widget.onLeftVerticalChange?.call(dragValue);
break;
case 1:
dragValue = moveTo + widget.currentVolume;
if (dragValue > 1) {
dragValue = 1;
} else if (dragValue < 0) {
dragValue = 0;
}
print("设置声音$dragValue");
widget.onRightVerticalChange?.call(dragValue);
break;
default:
}
},
onVerticalDragEnd: (details) {
switch (verticalType) {
case 0:
widget.onLeftVerticalEnd?.call(widget.currentBrightness);
break;
case 1:
widget.onRightVerticalEnd?.call(widget.currentVolume);
break;
default:
}
},
child: Container(
width: widget.size.width,
height: widget.size.height,
color: Colors.transparent,
child: widget.builderChild(context),
),
);
}
}
温馨提示:如果想全屏播放正常,Xcode需要勾选横竖屏