提交 8a544176 authored 作者: jungleiOS's avatar jungleiOS

播放器加入清晰度切换

上级 566881a7
......@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:gz_video_player/video.dart';
import 'package:gz_video_player/video_control_bar_style.dart';
import 'package:gz_video_player/video_definition.dart';
import 'package:gz_video_player/video_loading_style.dart';
import 'package:gz_video_player/video_play_options.dart';
import 'package:gz_video_player/video_progress_style.dart';
......@@ -72,7 +73,7 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
// String videoUrl = "http://vodkgeyttp8.vod.126.net/cloudmusic/1241/core/e30b/aec700ee466da6c8ce51d12953e7b89f.mp4?wsSecret=a6d7342a3ea018d632b3d7ce56ffd11f&wsTime=1580815486";
// String videoUrl = "http://vod.anyrtc.cc/364c01b9c8ca4e46bd65e7307887341d/34688ef93da349628d5e4efacf8a5167-9fd7790c8f5862b09c350e4a916b203d.mp4";
String videoUrl =
'https://mpv.videocc.net/8f38fa3352/5/8f38fa3352cdb65e6773173ae8c767b5_1.mp4';
'https://mpv.videocc.net/8f38fa3352/5/8f38fa33520aa63d3daa7a7b3eb6e4c5_1.mp4';
String mainSubtitles = ""; //主字幕
String subSubtitles = ""; //辅字幕
bool _isPlaying = false;
......@@ -90,6 +91,7 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
GZVideoPlayerState? state;
VideoPlayerController? videoPlayerController;
List<VideoDefinitionItem> definitionList = [];
@override
void initState() {
......@@ -104,6 +106,24 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
title: '测试视频',
),
];
definitionList = [
VideoDefinitionItem(
dataSource:
'https://mpv.videocc.net/8f38fa3352/5/8f38fa33520aa63d3daa7a7b3eb6e4c5_1.mp4',
title: 'LD',
),
VideoDefinitionItem(
dataSource:
'https://mpv.videocc.net/8f38fa3352/5/8f38fa33520aa63d3daa7a7b3eb6e4c5_2.mp4',
title: 'SD',
),
VideoDefinitionItem(
dataSource:
'https://mpv.videocc.net/8f38fa3352/5/8f38fa33520aa63d3daa7a7b3eb6e4c5_3.mp4',
title: 'HD',
),
];
super.initState();
}
......@@ -131,6 +151,7 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
autoplay: true,
allowScrubbing: true,
startPosition: const Duration(seconds: 0),
definitionList: definitionList,
),
/// 自定义视频样式
......@@ -146,19 +167,20 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
showPlayIcon: true,
videoLoadingStyle: VideoLoadingStyle(
/// 重写部分(二选一)
// 重写Loading的widget
// customLoadingIcon: CircularProgressIndicator(strokeWidth: 2.0),
// 重写Loading 下方的Text widget
// customLoadingText: Text("加载中..."),
/// 设置部分(二选一)
// 设置Loading icon 下方的文字
loadingText: "Loading...",
// 设置loading icon 下方的文字颜色
loadingTextFontColor: Colors.white,
// 设置loading icon 下方的文字大小
loadingTextFontSize: 20,
),
/// 重写部分(二选一)
// 重写Loading的widget
// customLoadingIcon: CircularProgressIndicator(strokeWidth: 2.0),
// 重写Loading 下方的Text widget
// customLoadingText: Text("加载中..."),
/// 设置部分(二选一)
// 设置Loading icon 下方的文字
// loadingText: "Loading...",
// // 设置loading icon 下方的文字颜色
// loadingTextFontColor: Colors.white,
// // 设置loading icon 下方的文字大小
// loadingTextFontSize: 20,
),
/// 自定义顶部控制栏
videoTopBarStyle: VideoTopBarStyle(
......@@ -307,7 +329,25 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
// ),
/// 决定控制栏的元素以及排序,示例见上方图3
// itemList: [
// itemList: _isFullscreen
// ? [
// 'play',
// 'progress',
// 'time',
// 'speed',
// 'definition',
// 'fullscreen',
// ]
// : [
// 'play',
// 'progress',
// 'time',
// // 'speed',
// // 'definition',
// 'fullscreen',
// ],
// [
// "rewind",
// "play",
// "forward",
......@@ -511,7 +551,8 @@ class _VideoPlayerPageState extends State<VideoPlayerPage> {
title: 'to second page',
onTap: () {
videoPlayerController?.pause();
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) {
return const OtherTestPage();
}));
},
......
......@@ -5,6 +5,7 @@ import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gz_orientation/gz_orientation.dart';
import 'package:gz_video_player/video_definition.dart';
import 'package:gz_video_player/video_play_options.dart';
import 'package:gz_video_player/video_speed.dart';
import 'package:gz_video_player/video_style.dart';
......@@ -12,6 +13,7 @@ import 'package:gz_video_player/widget/brightness.dart';
import 'package:gz_video_player/widget/default_progress_bar.dart';
import 'package:gz_video_player/widget/linear_progress_bar.dart';
import 'package:gz_video_player/widget/video_bottom_bar.dart';
import 'package:gz_video_player/widget/video_definition_side_bar.dart';
import 'package:gz_video_player/widget/video_loading_view.dart';
import 'package:gz_video_player/widget/video_speed_bar.dart';
import 'package:gz_video_player/widget/video_top_bar.dart';
......@@ -46,6 +48,7 @@ class GZVideoPlayer extends StatefulWidget {
this.onPop,
this.onProgressDrag,
this.onSpeedChange,
this.onDefinitionChange,
}) : playOptions = playOptions ?? VideoPlayOptions(),
videoStyle = videoStyle ?? VideoStyle();
......@@ -99,6 +102,9 @@ class GZVideoPlayer extends StatefulWidget {
/// 倍速改变的回调
final ValueChanged<VideoSpeedItem>? onSpeedChange;
/// 清晰度改变回调
final ValueChanged<VideoDefinitionItem>? onDefinitionChange;
@override
State<GZVideoPlayer> createState() => GZVideoPlayerState();
......@@ -121,6 +127,9 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
late Animation<double> _controlTopBarAnimation;
late Animation<double> _controlBottomBarAnimation;
/// 清晰度控制器
AnimationController? _definitionBarController;
/// 是否全屏
bool _fullScreened = false;
bool _initialized = false;
......@@ -154,6 +163,9 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
/// 倍数item
VideoSpeedItem _speedItem = VideoSpeedItem();
/// 清晰度按钮标题
String _definitionTitle = '';
/// 获取屏幕大小
Size get screenSize => MediaQuery.of(context).size;
......@@ -206,6 +218,17 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
/// 倍数按钮标题
_speedTitle = widget.videoStyle.videoSpeedButtonStyle.title;
/// 清晰度按钮标题
_definitionTitle = widget.videoStyle.videoDefinitionButtonStyle.title;
if (widget.playOptions.definitionList.isNotEmpty) {
for (VideoDefinitionItem item in widget.playOptions.definitionList) {
if (widget.dataSource == item.dataSource) {
_definitionTitle = item.title;
break;
}
}
}
_initPlayer();
}
......@@ -217,8 +240,9 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
@override
void didUpdateWidget(GZVideoPlayer oldWidget) {
if (oldWidget.dataSource != widget.dataSource) {
_updateDataSource();
if (oldWidget.dataSource != widget.dataSource ||
oldWidget.sourceType != widget.sourceType) {
_updateDataSource(type: widget.sourceType, source: widget.dataSource);
}
super.didUpdateWidget(oldWidget);
}
......@@ -281,8 +305,15 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
});
}
void _updateDataSource() async {
VideoPlayerController tempController = _createVideoPlayerController();
void _updateDataSource({
required DataSourceType type,
required dynamic source,
VoidCallback? initializeCallback,
}) async {
VideoPlayerController tempController = _createVideoPlayerController(
type: type,
source: source,
);
/// 释放老的 controller
await _controller.pause();
......@@ -292,7 +323,10 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
/// 设置新的 controller 并开启监听和初始化
_controller = tempController;
_controller.addListener(_listener);
_controller.initialize().then(_handleInit);
_controller.initialize().then((_) {
_handleInit();
initializeCallback?.call();
});
setState(() {});
}
......@@ -347,16 +381,19 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
});
} else {
if (oPosition >= oDuration) {
resetVideoPlayer();
// resetVideoPlayer();
if (widget.onEnded != null) {
widget.onEnded!(_controller.value);
}
}
}
_isBuffing = _controller.value.isBuffering;
setState(() {});
}
}
void _handleInit(_) {
void _handleInit() {
debugPrint("初始化完成");
setVolume(_volume.value);
widget.onInit?.call(_controller);
......@@ -376,9 +413,12 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
void _initPlayer() {
if (widget.dataSource == null || widget.dataSource == "") return;
_controller = _createVideoPlayerController()
_controller = _createVideoPlayerController(
source: widget.dataSource,
type: widget.sourceType,
)
..addListener(_listener)
..initialize().then(_handleInit)
..initialize().then((_) => _handleInit())
..setLooping(widget.playOptions.loop);
}
......@@ -505,16 +545,19 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
}
/// 创建video controller
VideoPlayerController _createVideoPlayerController() {
switch (widget.sourceType) {
VideoPlayerController _createVideoPlayerController({
required DataSourceType type,
required dynamic source,
}) {
switch (type) {
case DataSourceType.asset:
return VideoPlayerController.asset(widget.dataSource);
return VideoPlayerController.asset(source);
case DataSourceType.file:
return VideoPlayerController.file(File(widget.dataSource));
return VideoPlayerController.file(File(source));
case DataSourceType.contentUri:
return VideoPlayerController.contentUri(Uri.parse(widget.dataSource));
return VideoPlayerController.contentUri(Uri.parse(source));
case DataSourceType.network:
return VideoPlayerController.networkUrl(Uri.parse(widget.dataSource));
return VideoPlayerController.networkUrl(Uri.parse(source));
}
}
......@@ -561,7 +604,7 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
///线条视频进度条
'progress': Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: VideoLinearProgressBar(_controller,
allowScrubbing: widget.playOptions.allowScrubbing,
onProgressDrag: widget.onProgressDrag,
......@@ -614,6 +657,23 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
),
),
),
'definition': widget.playOptions.definitionList.isNotEmpty
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector(
onTap: () {
if (_showMenu) {
toggleControls();
}
_definitionBarController?.forward();
},
child: Text(
_definitionTitle,
style: widget.videoStyle.videoSpeedButtonStyle.textStyle,
),
),
)
: const SizedBox(),
'fullscreen': Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: GestureDetector(
......@@ -671,7 +731,7 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
child: widget.videoStyle.playIcon,
),
)
: const Text(""),
: const SizedBox(),
/// 是否显示重播按钮
_initialized && _isEnded
......@@ -680,12 +740,15 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
child: GestureDetector(
onTap: () {
_isEnded = false;
_updateDataSource();
_updateDataSource(
source: widget.dataSource,
type: widget.sourceType,
);
},
child: widget.videoStyle.replayIcon,
),
)
: const Text(""),
: const SizedBox(),
/// 主字幕
widget.videoStyle.videoSubtitlesStyle.mainTitle ??
......@@ -743,6 +806,29 @@ class GZVideoPlayerState extends State<GZVideoPlayer>
},
),
/// 清晰度设置栏
VideoDefinitionSideBar(
itemList: widget.playOptions.definitionList,
onInit: (AnimationController definitionBarController) {
_definitionBarController = definitionBarController;
},
onTapItem: (int index) {
Duration position = _controller.value.position;
VideoDefinitionItem item = widget.playOptions.definitionList[index];
_updateDataSource(
type: item.sourceType,
source: item.dataSource,
initializeCallback: () {
_controller.seekTo(position);
},
);
_isBuffing = true;
_definitionTitle = item.title;
widget.onDefinitionChange?.call(item);
setState(() {});
},
),
widget.brightnessWidget ??
BrightnessWidget(
brightness: _brightness,
......
......@@ -57,12 +57,11 @@ class VideoControlBarStyle {
semanticLabel: '退出全屏',
),
this.itemList = const [
'rewind',
'play',
'forward',
'progress',
'time',
'speed',
'definition',
'fullscreen',
],
}) : progressStyle = progressStyle ?? VideoProgressStyle();
......
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoDefinitionItem {
final dynamic dataSource;
final String title;
final DataSourceType sourceType;
VideoDefinitionItem({
required this.dataSource,
this.title = '',
this.sourceType = DataSourceType.network,
});
}
class VideoDefinitionItemStyle {
final TextStyle textStyle;
final TextAlign textAlign;
final EdgeInsetsGeometry padding;
final Color selectTextColor;
VideoDefinitionItemStyle({
this.textStyle = const TextStyle(
color: Colors.white,
fontSize: 16.0,
fontWeight: FontWeight.normal,
),
this.textAlign = TextAlign.center,
this.padding = const EdgeInsets.symmetric(vertical: 6.0),
this.selectTextColor = Colors.blue,
});
}
class VideoDefinitionButtonStyle {
final TextStyle textStyle;
final String title;
VideoDefinitionButtonStyle({
this.title = '清晰度',
this.textStyle = const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
color: Colors.white,
),
});
}
\ No newline at end of file
......@@ -5,9 +5,9 @@ class VideoLoadingStyle {
VideoLoadingStyle({
this.customLoadingIcon = const CircularProgressIndicator(strokeWidth: 2.0),
this.customLoadingText,
this.loadingText = "Loading...",
this.loadingTextFontColor = Colors.white,
this.loadingTextFontSize = 20,
this.loadingText = "数据缓冲中...",
this.loadingTextFontColor = Colors.blue,
this.loadingTextFontSize = 16,
});
final Widget customLoadingIcon;
......
import 'package:gz_video_player/video_definition.dart';
import 'package:gz_video_player/video_speed.dart';
/// 自定义播放参数
......@@ -13,6 +14,7 @@ class VideoPlayOptions {
this.autoplay = true,
this.allowScrubbing = true,
List<VideoSpeedItem>? speedList,
this.definitionList = const [],
}) : speedList = speedList ??
[
VideoSpeedItem(speed: 0.5, title: '0.5X'),
......@@ -53,4 +55,7 @@ class VideoPlayOptions {
/// 倍数列表
final List<VideoSpeedItem> speedList;
/// 清晰度列表
final List<VideoDefinitionItem> definitionList;
}
import 'package:flutter/material.dart';
import 'package:gz_video_player/video_brightness_style.dart';
import 'package:gz_video_player/video_control_bar_style.dart';
import 'package:gz_video_player/video_definition.dart';
import 'package:gz_video_player/video_loading_style.dart';
import 'package:gz_video_player/video_speed.dart';
import 'package:gz_video_player/video_subtitles.dart';
......@@ -16,6 +17,8 @@ class VideoStyle {
VideoLoadingStyle? videoLoadingStyle,
VideoSpeedButtonStyle? videoSpeedButtonStyle,
VideoSpeedItemStyle? videoSpeedItemStyle,
VideoDefinitionItemStyle? videoDefinitionItemStyle,
VideoDefinitionButtonStyle? videoDefinitionButtonStyle,
VideoBrightnessStyle? videoBrightnessStyle,
VideoVolumeStyle? videoVolumeStyle,
// this.videoCover = "",
......@@ -40,6 +43,8 @@ class VideoStyle {
videoSpeedButtonStyle =
videoSpeedButtonStyle ?? VideoSpeedButtonStyle(),
videoSpeedItemStyle = videoSpeedItemStyle ?? VideoSpeedItemStyle(),
videoDefinitionItemStyle = videoDefinitionItemStyle ?? VideoDefinitionItemStyle(),
videoDefinitionButtonStyle = videoDefinitionButtonStyle ?? VideoDefinitionButtonStyle(),
videoBrightnessStyle = videoBrightnessStyle ?? VideoBrightnessStyle(),
videoVolumeStyle = videoVolumeStyle ?? VideoVolumeStyle();
......@@ -47,10 +52,12 @@ class VideoStyle {
final VideoControlBarStyle videoControlBarStyle; //进度条样式
final VideoSubtitles videoSubtitlesStyle; //字幕样式
final VideoLoadingStyle videoLoadingStyle; //loading样式
final VideoSpeedButtonStyle videoSpeedButtonStyle;
final VideoSpeedItemStyle videoSpeedItemStyle;
final VideoBrightnessStyle videoBrightnessStyle;
final VideoVolumeStyle videoVolumeStyle;
final VideoSpeedButtonStyle videoSpeedButtonStyle; // 倍速按钮样式
final VideoSpeedItemStyle videoSpeedItemStyle; // 倍速 item 样式
final VideoDefinitionItemStyle videoDefinitionItemStyle; // 清晰度 item 样式
final VideoDefinitionButtonStyle videoDefinitionButtonStyle; // 清晰度按钮样式
final VideoBrightnessStyle videoBrightnessStyle; // 屏幕亮度样式
final VideoVolumeStyle videoVolumeStyle; // 音量样式
// final String videoCover; //视频封面
final Widget playIcon; //暂停时显示
final Widget replayIcon; //暂停时显示
......
......@@ -118,7 +118,7 @@ class _VideoScrubberState extends State<_VideoScrubber> {
}
_controllerWasPlaying = controller.value.isPlaying;
if (_controllerWasPlaying) {
controller.pause();
// controller.pause();
}
},
onHorizontalDragUpdate: (DragUpdateDetails details) {
......@@ -130,7 +130,7 @@ class _VideoScrubberState extends State<_VideoScrubber> {
},
onHorizontalDragEnd: (DragEndDetails details) {
if (_controllerWasPlaying) {
controller.play();
// controller.play();
}
},
onTapDown: (TapDownDetails details) {
......
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:gz_video_player/video_definition.dart';
class VideoDefinitionSideBar extends StatefulWidget {
VideoDefinitionSideBar({
super.key,
this.itemList = const [],
VideoDefinitionItemStyle? videoDefinitionItemStyle,
this.onTapItem,
this.onInit,
this.defaultSource,
}) : videoDefinitionItemStyle =
videoDefinitionItemStyle ?? VideoDefinitionItemStyle();
final List<VideoDefinitionItem> itemList;
final VideoDefinitionItemStyle videoDefinitionItemStyle;
final ValueChanged<int>? onTapItem;
final ValueChanged<AnimationController>? onInit;
final dynamic defaultSource;
@override
State<VideoDefinitionSideBar> createState() => _VideoDefinitionSideBarState();
}
class _VideoDefinitionSideBarState extends State<VideoDefinitionSideBar>
with SingleTickerProviderStateMixin {
List<VideoDefinitionItem> get itemList => widget.itemList;
VideoDefinitionItemStyle get videoDefinitionItemStyle =>
widget.videoDefinitionItemStyle;
ValueChanged<int>? get onTapItem => widget.onTapItem;
ValueChanged<AnimationController>? get onInit => widget.onInit;
dynamic get defaultSource => widget.defaultSource;
late final AnimationController _controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
late final Animation<Offset> _offsetAnimation = Tween<Offset>(
begin: const Offset(1, 0),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
HitTestBehavior? _behavior;
int _selectedIndex = 0;
@override
void initState() {
super.initState();
onInit?.call(_controller);
_controller.addListener(() {
if (_controller.status == AnimationStatus.forward) {
_behavior = HitTestBehavior.translucent;
setState(() {});
}
if (_controller.status == AnimationStatus.reverse) {
_behavior = null;
setState(() {});
}
});
}
void changeDefaultIndex() {
for (int i = 0; i < itemList.length; i++) {
VideoDefinitionItem item = itemList[i];
if (defaultSource == item.dataSource) {
_selectedIndex = i;
}
}
}
@override
void didUpdateWidget(covariant VideoDefinitionSideBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.defaultSource != defaultSource ||
oldWidget.itemList != itemList) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
changeDefaultIndex();
setState(() {});
});
}
}
@override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width / 3;
return GestureDetector(
behavior: _behavior,
onTap: () {
_controller.reverse();
},
child: Align(
alignment: Alignment.centerRight,
child: SlideTransition(
position: _offsetAnimation,
child: Container(
width: width,
color: Colors.black.withOpacity(0.6),
alignment: Alignment.center,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _renderItems(),
),
),
),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
List<Widget> _renderItems() {
List<Widget> widgets = [];
for (int i = 0; i < itemList.length; i++) {
VideoDefinitionItem item = itemList[i];
Widget widget = GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () async {
_controller.reverse();
if (_selectedIndex != i) {
onTapItem?.call(i);
_selectedIndex = i;
setState(() {});
}
},
child: Padding(
padding: videoDefinitionItemStyle.padding,
child: Text(
item.title,
textAlign: videoDefinitionItemStyle.textAlign,
style: i == _selectedIndex
? videoDefinitionItemStyle.textStyle
.copyWith(color: videoDefinitionItemStyle.selectTextColor)
: videoDefinitionItemStyle.textStyle,
),
),
);
widgets.add(widget);
}
return widgets;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论