From 6c42a43bd5ddc5200766094ec0087f0f0facc62d Mon Sep 17 00:00:00 2001 From: orz12 Date: Wed, 13 Mar 2024 18:34:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=86=E5=88=86=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=E9=9F=B3=E9=A2=91=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E3=80=81=E7=BC=93=E5=86=B2=E5=8C=BA=E3=80=81=E7=AB=96=E5=B1=8F?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E6=98=BE=E7=A4=BA=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/audio_video_progress_bar.dart | 2 +- lib/pages/setting/play_setting.dart | 149 +------------ lib/pages/setting/video_setting.dart | 203 ++++++++++++++++++ lib/pages/setting/view.dart | 19 +- lib/pages/video/detail/view.dart | 55 +++-- lib/plugin/pl_player/controller.dart | 15 +- lib/router/app_pages.dart | 7 +- lib/utils/storage.dart | 3 + 8 files changed, 280 insertions(+), 173 deletions(-) create mode 100644 lib/pages/setting/video_setting.dart diff --git a/lib/common/widgets/audio_video_progress_bar.dart b/lib/common/widgets/audio_video_progress_bar.dart index 8dd0d9a7..5e9972de 100644 --- a/lib/common/widgets/audio_video_progress_bar.dart +++ b/lib/common/widgets/audio_video_progress_bar.dart @@ -1080,7 +1080,7 @@ class _RenderProgressBar extends RenderBox { if (total.inMilliseconds == 0) { return 0.0; } - return duration.inMilliseconds / total.inMilliseconds; + return (duration.inMilliseconds / total.inMilliseconds).clamp(0.0, 1.0); } String _getTimeString(Duration time) { diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 153687b4..85fcfc2d 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; -import 'package:PiliPalaX/models/video/play/quality.dart'; import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:PiliPalaX/services/service_locator.dart'; @@ -21,10 +20,6 @@ class PlaySetting extends StatefulWidget { class _PlaySettingState extends State { Box setting = GStrorage.setting; - late dynamic defaultVideoQa; - late dynamic defaultAudioQa; - late dynamic defaultDecode; - late dynamic secondDecode; late String defaultSubtitlePreference; late int defaultFullScreenMode; late int defaultBtmProgressBehavior; @@ -32,14 +27,6 @@ class _PlaySettingState extends State { @override void initState() { super.initState(); - defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa, - defaultValue: VideoQuality.values.last.code); - defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, - defaultValue: AudioQuality.values.last.code); - defaultDecode = setting.get(SettingBoxKey.defaultDecode, - defaultValue: VideoDecodeFormats.values.last.code); - secondDecode = setting.get(SettingBoxKey.secondDecode, - defaultValue: VideoDecodeFormats.values[1].code); defaultFullScreenMode = setting.get(SettingBoxKey.fullScreenMode, defaultValue: FullScreenMode.values.first.code); defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior, @@ -68,7 +55,7 @@ class _PlaySettingState extends State { centerTitle: false, titleSpacing: 0, title: Text( - '播放设置', + '播放器设置', style: Theme.of(context).textTheme.titleMedium, ), ), @@ -125,6 +112,12 @@ class _PlaySettingState extends State { } }, ), + const SetSwitchItem( + title: '竖屏扩大展示', + subTitle: '小屏竖屏视频由16:9扩大至5:4展示(!暂不支持临时收起)', + setKey: SettingBoxKey.enableVerticalExpand, + defaultVal: false, + ), const SetSwitchItem( title: '自动全屏', subTitle: '视频开始播放时进入全屏', @@ -162,134 +155,6 @@ class _PlaySettingState extends State { setKey: SettingBoxKey.enableOnlineTotal, defaultVal: false, ), - ListTile( - dense: false, - title: Text('默认画质', style: titleStyle), - subtitle: Text( - '当前画质:${VideoQualityCode.fromCode(defaultVideoQa)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认画质', - value: defaultVideoQa, - values: VideoQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultVideoQa = result; - setting.put(SettingBoxKey.defaultVideoQa, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('默认音质', style: titleStyle), - subtitle: Text( - '当前音质:${AudioQualityCode.fromCode(defaultAudioQa)!.description!}', - style: subTitleStyle, - ), - onTap: () async { - int? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认音质', - value: defaultAudioQa, - values: AudioQuality.values.reversed.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultAudioQa = result; - setting.put(SettingBoxKey.defaultAudioQa, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('首选解码格式', style: titleStyle), - subtitle: Text( - '首选解码格式:${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!},请根据设备支持情况与需求调整', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '默认解码格式', - value: defaultDecode, - values: VideoDecodeFormats.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - defaultDecode = result; - setting.put(SettingBoxKey.defaultDecode, result); - setState(() {}); - } - }, - ), - ListTile( - dense: false, - title: Text('次选解码格式', style: titleStyle), - subtitle: Text( - '非杜比视频次选:${VideoDecodeFormatsCode.fromCode(secondDecode)!.description!},仍无则选择首个提供的解码格式', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '次选解码格式', - value: secondDecode, - values: VideoDecodeFormats.values.map((e) { - return {'title': e.description, 'value': e.code}; - }).toList()); - }, - ); - if (result != null) { - secondDecode = result; - setting.put(SettingBoxKey.secondDecode, result); - setState(() {}); - } - }, - ), - const SetSwitchItem( - title: '开启硬解', - subTitle: '以较低功耗播放视频,若使用中异常卡死,请尝试关闭', - setKey: SettingBoxKey.enableHA, - defaultVal: true, - ), - const SetSwitchItem( - title: '亮度记忆', - subTitle: '返回时自动调整视频亮度', - setKey: SettingBoxKey.enableAutoBrightness, - defaultVal: false, - ), - const SetSwitchItem( - title: '免登录1080P', - subTitle: '免登录查看1080P视频', - setKey: SettingBoxKey.p1080, - defaultVal: true, - ), - const SetSwitchItem( - title: 'CDN优化', - subTitle: '使用优质CDN线路', - setKey: SettingBoxKey.enableCDN, - defaultVal: true, - ), ListTile( dense: false, title: Text('默认全屏方式', style: titleStyle), diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart new file mode 100644 index 00000000..f15a8fcb --- /dev/null +++ b/lib/pages/setting/video_setting.dart @@ -0,0 +1,203 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; +import 'package:PiliPalaX/models/video/play/quality.dart'; +import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; +import 'package:PiliPalaX/plugin/pl_player/index.dart'; +import 'package:PiliPalaX/utils/storage.dart'; + +import '../../models/video/play/subtitle.dart'; +import 'widgets/switch_item.dart'; + +class VideoSetting extends StatefulWidget { + const VideoSetting({super.key}); + + @override + State createState() => _VideoSettingState(); +} + +class _VideoSettingState extends State { + Box setting = GStrorage.setting; + late dynamic defaultVideoQa; + late dynamic defaultAudioQa; + late dynamic defaultDecode; + late dynamic secondDecode; + + @override + void initState() { + super.initState(); + defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa, + defaultValue: VideoQuality.values.last.code); + defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa, + defaultValue: AudioQuality.values.last.code); + defaultDecode = setting.get(SettingBoxKey.defaultDecode, + defaultValue: VideoDecodeFormats.values.last.code); + secondDecode = setting.get(SettingBoxKey.secondDecode, + defaultValue: VideoDecodeFormats.values[1].code); + } + + @override + Widget build(BuildContext context) { + TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; + TextStyle subTitleStyle = Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline); + return Scaffold( + appBar: AppBar( + centerTitle: false, + titleSpacing: 0, + title: Text( + '音视频设置', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + body: ListView( + children: [ + const SetSwitchItem( + title: '开启硬解', + subTitle: '以较低功耗播放视频,若遇异常卡死,请尝试关闭', + setKey: SettingBoxKey.enableHA, + defaultVal: true, + ), + const SetSwitchItem( + title: '亮度记忆', + subTitle: '返回时自动调整视频亮度', + setKey: SettingBoxKey.enableAutoBrightness, + defaultVal: false, + ), + const SetSwitchItem( + title: '免登录1080P', + subTitle: '免登录查看1080P视频', + setKey: SettingBoxKey.p1080, + defaultVal: true, + ), + const SetSwitchItem( + title: 'CDN优化', + subTitle: '使用优质CDN线路', + setKey: SettingBoxKey.enableCDN, + defaultVal: true, + ), + ListTile( + dense: false, + title: Text('默认画质', style: titleStyle), + subtitle: Text( + '当前画质:${VideoQualityCode.fromCode(defaultVideoQa)!.description!}', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '默认画质', + value: defaultVideoQa, + values: VideoQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + defaultVideoQa = result; + setting.put(SettingBoxKey.defaultVideoQa, result); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + title: Text('默认音质', style: titleStyle), + subtitle: Text( + '当前音质:${AudioQualityCode.fromCode(defaultAudioQa)!.description!}', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '默认音质', + value: defaultAudioQa, + values: AudioQuality.values.reversed.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + defaultAudioQa = result; + setting.put(SettingBoxKey.defaultAudioQa, result); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + title: Text('首选解码格式', style: titleStyle), + subtitle: Text( + '首选解码格式:${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!},请根据设备支持情况与需求调整', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '默认解码格式', + value: defaultDecode, + values: VideoDecodeFormats.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + defaultDecode = result; + setting.put(SettingBoxKey.defaultDecode, result); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + title: Text('次选解码格式', style: titleStyle), + subtitle: Text( + '非杜比视频次选:${VideoDecodeFormatsCode.fromCode(secondDecode)!.description!},仍无则选择首个提供的解码格式', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '次选解码格式', + value: secondDecode, + values: VideoDecodeFormats.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + secondDecode = result; + setting.put(SettingBoxKey.secondDecode, result); + setState(() {}); + } + }, + ), + if (Platform.isAndroid) + const SetSwitchItem( + title: '优先使用 OpenSL ES 输出音频', + subTitle: '关闭则优先使用AudioTrack输出音频(此项即mpv的--ao)', + setKey: SettingBoxKey.useOpenSLES, + defaultVal: true, + ), + const SetSwitchItem( + title: '扩大缓冲区', + subTitle: '默认缓冲区为视频5MB/直播32MB,开启后为视频32MB/直播64MB,但会延长首次加载时间', + setKey: SettingBoxKey.expandBuffer, + defaultVal: false, + ), + ], + ), + ); + } +} diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index d9838956..3ef01578 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -33,23 +33,30 @@ class SettingPage extends StatelessWidget { ListTile( onTap: () => Get.toNamed('/recommendSetting'), dense: false, - leading: const Icon(Icons.grid_view_outlined), + leading: const Icon(Icons.explore_outlined), title: const Text('推荐流设置'), subtitle: Text('推荐来源(web/app)、刷新保留内容、过滤器', style: subTitleStyle), ), ListTile( - onTap: () => Get.toNamed('/playSetting'), - leading: const Icon(Icons.play_arrow_outlined), + onTap: () => Get.toNamed('/videoSetting'), + leading: const Icon(Icons.video_settings_outlined), dense: false, - title: const Text('播放设置'), - subtitle: Text('弹幕、字幕、播放器行为与手势、画质、音质、解码、底部进度条等', style: subTitleStyle), + title: const Text('音视频设置'), + subtitle: Text('画质、音质、解码、缓冲、音频输出等', style: subTitleStyle), + ), + ListTile( + onTap: () => Get.toNamed('/playSetting'), + leading: const Icon(Icons.touch_app_outlined), + dense: false, + title: const Text('播放器设置'), + subtitle: Text('双击/长按、全屏、后台播放、弹幕、字幕、底部进度条等', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/styleSetting'), leading: const Icon(Icons.style_outlined), dense: false, title: const Text('外观设置'), - subtitle: Text('横屏适配、列宽、首页、主题、字号、图片、动态红点、帧率等', style: subTitleStyle), + subtitle: Text('横屏适配(平板)、列宽、首页、主题、字号、图片、动态红点、帧率等', style: subTitleStyle), ), ListTile( onTap: () => Get.toNamed('/extraSetting'), diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index bbc41b4a..2ef67d70 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -56,6 +56,7 @@ class _VideoDetailPageState extends State late bool autoExitFullcreen; late bool autoPlayEnable; late bool horizontalScreen; + late bool enableVerticalExpand; late bool autoPiP; final Floating floating = Floating(); // 生命周期监听 @@ -64,6 +65,8 @@ class _VideoDetailPageState extends State RxBool isFullScreen = false.obs; late StreamSubscription fullScreenStatusListener; late final MethodChannel onUserLeaveHintListener; + late AnimationController _animationController; + late Animation _animation; @override void initState() { @@ -91,6 +94,8 @@ class _VideoDetailPageState extends State autoPlayEnable = setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true); autoPiP = setting.get(SettingBoxKey.autoPiP, defaultValue: false); + enableVerticalExpand = + setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false); videoSourceInit(); appbarStreamListen(); // lifecycleListener(); @@ -103,6 +108,19 @@ class _VideoDetailPageState extends State } } }); + // _animationController = AnimationController( + // vsync: this, + // duration: const Duration(milliseconds: 300), + // ); + // _animation = Tween( + // begin: MediaQuery.of(context).orientation == Orientation.landscape + // ? context.height + // : ((enableVerticalExpand && + // plPlayerController?.direction.value == 'vertical') + // ? context.width * 5 / 4 + // : context.width * 9 / 16), + // end: 0, + // ).animate(_animationController); } // 获取视频资源,初始化播放器 @@ -233,6 +251,7 @@ class _VideoDetailPageState extends State videoPlayerServiceHandler.onVideoDetailDispose(); // _lifecycleListener.dispose(); showStatusBar(); + // _animationController.dispose(); super.dispose(); } @@ -341,12 +360,14 @@ class _VideoDetailPageState extends State children: [ Obx( () { - final double videoheight = Get.width * 9 / 16; - // final double videoheight = - // plPlayerController?.direction.value == 'vertical' - // ? Get.width - // : Get.width * 9 / 16; - final double videowidth = Get.width; + double videoheight = context.width * 9 / 16; + final double videowidth = context.width; + print(videoDetailController.tabCtr.index); + if (enableVerticalExpand && + plPlayerController?.direction.value == 'vertical' && + videoDetailController.tabCtr.index != 1) { + videoheight = context.width * 5 / 4; + } return SizedBox( height: MediaQuery.of(context).orientation == Orientation.landscape || @@ -357,7 +378,7 @@ class _VideoDetailPageState extends State ? 0 : MediaQuery.of(context).padding.top) : videoheight, - width: MediaQuery.of(context).size.width, + width: context.width, child: PopScope( canPop: isFullScreen.value != true && (horizontalScreen || @@ -514,7 +535,7 @@ class _VideoDetailPageState extends State Opacity( opacity: 0, child: SizedBox( - width: double.infinity, + width: context.width, height: 0, child: Obx( () => TabBar( @@ -598,9 +619,9 @@ class _VideoDetailPageState extends State // 1812x+2176y+1985.68z=680 // 1080x+2340y+1589.72z=540 final double videoheight = - sqrt(Get.height * Get.width) * 12.972 - - Get.height * 7.928 - - Get.width * 4.923; + sqrt(context.height * context.width) * 12.972 - + context.height * 7.928 - + context.width * 4.923; final double videowidth = videoheight * 16 / 9; return Row( children: [ @@ -608,10 +629,10 @@ class _VideoDetailPageState extends State children: [ SizedBox( width: isFullScreen.value == true - ? Get.width + ? context.width : videowidth, height: isFullScreen.value == true - ? Get.height + ? context.height : videoheight, child: PopScope( canPop: isFullScreen.value != true, @@ -758,11 +779,11 @@ class _VideoDetailPageState extends State ))), SizedBox( width: isFullScreen.value == true - ? Get.width + ? context.width : videowidth, height: isFullScreen.value == true ? 0 - : Get.height - + : context.height - videoheight - MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.bottom, @@ -781,11 +802,11 @@ class _VideoDetailPageState extends State SizedBox( width: isFullScreen.value == true ? 0 - : (Get.width - + : (context.width - MediaQuery.of(context).padding.left - MediaQuery.of(context).padding.right - videowidth), - height: Get.height - + height: context.height - MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.bottom, child: TabBarView( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 292ab2ea..0b0e2b4a 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -429,12 +429,15 @@ class PlPlayerController { if (danmakuController != null) { danmakuController!.clear(); } + int bufferSize = + setting.get(SettingBoxKey.expandBuffer, defaultValue: false) + ? (videoType.value == 'live' ? 64 * 1024 * 1024 : 32 * 1024 * 1024) + : (videoType.value == 'live' ? 32 * 1024 * 1024 : 5 * 1024 * 1024); Player player = _videoPlayerController ?? Player( configuration: PlayerConfiguration( - // 默认缓存 5M 大小 - bufferSize: - videoType.value == 'live' ? 32 * 1024 * 1024 : 5 * 1024 * 1024, + // 默认缓冲 5M 大小 + bufferSize: bufferSize, ), ); @@ -444,8 +447,10 @@ class PlPlayerController { // 音量不一致 if (Platform.isAndroid) { await pp.setProperty("volume-max", "100"); - // await pp.setProperty("ao", "audiotrack,opensles"); - await pp.setProperty("ao", "opensles,audiotrack"); + String ao = setting.get(SettingBoxKey.useOpenSLES, defaultValue: true) + ? "opensles,audiotrack" + : "audiotrack,opensles"; + await pp.setProperty("ao", ao); } // // vo=gpu-next & gpu-context=android & gpu-api=opengl // await pp.setProperty("vo", "gpu-next"); diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index db4c40ff..08608b39 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -46,6 +46,7 @@ import '../pages/setting/pages/home_tabbar_set.dart'; import '../pages/setting/pages/play_speed_set.dart'; import '../pages/setting/recommend_setting.dart'; import '../pages/setting/play_setting.dart'; +import '../pages/setting/video_setting.dart'; import '../pages/setting/privacy_setting.dart'; import '../pages/setting/style_setting.dart'; import '../pages/setting/hidden_settings.dart'; @@ -111,10 +112,12 @@ class Routes { // 二级回复 CustomGetPage( name: '/replyReply', page: () => const VideoReplyReplyPanel()), - // 推荐设置 + // 推荐流设置 CustomGetPage( name: '/recommendSetting', page: () => const RecommendSetting()), - // 播放设置 + // 音视频设置 + CustomGetPage(name: '/videoSetting', page: () => const VideoSetting()), + // 播放器设置 CustomGetPage(name: '/playSetting', page: () => const PlaySetting()), // 外观设置 CustomGetPage(name: '/styleSetting', page: () => const StyleSetting()), diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 26e83481..48537365 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -87,6 +87,9 @@ class SettingBoxKey { defaultToastOp = 'defaultToastOp', defaultPicQa = 'defaultPicQa', enableHA = 'enableHA', + useOpenSLES = 'useOpenSLES', + expandBuffer = 'expandBuffer', + enableVerticalExpand = 'enableVerticalExpand', enableOnlineTotal = 'enableOnlineTotal', enableAutoBrightness = 'enableAutoBrightness', enableAutoEnter = 'enableAutoEnter',