diff --git a/lib/models/common/sponsor_block/skip_type.dart b/lib/models/common/sponsor_block/skip_type.dart index 92ce89e81..63616264a 100644 --- a/lib/models/common/sponsor_block/skip_type.dart +++ b/lib/models/common/sponsor_block/skip_type.dart @@ -1,4 +1,6 @@ -enum SkipType { +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum SkipType implements EnumWithLabel { alwaysSkip('总是跳过'), skipOnce('跳过一次'), skipManually('手动跳过'), @@ -6,6 +8,7 @@ enum SkipType { disable('禁用') ; - final String title; - const SkipType(this.title); + @override + final String label; + const SkipType(this.label); } diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart index 5ad9f7268..9abd13381 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -134,54 +134,12 @@ List get extraSettings => [ ], ), ), - NormalModel( - leading: const Icon(MdiIcons.debugStepOver), + getPopupMenuModel( title: '番剧片头/片尾跳过类型', - getTrailing: () => Builder( - builder: (context) { - final pgcSkipType = Pref.pgcSkipType; - final colorScheme = ColorScheme.of(context); - final color = pgcSkipType == SkipType.disable - ? colorScheme.outline - : colorScheme.secondary; - return PopupMenuButton( - initialValue: pgcSkipType, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text.rich( - style: TextStyle(fontSize: 14, height: 1, color: color), - strutStyle: const StrutStyle( - leading: 0, - height: 1, - fontSize: 14, - ), - TextSpan( - children: [ - TextSpan(text: pgcSkipType.title), - WidgetSpan( - alignment: .middle, - child: Icon( - MdiIcons.unfoldMoreHorizontal, - size: 14, - color: color, - ), - ), - ], - ), - ), - ), - onSelected: (value) async { - await GStorage.setting.put(SettingBoxKey.pgcSkipType, value.index); - if (context.mounted) { - (context as Element).markNeedsBuild(); - } - }, - itemBuilder: (context) => SkipType.values - .map((e) => PopupMenuItem(value: e, child: Text(e.title))) - .toList(), - ); - }, - ), + leading: const Icon(MdiIcons.debugStepOver), + key: SettingBoxKey.pgcSkipType, + values: SkipType.values, + defaultIndex: SkipType.skipOnce.index, ), SwitchModel( title: '检查未读动态', diff --git a/lib/pages/setting/models/model.dart b/lib/pages/setting/models/model.dart index fd6e318cd..2b3663890 100644 --- a/lib/pages/setting/models/model.dart +++ b/lib/pages/setting/models/model.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/pages/setting/widgets/normal_item.dart'; import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/switch_item.dart'; @@ -7,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @immutable sealed class SettingsModel { @@ -258,3 +260,61 @@ SettingsModel getVideoFilterSelectModel({ }, ); } + +SettingsModel getPopupMenuModel({ + required String title, + Widget? leading, + String? subtitle, + required String key, + required List values, + int defaultIndex = 0, +}) { + // final globalKey = GlobalKey>(); + return NormalModel( + title: title, + subtitle: subtitle, + leading: leading, + // onTap: (context, setState) => globalKey.currentState?.showButtonMenu(), + getTrailing: () => Builder( + builder: (context) { + final color = ColorScheme.of(context).secondary; + final v = values[GStorage.setting.get(key, defaultValue: defaultIndex)]; + return PopupMenuButton( + // key: globalKey, + padding: .zero, + initialValue: v, + onSelected: (value) async { + await GStorage.setting.put(key, value.index); + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + itemBuilder: (context) => values + .map((i) => PopupMenuItem(value: i, child: Text(i.label))) + .toList(), + child: Padding( + padding: const .symmetric(vertical: 8), + child: Text.rich( + style: TextStyle(fontSize: 14, height: 1, color: color), + strutStyle: const StrutStyle(leading: 0, height: 1, fontSize: 14), + TextSpan( + children: [ + TextSpan(text: v.label), + WidgetSpan( + alignment: .middle, + child: Icon( + size: 14, + MdiIcons.unfoldMoreHorizontal, + color: color, + ), + ), + ], + style: TextStyle(color: color), + ), + ), + ), + ); + }, + ), + ); +} diff --git a/lib/pages/setting/models/video_settings.dart b/lib/pages/setting/models/video_settings.dart index 4fa2cbc22..9b05e2959 100644 --- a/lib/pages/setting/models/video_settings.dart +++ b/lib/pages/setting/models/video_settings.dart @@ -8,11 +8,13 @@ import 'package:PiliPlus/models/common/video/video_quality.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/widgets/ordered_multi_select_dialog.dart'; import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart'; +import 'package:PiliPlus/plugin/pl_player/models/audio_output_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/video_utils.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -317,13 +319,32 @@ List get videoSettings => [ } }, ), - if (Platform.isAndroid) - const SwitchModel( - title: '优先使用 OpenSL ES 输出音频', - leading: Icon(Icons.speaker_outlined), - subtitle: '关闭则优先使用AAudio输出音频(此项即mpv的--ao),若遇系统音效丢失、无声、音画不同步等问题请尝试打开。', - setKey: SettingBoxKey.useOpenSLES, - defaultVal: false, + if (kDebugMode || Platform.isAndroid) + NormalModel( + title: '音频输出设备', + leading: const Icon(Icons.speaker_outlined), + getSubtitle: () => '当前:${Pref.audioOutput}', + onTap: (context, setState) async { + final result = await showDialog>( + context: context, + builder: (context) { + return OrderedMultiSelectDialog( + title: '音频输出设备', + initValues: Pref.audioOutput.split(','), + values: { + for (final e in AudioOutput.values) e.name: e.label, + }, + ); + }, + ); + if (result != null && result.isNotEmpty) { + await GStorage.setting.put( + SettingBoxKey.audioOutput, + result.join(','), + ); + setState(); + } + }, ), const SwitchModel( title: '扩大缓冲区', diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index 09d94a56b..57e19b556 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -594,7 +594,7 @@ class _SponsorBlockPageState extends State { .map( (item) => PopupMenuItem( value: item, - child: Text(item.title), + child: Text(item.label), ), ) .toList(), @@ -617,7 +617,7 @@ class _SponsorBlockPageState extends State { ), TextSpan( children: [ - TextSpan(text: item.second.title), + TextSpan(text: item.second.label), WidgetSpan( alignment: .middle, child: Icon( diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index db32c55fc..ef804642c 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -659,7 +659,7 @@ class VideoDetailController extends GetxController mainAxisSize: MainAxisSize.min, children: [ Text( - item.skipType.title, + item.skipType.label, style: const TextStyle(fontSize: 13), ), if (item.segment.second != 0) diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index c75f05967..3560705a9 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -796,8 +796,7 @@ class PlPlayerController { await pp.setProperty("af", "scaletempo2=max-speed=8"); if (Platform.isAndroid) { await pp.setProperty("volume-max", "100"); - final ao = Pref.useOpenSLES ? "opensles,aaudio" : "aaudio,opensles"; - await pp.setProperty("ao", ao); + await pp.setProperty("ao", Pref.audioOutput); } // video-sync=display-resample await pp.setProperty("video-sync", Pref.videoSync); diff --git a/lib/plugin/pl_player/models/audio_output_type.dart b/lib/plugin/pl_player/models/audio_output_type.dart new file mode 100644 index 000000000..8b5a7b6ce --- /dev/null +++ b/lib/plugin/pl_player/models/audio_output_type.dart @@ -0,0 +1,14 @@ +import 'package:PiliPlus/models/common/enum_with_label.dart'; + +enum AudioOutput implements EnumWithLabel { + aaudio('AAudio'), + opensles('OpenSL ES'), + audiotrack('AudioTrack') + ; + + static final defaultValue = values.map((e) => e.name).join(','); + + @override + final String label; + const AudioOutput(this.label); +} diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index d2148e75d..ccf2fafc7 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -13,7 +13,7 @@ abstract final class SettingBoxKey { defaultToastOp = 'defaultToastOp', defaultPicQa = 'defaultPicQa', enableHA = 'enableHA', - useOpenSLES = 'useOpenSLES', + audioOutput = 'audioOutput', expandBuffer = 'expandBuffer', hardwareDecoding = 'hardwareDecoding', videoSync = 'videoSync', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 99b197c32..fa8d90868 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -24,6 +24,7 @@ import 'package:PiliPlus/models/common/video/video_decode_type.dart'; import 'package:PiliPlus/models/common/video/video_quality.dart'; import 'package:PiliPlus/models/user/danmaku_rule.dart'; import 'package:PiliPlus/models/user/info.dart'; +import 'package:PiliPlus/plugin/pl_player/models/audio_output_type.dart'; 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/hwdec_type.dart'; @@ -786,8 +787,10 @@ abstract final class Pref { static bool get expandBuffer => _setting.get(SettingBoxKey.expandBuffer, defaultValue: false); - static bool get useOpenSLES => - _setting.get(SettingBoxKey.useOpenSLES, defaultValue: false); + static String get audioOutput => _setting.get( + SettingBoxKey.audioOutput, + defaultValue: AudioOutput.defaultValue, + ); static bool get enableAi => _setting.get(SettingBoxKey.enableAi, defaultValue: false);