From 90d79b236f8bf0a421b7d2819ede9e749e768cd8 Mon Sep 17 00:00:00 2001 From: dom Date: Mon, 8 Jun 2026 17:28:32 +0800 Subject: [PATCH] custom player/max volume Closes #2199 Closes #2358 Signed-off-by: dom --- lib/common/style.dart | 5 +- lib/pages/audio/controller.dart | 15 ++- lib/pages/audio/view.dart | 49 ++++++--- lib/pages/audio/volume_button.dart | 3 +- lib/pages/dynamics_mention/view.dart | 5 +- .../live_room/widgets/header_control.dart | 72 +++++++++--- lib/pages/setting/models/extra_settings.dart | 8 +- lib/pages/setting/models/play_settings.dart | 81 +++++++++++++- lib/pages/setting/models/style_settings.dart | 16 +-- .../setting/widgets/dual_slider_dialog.dart | 12 +- lib/pages/setting/widgets/select_dialog.dart | 2 +- lib/pages/setting/widgets/slider_dialog.dart | 25 ++--- lib/pages/video/widgets/header_control.dart | 103 ++++++++---------- lib/pages/video/widgets/player_focus.dart | 2 +- lib/plugin/pl_player/controller.dart | 13 ++- lib/plugin/pl_player/view/view.dart | 6 +- lib/utils/extension/string_ext.dart | 9 +- lib/utils/storage_key.dart | 4 +- lib/utils/storage_pref.dart | 6 + 19 files changed, 293 insertions(+), 143 deletions(-) diff --git a/lib/common/style.dart b/lib/common/style.dart index a50f36f79..2a7793b06 100644 --- a/lib/common/style.dart +++ b/lib/common/style.dart @@ -12,10 +12,7 @@ abstract final class Style { static const bottomSheetRadius = BorderRadius.vertical( top: Radius.circular(18), ); - static const dialogFixedConstraints = BoxConstraints( - minWidth: 420, - maxWidth: 420, - ); + static const dialogFixedConstraints = BoxConstraints.tightFor(width: 420); static const topBarHeight = 52.0; static const buttonStyle = ButtonStyle( visualDensity: VisualDensity(horizontal: -2, vertical: -1.25), diff --git a/lib/pages/audio/controller.dart b/lib/pages/audio/controller.dart index e0cbf46b2..bbe7126f1 100644 --- a/lib/pages/audio/controller.dart +++ b/lib/pages/audio/controller.dart @@ -19,6 +19,8 @@ import 'package:PiliPlus/pages/common/common_intro_controller.dart' show FavMixin; import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/pages/main_reply/view.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart' + show kMaxVolume; import 'package:PiliPlus/pages/sponsor_block/block_mixin.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/introduction/ugc/widgets/triple_mixin.dart'; @@ -328,11 +330,14 @@ class AudioController extends GetxController _hasInit = true; assert(player == null, _subscriptions = null); player = await Player.create( - configuration: PlatformUtils.isDesktop - ? PlayerConfiguration( - options: {'volume': (desktopVolume.value * 100).toString()}, - ) - : const PlayerConfiguration(), + configuration: PlayerConfiguration( + options: { + 'volume': PlatformUtils.isDesktop + ? (desktopVolume.value * 100).toString() + : Pref.playerVolume.toString(), + 'volume-max': kMaxVolume.toString(), + }, + ), ); if (isClosed) { player!.dispose(); diff --git a/lib/pages/audio/view.dart b/lib/pages/audio/view.dart index 00ecad8e8..80c4fbcf3 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -14,7 +14,11 @@ import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/pages/audio/controller.dart'; import 'package:PiliPlus/pages/audio/volume_button.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart' + show showPlayerVolumeDialog; import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart'; +import 'package:PiliPlus/pages/video/widgets/header_control.dart' + show HeaderControlState; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; import 'package:PiliPlus/services/shutdown_timer_service.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -615,28 +619,43 @@ class _AudioPageState extends State { ), ), ), - // ListTile( - // dense: true, - // title: const Text( - // '定时关闭', - // style: TextStyle(fontSize: 14), - // ), - // onTap: () { - // Get.back(); - // _controller.showTimerDialog(); - // }, - // ), ListTile( dense: true, - title: const Text( - '举报', - style: TextStyle(fontSize: 14), - ), + leading: const Icon(Icons.warning_amber_rounded, size: 20), + title: const Text('举报', style: TextStyle(fontSize: 14)), onTap: () { Get.back(); PageUtils.reportVideo(_controller.oid.toInt()); }, ), + if (_controller.player case final player?) ...[ + ListTile( + dense: true, + leading: const Icon(Icons.info_outline, size: 20), + title: const Text('播放信息', style: TextStyle(fontSize: 14)), + onTap: () { + Get.back(); + HeaderControlState.showPlayerInfo(context, player: player); + }, + ), + if (PlatformUtils.isMobile) + ListTile( + dense: true, + leading: const Icon(Icons.volume_up, size: 20), + title: Text( + '播放器音量: ${player.getProperty('volume').subLength(3)}%', + style: const TextStyle(fontSize: 14), + ), + onTap: () { + Get.back(); + showPlayerVolumeDialog( + context, + () {}, + onChanged: player.setVolume, + ); + }, + ), + ], ], ), ); diff --git a/lib/pages/audio/volume_button.dart b/lib/pages/audio/volume_button.dart index d422b15d1..6b30bd2cf 100644 --- a/lib/pages/audio/volume_button.dart +++ b/lib/pages/audio/volume_button.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:PiliPlus/common/widgets/flutter/vertical_slider.dart'; import 'package:PiliPlus/pages/audio/controller.dart'; +import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' show RenderProxyBox, BoxHitTestResult; import 'package:get/get.dart'; @@ -140,7 +141,7 @@ class _VolumeButtonState extends State { child: VerticalSlider( year2023: true, min: 0.0, - max: 2.0, + max: Pref.maxVolume, value: volume, showValueIndicator: .never, onChanged: widget.controller.setVolume, diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index be7d52c36..da9e8f4a0 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -123,10 +123,7 @@ class _DynMentionPanelState padding: EdgeInsets.only(left: 12, right: 4), child: Icon(Icons.search, size: 20), ), - prefixIconConstraints: const BoxConstraints( - minHeight: 0, - minWidth: 0, - ), + prefixIconConstraints: const .new(minHeight: 0, minWidth: 0), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 6, diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index 5e768a4f2..f031968d9 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -2,12 +2,15 @@ import 'dart:io'; import 'package:PiliPlus/common/widgets/marquee.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart' + show showPlayerVolumeDialog; import 'package:PiliPlus/pages/video/widgets/header_control.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart'; import 'package:PiliPlus/services/shutdown_timer_service.dart' show shutdownTimerService; import 'package:PiliPlus/utils/android/bindings.g.dart'; +import 'package:PiliPlus/utils/extension/string_ext.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -233,19 +236,62 @@ class _LiveHeaderControlState extends State color: Colors.white, ), ), - ComBtn( - height: 30, - tooltip: '播放信息', - onTap: () => HeaderControlState.showPlayerInfo( - context, - plPlayerController: plPlayerController, - ), - icon: const Icon( - size: 18, - Icons.info_outline, - color: Colors.white, - ), - ), + if (plPlayerController.videoPlayerController case final player?) + if (PlatformUtils.isMobile) + SizedBox.square( + dimension: 30, + child: PopupMenuButton( + iconSize: 18, + padding: .zero, + iconColor: Colors.white, + itemBuilder: (context) => [ + PopupMenuItem( + height: 35, + child: const Row( + spacing: 8, + children: [ + Icon(Icons.info_outline, size: 16), + Text('播放信息', style: TextStyle(fontSize: 14)), + ], + ), + onTap: () => HeaderControlState.showPlayerInfo( + context, + player: player, + ), + ), + PopupMenuItem( + height: 35, + child: Row( + spacing: 8, + children: [ + const Icon(Icons.volume_up, size: 16), + Text( + '播放器音量: ${player.getProperty('volume').subLength(3)}%', + style: const TextStyle(fontSize: 14), + ), + ], + ), + onTap: () => showPlayerVolumeDialog( + context, + () {}, + onChanged: player.setVolume, + ), + ), + ], + ), + ) + else + ComBtn( + height: 30, + tooltip: '播放信息', + onTap: () => + HeaderControlState.showPlayerInfo(context, player: player), + icon: const Icon( + size: 18, + Icons.info_outline, + color: Colors.white, + ), + ), ], ), ); diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart index a1806be97..541f5220e 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -940,7 +940,7 @@ Future _showRefreshDragDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '刷新滑动距离', + title: const Text('刷新滑动距离'), min: 0.1, max: 0.5, divisions: 8, @@ -963,7 +963,7 @@ Future _showRefreshDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '刷新指示器高度', + title: const Text('刷新指示器高度'), min: 10.0, max: 100.0, divisions: 9, @@ -1063,7 +1063,7 @@ Future _showReplyCountDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '连接重试次数', + title: const Text('连接重试次数'), min: 0, max: 8, divisions: 8, @@ -1085,7 +1085,7 @@ Future _showReplyDelayDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '连接重试间隔', + title: const Text('连接重试间隔'), min: 0, max: 1000, divisions: 10, diff --git a/lib/pages/setting/models/play_settings.dart b/lib/pages/setting/models/play_settings.dart index bffc4e1fd..b5943d815 100644 --- a/lib/pages/setting/models/play_settings.dart +++ b/lib/pages/setting/models/play_settings.dart @@ -12,6 +12,7 @@ import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart'; import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart'; import 'package:PiliPlus/services/service_locator.dart'; +import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -101,6 +102,20 @@ List get playSettings => [ setKey: SettingBoxKey.enableSlideFS, defaultVal: true, ), + if (PlatformUtils.isMobile) + NormalModel( + title: '播放器音量', + leading: const Icon(Icons.volume_up), + getSubtitle: () => '当前:「${Pref.playerVolume.toStringAsFixed(0)}%」', + onTap: showPlayerVolumeDialog, + ) + else + NormalModel( + title: '最高音量', + leading: const Icon(Icons.volume_up), + getSubtitle: () => '当前:「${(Pref.maxVolume * 100).toStringAsFixed(0)}%」', + onTap: _showMaxVolumeDialog, + ), getVideoFilterSelectModel( title: '双击快进/快退时长', suffix: 's', @@ -362,7 +377,7 @@ Future _showAngleDegreesDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '倾斜角度阈值', + title: const Text('倾斜角度阈值'), min: 10.0, max: 90.0, divisions: 90, @@ -376,3 +391,67 @@ Future _showAngleDegreesDialog( setState(); } } + +Future showPlayerVolumeDialog( + BuildContext context, + VoidCallback setState, { + ValueChanged? onChanged, +}) { + return showVolumeDialog( + context, + title: const Text('播放器音量'), + value: Pref.playerVolume, + onChanged: (value) => GStorage.setting + .put(SettingBoxKey.playerVolume, value) + .whenComplete(() { + setState(); + onChanged?.call(value); + }), + ); +} + +Future _showMaxVolumeDialog( + BuildContext context, + VoidCallback setState, +) { + return showVolumeDialog( + context, + title: const Text('最高音量'), + value: Pref.maxVolume * 100, + onChanged: (rawValue) { + final maxVolume = (rawValue / 100).toPrecision(2); + if (Pref.desktopVolume > maxVolume) { + GStorage.setting.put(SettingBoxKey.desktopVolume, maxVolume); + } + GStorage.setting + .put(SettingBoxKey.maxVolume, maxVolume) + .whenComplete(setState); + }, + ); +} + +const kMinVolume = 100.0; +const kMaxVolume = 300.0; + +Future showVolumeDialog( + BuildContext context, { + required Widget title, + required double value, + required ValueChanged onChanged, +}) async { + final res = await showDialog( + context: context, + builder: (context) => SliderDialog( + title: title, + min: kMinVolume, + max: kMaxVolume, + divisions: 40, + precise: 0, + value: value, + suffix: '%', + ), + ); + if (res != null) { + onChanged(res); + } +} diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index 51ada47f7..6a70f87df 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -220,7 +220,7 @@ List get styleSettings => [ NormalModel( onTap: (context, setState) => _showQualityDialog( context: context, - title: '图片质量', + title: const Text('图片质量'), initValue: Pref.picQuality, onChanged: (picQuality) async { GlobalData().imgQuality = picQuality; @@ -239,7 +239,7 @@ List get styleSettings => [ NormalModel( onTap: (context, setState) => _showQualityDialog( context: context, - title: '查看大图质量', + title: const Text('查看大图质量'), initValue: Pref.previewQ, onChanged: (picQuality) async { await GStorage.setting.put(SettingBoxKey.previewQuality, picQuality); @@ -381,7 +381,7 @@ List get styleSettings => [ void _showQualityDialog({ required BuildContext context, - required String title, + required Widget title, required int initValue, required ValueChanged onChanged, }) { @@ -637,7 +637,7 @@ Future _showFontWeightDialog(BuildContext context) async { final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: 'App字体字重', + title: const Text('App字体字重'), value: Pref.appFontWeight.toDouble() + 1, min: 1, max: FontWeight.values.length.toDouble(), @@ -676,11 +676,11 @@ Future _showCardWidthDialog( final res = await showDialog<(double, double)>( context: context, builder: (context) => DualSliderDialog( - title: '列表最大列宽度(默认240dp)', + title: const Text('列表最大列宽度(默认240dp)'), value1: Pref.recommendCardWidth, value2: Pref.smallCardWidth, - description1: '主页推荐流', - description2: '其他', + description1: const Text('主页推荐流'), + description2: const Text('其他'), min: 150.0, max: 500.0, divisions: 35, @@ -853,7 +853,7 @@ Future _showToastDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: 'Toast不透明度', + title: const Text('Toast不透明度'), value: CustomToast.toastOpacity, min: 0.0, max: 1.0, diff --git a/lib/pages/setting/widgets/dual_slider_dialog.dart b/lib/pages/setting/widgets/dual_slider_dialog.dart index 95beac98d..ba3b0b679 100644 --- a/lib/pages/setting/widgets/dual_slider_dialog.dart +++ b/lib/pages/setting/widgets/dual_slider_dialog.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; class DualSliderDialog extends StatefulWidget { final double value1; final double value2; - final String title; - final String description1; - final String description2; + final Widget title; + final Widget description1; + final Widget description2; final double min; final double max; final int? divisions; @@ -45,7 +45,7 @@ class _DualSliderDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(widget.title), + title: widget.title, contentPadding: const EdgeInsets.only( top: 20, left: 8, @@ -55,7 +55,7 @@ class _DualSliderDialogState extends State { content: Column( mainAxisSize: .min, children: [ - Text(widget.description1), + widget.description1, Builder( builder: (context) { return Slider( @@ -72,7 +72,7 @@ class _DualSliderDialogState extends State { ); }, ), - Text(widget.description2), + widget.description2, Builder( builder: (context) { return Slider( diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index 5097e3173..a01aa23a8 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -35,7 +35,7 @@ class SelectDialog extends StatelessWidget { clipBehavior: Clip.hardEdge, title: Text(title), constraints: subtitleBuilder != null - ? const BoxConstraints(maxWidth: 320, minWidth: 320) + ? const BoxConstraints.tightFor(width: 320) : null, contentPadding: const EdgeInsets.symmetric(vertical: 12), content: Material( diff --git a/lib/pages/setting/widgets/slider_dialog.dart b/lib/pages/setting/widgets/slider_dialog.dart index b1e18f749..7cf64475f 100644 --- a/lib/pages/setting/widgets/slider_dialog.dart +++ b/lib/pages/setting/widgets/slider_dialog.dart @@ -2,14 +2,6 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:flutter/material.dart'; class SliderDialog extends StatefulWidget { - final double value; - final String title; - final double min; - final double max; - final int? divisions; - final String suffix; - final int precise; - const SliderDialog({ super.key, required this.value, @@ -21,6 +13,14 @@ class SliderDialog extends StatefulWidget { this.precise = 1, }); + final double value; + final Widget title; + final double min; + final double max; + final int? divisions; + final String suffix; + final int precise; + @override State createState() => _SliderDialogState(); } @@ -37,13 +37,8 @@ class _SliderDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(widget.title), - contentPadding: const EdgeInsets.only( - top: 20, - left: 8, - right: 8, - bottom: 8, - ), + title: widget.title, + contentPadding: const .only(top: 20, left: 8, right: 8, bottom: 8), content: SizedBox( height: 40, child: Slider( diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 81e96e979..314a44324 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -23,6 +23,8 @@ import 'package:PiliPlus/models/video/play/url.dart'; import 'package:PiliPlus/models_new/video/video_play_info/subtitle.dart'; import 'package:PiliPlus/pages/common/common_intro_controller.dart'; import 'package:PiliPlus/pages/danmaku/danmaku_model.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart' + show showPlayerVolumeDialog; import 'package:PiliPlus/pages/setting/widgets/popup_item.dart'; import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPlus/pages/video/controller.dart'; @@ -66,6 +68,7 @@ import 'package:get/get.dart'; import 'package:hive_ce/hive.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; +import 'package:media_kit/media_kit.dart' show NativePlayer; mixin TimeBatteryMixin on State { PlPlayerController get plPlayerController; @@ -476,6 +479,24 @@ class HeaderControlState extends State descFontSize: 12, descPosType: .subtitle, ), + if (PlatformUtils.isMobile) + if (plPlayerController.videoPlayerController + case final player?) + Builder( + builder: (context) => ListTile( + dense: true, + leading: const Icon(Icons.volume_up, size: 20), + title: const Text('播放器音量'), + subtitle: Text( + '当前: ${Pref.playerVolume.toStringAsFixed(0)}%', + ), + onTap: () => showPlayerVolumeDialog( + context, + () => (context as Element).markNeedsBuild(), + onChanged: player.setVolume, + ), + ), + ), if (!isFileSource) ListTile( dense: true, @@ -729,15 +750,13 @@ class HeaderControlState extends State leading: const Icon(Icons.download_outlined, size: 20), title: const Text('保存字幕', style: titleStyle), ), - ListTile( - dense: true, - title: const Text('播放信息', style: titleStyle), - leading: const Icon(Icons.info_outline, size: 20), - onTap: () => showPlayerInfo( - context, - plPlayerController: plPlayerController, + if (plPlayerController.videoPlayerController case final player?) + ListTile( + dense: true, + title: const Text('播放信息', style: titleStyle), + leading: const Icon(Icons.info_outline, size: 20), + onTap: () => showPlayerInfo(context, player: player), ), - ), ListTile( dense: true, onTap: () { @@ -761,14 +780,10 @@ class HeaderControlState extends State static void showPlayerInfo( BuildContext context, { - required PlPlayerController plPlayerController, + required NativePlayer player, }) { - final player = plPlayerController.videoPlayerController; - if (player == null) { - SmartDialog.showToast('播放器未初始化'); - return; - } final hwdec = player.getProperty('hwdec-current'); + final volume = player.getProperty('volume').subLength(3); showDialog( context: context, builder: (context) { @@ -789,9 +804,7 @@ class HeaderControlState extends State ListTile( dense: true, title: const Text("Resolution"), - subtitle: Text( - '${state.width}x${state.height}', - ), + subtitle: Text('${state.width}x${state.height}'), onTap: () => Utils.copyText( 'Resolution\n${state.width}x${state.height}', ), @@ -799,60 +812,42 @@ class HeaderControlState extends State ListTile( dense: true, title: const Text("VideoParams"), - subtitle: Text( - state.videoParams.toString(), - ), - onTap: () => Utils.copyText( - 'VideoParams\n${state.videoParams}', - ), + subtitle: Text(state.videoParams.toString()), + onTap: () => + Utils.copyText('VideoParams\n${state.videoParams}'), ), ListTile( dense: true, title: const Text("AudioParams"), - subtitle: Text( - state.audioParams.toString(), - ), - onTap: () => Utils.copyText( - 'AudioParams\n${state.audioParams}', - ), + subtitle: Text(state.audioParams.toString()), + onTap: () => + Utils.copyText('AudioParams\n${state.audioParams}'), ), ListTile( dense: true, title: const Text("Media"), - subtitle: Text( - state.playlist.toString(), - ), - onTap: () => Utils.copyText( - 'Media\n${state.playlist}', - ), + subtitle: Text(state.playlist.toString()), + onTap: () => Utils.copyText('Media\n${state.playlist}'), ), ListTile( dense: true, title: const Text("AudioTrack"), - subtitle: Text( - state.track.audio.toString(), - ), - onTap: () => Utils.copyText( - 'AudioTrack\n${state.track.audio}', - ), + subtitle: Text(state.track.audio.toString()), + onTap: () => + Utils.copyText('AudioTrack\n${state.track.audio}'), ), ListTile( dense: true, title: const Text("VideoTrack"), - subtitle: Text( - state.track.video.toString(), - ), - onTap: () => Utils.copyText( - 'VideoTrack\n${state.track.audio}', - ), + subtitle: Text(state.track.video.toString()), + onTap: () => + Utils.copyText('VideoTrack\n${state.track.audio}'), ), ListTile( dense: true, title: const Text("pitch"), subtitle: Text(state.pitch.toString()), - onTap: () => Utils.copyText( - 'pitch\n${state.pitch}', - ), + onTap: () => Utils.copyText('pitch\n${state.pitch}'), ), ListTile( dense: true, @@ -863,12 +858,8 @@ class HeaderControlState extends State ListTile( dense: true, title: const Text("Volume"), - subtitle: Text( - state.volume.toString(), - ), - onTap: () => Utils.copyText( - 'Volume\n${state.volume}', - ), + subtitle: Text(volume.toString()), + onTap: () => Utils.copyText('Volume\n$volume'), ), ListTile( dense: true, diff --git a/lib/pages/video/widgets/player_focus.dart b/lib/pages/video/widgets/player_focus.dart index 34b0493d9..8c98da417 100644 --- a/lib/pages/video/widgets/player_focus.dart +++ b/lib/pages/video/widgets/player_focus.dart @@ -63,7 +63,7 @@ class PlayerFocus extends StatelessWidget { void _setVolume({required bool isIncrease}) { final volume = isIncrease ? math.min( - PlPlayerController.maxVolume, + plPlayerController.maxVolume, plPlayerController.volume.value + 0.1, ) : math.max(0.0, plPlayerController.volume.value - 0.1); diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 85f2f29ef..16471b8fc 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -17,6 +17,8 @@ import 'package:PiliPlus/models/user/danmaku_rule.dart'; import 'package:PiliPlus/models/video/play/url.dart'; import 'package:PiliPlus/models_new/video/video_shot/data.dart'; import 'package:PiliPlus/pages/danmaku/danmaku_model.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart' + show kMaxVolume; import 'package:PiliPlus/pages/sponsor_block/block_mixin.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/plugin/pl_player/models/data_status.dart'; @@ -772,11 +774,14 @@ class PlPlayerController with BlockConfigMixin { 'video-sync': Pref.videoSync, }; if (Platform.isAndroid) { - opt['volume-max'] = '100'; opt['ao'] = Pref.audioOutput; - } else if (PlatformUtils.isDesktop) { + } + if (PlatformUtils.isMobile) { + opt['volume'] = Pref.playerVolume.toString(); + } else { opt['volume'] = (volume.value * 100).toString(); } + opt['volume-max'] = kMaxVolume.toString(); final autosync = Pref.autosync; if (autosync != '0') { opt['autosync'] = autosync; @@ -1250,13 +1255,13 @@ class PlPlayerController with BlockConfigMixin { Timer? volumeTimer; bool volumeInterceptEventStream = false; - static final double maxVolume = PlatformUtils.isDesktop ? 2.0 : 1.0; + final double maxVolume = PlatformUtils.isDesktop ? Pref.maxVolume : 1.0; Future setVolume(double volume, {bool showIndicator = true}) async { if (this.volume.value != volume) { this.volume.value = volume; try { if (PlatformUtils.isDesktop) { - _videoPlayerController!.setVolume(volume * 100); + await _videoPlayerController!.setVolume(volume * 100); } else { FlutterVolumeController.updateShowSystemUI(false); await FlutterVolumeController.setVolume(volume); diff --git a/lib/plugin/pl_player/view/view.dart b/lib/plugin/pl_player/view/view.dart index 0aea1f1b3..3d80961f3 100644 --- a/lib/plugin/pl_player/view/view.dart +++ b/lib/plugin/pl_player/view/view.dart @@ -1132,7 +1132,7 @@ class _PLVideoPlayerState extends State final double volume = clampDouble( plPlayerController.volume.value - delta.dy / level, 0.0, - PlPlayerController.maxVolume, + plPlayerController.maxVolume, ); plPlayerController.setVolume(volume); }, @@ -1350,7 +1350,7 @@ class _PLVideoPlayerState extends State final double volume = clampDouble( plPlayerController.volume.value - event.localPanDelta.dy / level, 0.0, - PlPlayerController.maxVolume, + plPlayerController.maxVolume, ); plPlayerController.setVolume(volume); }, @@ -1374,7 +1374,7 @@ class _PLVideoPlayerState extends State final volume = clampDouble( plPlayerController.volume.value + offset, 0.0, - PlPlayerController.maxVolume, + plPlayerController.maxVolume, ); plPlayerController.setVolume(volume); } diff --git a/lib/utils/extension/string_ext.dart b/lib/utils/extension/string_ext.dart index dc039ffd8..452191735 100644 --- a/lib/utils/extension/string_ext.dart +++ b/lib/utils/extension/string_ext.dart @@ -1,7 +1,14 @@ final _regExp = RegExp("^(http:)?//", caseSensitive: false); -extension StringExt on String? { +extension NullableStringExt on String? { String get http2https => this?.replaceFirst(_regExp, "https://") ?? ''; bool get isNullOrEmpty => this == null || this!.isEmpty; } + +extension StringExt on String { + String subLength(int length) { + if (this.length < length) return this; + return substring(0, length); + } +} diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index df3858fe0..37182cbe4 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -28,7 +28,9 @@ abstract final class SettingBoxKey { pauseOnMinimize = 'pauseOnMinimize', pgcSkipType = 'pgcSkipType', audioPlayMode = 'audioPlayMode', - showBatteryLevel = 'showBatteryLevel'; + showBatteryLevel = 'showBatteryLevel', + playerVolume = 'playerVolume', + maxVolume = 'maxVolume'; static const String enableVerticalExpand = 'enableVerticalExpand', feedBackEnable = 'feedBackEnable', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index b212baf76..6acb9e7da 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -985,4 +985,10 @@ abstract final class Pref { static int get angleDegrees => _setting.get(SettingBoxKey.angleDegrees, defaultValue: 30); + + static double get playerVolume => // mobile + _setting.get(SettingBoxKey.playerVolume, defaultValue: 100.0); + + static double get maxVolume => // desktop + _setting.get(SettingBoxKey.maxVolume, defaultValue: 2.0); }