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 464024509..eab77c4ba 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 7f4ae5302..b2b1b0122 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -14,7 +14,11 @@ import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pb.dart'; import 'package:PiliPlus/models/common/image_preview_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'; @@ -610,28 +614,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/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index ad5224452..4478907c7 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -553,10 +553,7 @@ Future showVoteDialog( showDialog( context: context, builder: (context) => Dialog( - constraints: const BoxConstraints( - minWidth: 280, - maxWidth: 625, - ), + constraints: const BoxConstraints(minWidth: 280, maxWidth: 625), child: Padding( padding: const EdgeInsets.all(24), child: VotePanel( diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index d293b8e03..bf71f7eee 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -122,10 +122,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 44d9da8c3..01e820576 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' show Platform; 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 a61d0c55f..edaf98373 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -267,7 +267,7 @@ Future _showRefreshDragDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '刷新滑动距离', + title: const Text('刷新滑动距离'), min: 0.1, max: 0.5, divisions: 8, @@ -290,7 +290,7 @@ Future _showRefreshDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '刷新指示器高度', + title: const Text('刷新指示器高度'), min: 10.0, max: 100.0, divisions: 9, @@ -369,7 +369,7 @@ Future _showReplyCountDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '连接重试次数', + title: const Text('连接重试次数'), min: 0, max: 8, divisions: 8, @@ -391,7 +391,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 f8545e5fc..a815eb90d 100644 --- a/lib/pages/setting/models/play_settings.dart +++ b/lib/pages/setting/models/play_settings.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/widgets/slider_dialog.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_repeat.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'; @@ -39,6 +40,20 @@ List get playSettings => [ setKey: SettingBoxKey.showBatteryLevel, defaultVal: PlatformUtils.isMobile, ), + 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', @@ -95,7 +110,7 @@ Future _showAngleDegreesDialog( final res = await showDialog( context: context, builder: (context) => SliderDialog( - title: '倾斜角度阈值', + title: const Text('倾斜角度阈值'), min: 10.0, max: 90.0, divisions: 80, @@ -109,3 +124,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 dad327d0d..2c9a0c00c 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -98,7 +98,7 @@ List get styleSettings => [ NormalModel( onTap: (context, setState) => _showQualityDialog( context: context, - title: '图片质量', + title: const Text('图片质量'), initValue: Pref.picQuality, onChanged: (picQuality) async { GlobalData.imgQuality = picQuality; @@ -117,7 +117,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); @@ -164,7 +164,7 @@ List get styleSettings => [ void _showQualityDialog({ required BuildContext context, - required String title, + required Widget title, required int initValue, required ValueChanged onChanged, }) { @@ -341,11 +341,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, 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 36c47e15b..2971298e1 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 7b75fdc7d..c15aeca9f 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -21,6 +21,7 @@ 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'; 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'; @@ -63,6 +64,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; @@ -452,6 +454,24 @@ class HeaderControlState extends State title: const Text('重载视频', style: titleStyle), ), ], + 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, @@ -696,15 +716,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: () { @@ -728,14 +746,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) { @@ -756,9 +770,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}', ), @@ -766,60 +778,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, @@ -830,12 +824,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 3127dbf53..4d40f8751 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 08bf521ba..9717c1c92 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -12,6 +12,8 @@ import 'package:PiliPlus/models/common/video/video_type.dart'; 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/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'; @@ -601,11 +603,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 player = await Player.create( configuration: PlayerConfiguration( @@ -969,13 +974,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 78b0ead59..d6c9b8a47 100644 --- a/lib/plugin/pl_player/view/view.dart +++ b/lib/plugin/pl_player/view/view.dart @@ -953,7 +953,7 @@ class _PLVideoPlayerState extends State final double volume = clampDouble( plPlayerController.volume.value - delta.dy / level, 0.0, - PlPlayerController.maxVolume, + plPlayerController.maxVolume, ); plPlayerController.setVolume(volume); }, @@ -1132,7 +1132,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); }, @@ -1156,7 +1156,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 49811d908..cf262c41f 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -15,7 +15,9 @@ abstract final class SettingBoxKey { keyboardControl = 'keyboardControl', pgcSkipType = 'pgcSkipType', audioPlayMode = 'audioPlayMode', - showBatteryLevel = 'showBatteryLevel'; + showBatteryLevel = 'showBatteryLevel', + playerVolume = 'playerVolume', + maxVolume = 'maxVolume'; static const String horizontalScreen = 'horizontalScreen', CDNService = 'CDNService', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 8b4addae5..615860bb3 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -521,4 +521,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); }