diff --git a/lib/http/download.dart b/lib/http/download.dart index 66244d8f5..609ef58c7 100644 --- a/lib/http/download.dart +++ b/lib/http/download.dart @@ -2,12 +2,12 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/video/audio_quality.dart'; -import 'package:PiliPlus/models/common/video/video_decode_type.dart'; import 'package:PiliPlus/models/common/video/video_quality.dart'; import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/video/play/url.dart'; import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; import 'package:PiliPlus/models_new/download/bili_download_media_file_info.dart'; +import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; @@ -40,9 +40,9 @@ abstract final class DownloadHttp { }, ); if (res case Success(:final response)) { - final Dash? dash = response.dash; + final dash = response.dash; if (dash != null) { - final List videoList = dash.video!; + final videoList = dash.video!; final curHighestVideoQa = videoList.first.quality.code; final preferVideoQa = entry.preferedVideoQuality; int targetVideoQa = curHighestVideoQa; @@ -55,19 +55,18 @@ abstract final class DownloadHttp { ); } - /// 取出符合当前画质的videoList - final List videosList = videoList - .where((e) => e.quality.code == targetVideoQa) - .toList(); - /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式 - final List supportFormats = response.supportFormats!; + final supportFormats = response.supportFormats!; // 根据画质选编码格式 - final FormatItem targetSupportFormats = supportFormats.firstWhere( + final targetSupportFormats = supportFormats.firstWhere( (e) => e.quality == targetVideoQa, orElse: () => supportFormats.first, ); - final List supportDecodeFormats = targetSupportFormats.codecs!; + + final currentDecodeFormats = VideoDetailController.selectCodec( + targetSupportFormats.codecs!, + Pref.preferCodecs, + ); entry ..typeTag = targetVideoQa.toString() @@ -77,31 +76,10 @@ abstract final class DownloadHttp { targetSupportFormats.newDesc ?? VideoQuality.fromCode(targetVideoQa).desc; - String preferDecode = Pref.defaultDecode; // def avc - String preferSecondDecode = Pref.secondDecode; // def av1 - - // 默认从设置中取AV1 - VideoDecodeFormatType currentDecodeFormats = - VideoDecodeFormatType.fromString(preferDecode); - VideoDecodeFormatType secondDecodeFormats = - VideoDecodeFormatType.fromString(preferSecondDecode); - // 当前视频没有对应格式返回第一个 - int flag = 0; - for (final e in supportDecodeFormats) { - if (currentDecodeFormats.codes.any(e.startsWith)) { - flag = 1; - break; - } else if (secondDecodeFormats.codes.any(e.startsWith)) { - flag = 2; - } - } - if (flag == 2) { - currentDecodeFormats = secondDecodeFormats; - } else if (flag == 0) { - currentDecodeFormats = VideoDecodeFormatType.fromString( - supportDecodeFormats.first, - ); - } + /// 取出符合当前画质的videoList + final videosList = videoList + .where((e) => e.quality.code == targetVideoQa) + .toList(); /// 取出符合当前解码格式的videoItem final videoDash = videosList.firstWhere( diff --git a/lib/models/common/video/video_decode_type.dart b/lib/models/common/video/video_decode_type.dart index f17ec8c67..4020de3f2 100644 --- a/lib/models/common/video/video_decode_type.dart +++ b/lib/models/common/video/video_decode_type.dart @@ -12,9 +12,6 @@ enum VideoDecodeFormatType { const VideoDecodeFormatType(this.codes); - static VideoDecodeFormatType fromCode(String code) => - values.firstWhere((i) => i.codes.contains(code)); - static VideoDecodeFormatType fromString(String val) => values.firstWhere((i) => i.codes.any(val.startsWith)); } diff --git a/lib/pages/setting/models/video_settings.dart b/lib/pages/setting/models/video_settings.dart index 886e9d2c8..2136518ea 100644 --- a/lib/pages/setting/models/video_settings.dart +++ b/lib/pages/setting/models/video_settings.dart @@ -127,16 +127,11 @@ List get videoSettings => [ NormalModel( title: '首选解码格式', leading: const Icon(Icons.movie_creation_outlined), - getSubtitle: () => - '首选解码格式:${VideoDecodeFormatType.fromCode(Pref.defaultDecode).description},请根据设备支持情况与需求调整', - onTap: _showDecodeDialog, - ), - NormalModel( - title: '次选解码格式', - getSubtitle: () => - '非杜比视频次选:${VideoDecodeFormatType.fromCode(Pref.secondDecode).description},仍无则选择首个提供的解码格式', - leading: const Icon(Icons.swap_horizontal_circle_outlined), - onTap: _showSecondDecodeDialog, + getSubtitle: () { + final list = Pref.preferCodecs; + return '首选解码格式:${(list.isEmpty ? '第一个可用' : list.map((i) => i.name).join(","))},请根据设备支持情况与需求调整'; + }, + onTap: _showCodecsDialog, ), if (kDebugMode || Platform.isAndroid) NormalModel( @@ -349,42 +344,25 @@ Future _showLiveCellularQaDialog( } } -Future _showDecodeDialog( +Future _showCodecsDialog( BuildContext context, VoidCallback setState, ) async { - final res = await showDialog( + final res = await showDialog>( context: context, - builder: (context) => SelectDialog( - title: '默认解码格式', - value: Pref.defaultDecode, - values: VideoDecodeFormatType.values - .map((e) => (e.codes.first, e.description)) - .toList(), + builder: (context) => OrderedMultiSelectDialog( + title: '首选解码格式', + initValues: Pref.preferCodecs, + values: {for (final e in VideoDecodeFormatType.values) e: e.name}, ), ); if (res != null) { - await GStorage.setting.put(SettingBoxKey.defaultDecode, res); - setState(); - } -} - -Future _showSecondDecodeDialog( - BuildContext context, - VoidCallback setState, -) async { - final res = await showDialog( - context: context, - builder: (context) => SelectDialog( - title: '次选解码格式', - value: Pref.secondDecode, - values: VideoDecodeFormatType.values - .map((e) => (e.codes.first, e.description)) - .toList(), - ), - ); - if (res != null) { - await GStorage.setting.put(SettingBoxKey.secondDecode, res); + await (res.isEmpty + ? GStorage.setting.delete(SettingBoxKey.preferCodecs) + : GStorage.setting.put( + SettingBoxKey.preferCodecs, + res.map((i) => i.name).toList(), + )); setState(); } } diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index e3e3a5611..9537bbe61 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -143,8 +143,7 @@ class VideoDetailController extends GetxController Box setting = GStorage.setting; // 预设的解码格式 - late String cacheDecode = Pref.defaultDecode; // def avc - late String cacheSecondDecode = Pref.secondDecode; // def av1 + late List preferCodecs = Pref.preferCodecs; bool get showReply => isFileSource ? false @@ -669,31 +668,38 @@ class VideoDetailController extends GetxController } } - VideoItem findVideoByQa(int qa) { + VideoItem findVideoByQa(int qa, {bool setCodecs = false}) { /// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl final videoList = data.dash!.video!.where((i) => i.id == qa).toList(); - final currentDecodeFormats = this.currentDecodeFormats.codes; - final defaultDecodeFormats = VideoDecodeFormatType.fromString( - cacheDecode, - ).codes; - final secondDecodeFormats = VideoDecodeFormatType.fromString( - cacheSecondDecode, - ).codes; - - VideoItem? video; - for (final i in videoList) { - final codec = i.codecs!; - if (currentDecodeFormats.any(codec.startsWith)) { - video = i; - break; - } else if (defaultDecodeFormats.any(codec.startsWith)) { - video = i; - } else if (video == null && secondDecodeFormats.any(codec.startsWith)) { - video = i; + final currentCodes = currentDecodeFormats.codes; + VideoItem? bestVideo; + int bestIndex = preferCodecs.length; + for (final video in videoList) { + final c = video.codecs!; + if (currentCodes.any(c.startsWith)) { + return video; + } + for (int i = 0; i < bestIndex; i++) { + if (preferCodecs[i].codes.any(c.startsWith)) { + bestIndex = i; + bestVideo = video; + break; + } } } - return video ?? videoList.first; + + if (setCodecs) { + if (bestIndex < preferCodecs.length) { + currentDecodeFormats = preferCodecs[bestIndex]; + } else { + currentDecodeFormats = VideoDecodeFormatType.fromString( + videoList.first.codecs!, + ); + } + } + + return bestVideo ?? videoList.first; } /// 更新画质、音质 @@ -706,11 +712,7 @@ class VideoDetailController extends GetxController ..isBuffering.value = false ..buffered.value = Duration.zero; - final video = findVideoByQa(currentVideoQa.code); - if (firstVideo.codecs != video.codecs) { - currentDecodeFormats = VideoDecodeFormatType.fromString(video.codecs!); - } - firstVideo = video; + firstVideo = findVideoByQa(currentVideoQa.code, setCodecs: true); videoUrl = VideoUtils.getCdnUrl(firstVideo.playUrls); /// 根据currentAudioQa 重新设置audioUrl @@ -814,9 +816,34 @@ class VideoDetailController extends GetxController queryVideoUrl(fromReset: true); } + static VideoDecodeFormatType selectCodec( + Iterable codecs, + List preferCodecs, + ) { + if (preferCodecs.isNotEmpty) { + int bestIndex = preferCodecs.length; + for (final e in codecs) { + for (int i = 0; i < bestIndex; i++) { + if (preferCodecs[i].codes.any(e.startsWith)) { + bestIndex = i; + if (bestIndex == 0) { + return preferCodecs[0]; + } + break; + } + } + } + if (bestIndex < preferCodecs.length) { + return preferCodecs[bestIndex]; + } + } + return VideoDecodeFormatType.fromString(codecs.first); + } + Volume? volume; // 视频链接 + /// TODO: merge [DownloadHttp.getVideoUrl]. Future queryVideoUrl({ bool fromReset = false, bool autoFullScreenFlag = false, @@ -897,7 +924,7 @@ class VideoDetailController extends GetxController quality: videoQuality, ); _setVideoHeight(); - currentDecodeFormats = VideoDecodeFormatType.fromString('avc1'); + currentDecodeFormats = VideoDecodeFormatType.AVC; currentVideoQa.value = videoQuality; await _initPlayerIfNeeded(autoFullScreenFlag); isQuerying = false; @@ -929,42 +956,25 @@ class VideoDetailController extends GetxController } currentVideoQa.value = VideoQuality.fromCode(targetVideoQa); + /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式 + final supportFormats = data.supportFormats!; + + // 根据画质选编码格式 + currentDecodeFormats = selectCodec( + supportFormats + .firstWhere( + (e) => e.quality == targetVideoQa, + orElse: () => supportFormats.first, + ) + .codecs!, + preferCodecs, + ); + /// 取出符合当前画质的videoList - final List videosList = videoList + final videosList = videoList .where((e) => e.quality.code == targetVideoQa) .toList(); - /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式 - final List supportFormats = data.supportFormats!; - // 根据画质选编码格式 - final List supportDecodeFormats = supportFormats - .firstWhere( - (e) => e.quality == targetVideoQa, - orElse: () => supportFormats.first, - ) - .codecs!; - // 默认从设置中取AV1 - currentDecodeFormats = VideoDecodeFormatType.fromString(cacheDecode); - VideoDecodeFormatType secondDecodeFormats = - VideoDecodeFormatType.fromString(cacheSecondDecode); - // 当前视频没有对应格式返回第一个 - int flag = 0; - for (final e in supportDecodeFormats) { - if (currentDecodeFormats.codes.any(e.startsWith)) { - flag = 1; - break; - } else if (secondDecodeFormats.codes.any(e.startsWith)) { - flag = 2; - } - } - if (flag == 2) { - currentDecodeFormats = secondDecodeFormats; - } else if (flag == 0) { - currentDecodeFormats = VideoDecodeFormatType.fromString( - supportDecodeFormats.first, - ); - } - /// 取出符合当前解码格式的videoItem firstVideo = videosList.firstWhere( (e) => currentDecodeFormats.codes.any(e.codecs!.startsWith), diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index fcb69f414..c10612ef7 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -1069,28 +1069,26 @@ class HeaderControlState extends State // 选择解码格式 void showSetDecodeFormats() { - final VideoItem firstVideo = videoDetailCtr.firstVideo; + final firstCode = videoDetailCtr.firstVideo.quality.code; // 当前视频可用的解码格式 - final List videoFormat = videoInfo.supportFormats!; - final List? list = videoFormat - .firstWhere((FormatItem e) => e.quality == firstVideo.quality.code) - .codecs; + final videoFormat = videoInfo.supportFormats!; + + final list = videoFormat.firstWhere((e) => e.quality == firstCode).codecs; if (list == null) { SmartDialog.showToast('当前视频不支持选择解码格式'); return; } // 当前选中的解码格式 - final VideoDecodeFormatType currentDecodeFormats = - videoDetailCtr.currentDecodeFormats; + final curCodecs = videoDetailCtr.currentDecodeFormats.codes; showBottomSheet( (context, setState) { - final theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return Padding( padding: const EdgeInsets.all(12), child: Material( clipBehavior: Clip.hardEdge, - color: theme.colorScheme.surface, + color: colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), child: Column( children: [ @@ -1108,30 +1106,22 @@ class HeaderControlState extends State itemBuilder: (context, index) { final item = list[index]; final format = VideoDecodeFormatType.fromString(item); - final isCurr = currentDecodeFormats.codes.any( - item.startsWith, - ); + final isCurr = curCodecs.any(item.startsWith); return ListTile( dense: true, onTap: () { - if (isCurr) { - return; - } + if (isCurr) return; Get.back(); videoDetailCtr ..currentDecodeFormats = format ..updatePlayer(); + SmartDialog.showToast("解码已变为:${format.name}"); }, - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - ), + contentPadding: const .symmetric(horizontal: 20), title: Text(format.description), subtitle: Text(item, style: subTitleStyle), trailing: isCurr - ? Icon( - Icons.done, - color: theme.colorScheme.primary, - ) + ? Icon(Icons.done, color: colorScheme.primary) : null, ); }, diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index c9f76e6f3..752859b51 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -8,8 +8,7 @@ abstract final class SettingBoxKey { defaultAudioQaCellular = 'defaultAudioQaCellular', autoPlayEnable = 'autoPlayEnable', fullScreenMode = 'fullScreenMode', - defaultDecode = 'defaultDecode', - secondDecode = 'secondDecode', + preferCodecs = 'preferCodecs', defaultToastOp = 'defaultToastOp', defaultPicQa = 'defaultPicQa', enableHA = 'enableHA', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 195e96dc1..14cdff96a 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -245,15 +245,33 @@ abstract final class Pref { defaultValue: AudioQuality.k192.code, ); - static String get defaultDecode => _setting.get( - SettingBoxKey.defaultDecode, - defaultValue: VideoDecodeFormatType.AVC.codes.first, - ); + static List get preferCodecs { + // TODO: remove next 2 version + if (_setting.get('defaultDecode') case String codecStr) { + String? codecStr2 = _setting.get('secondDecode'); + _setting.deleteAll(const ['defaultDecode', 'secondDecode']); + final codecs = [ + VideoDecodeFormatType.values.firstWhere( + (i) => i.codes.contains(codecStr), + ), + if (codecStr2 != null && codecStr2 != codecStr) + VideoDecodeFormatType.values.firstWhere( + (i) => i.codes.contains(codecStr2), + ), + ]; + _setting.put( + SettingBoxKey.preferCodecs, + codecs.map((i) => i.name).toList(), + ); + return codecs; + } - static String get secondDecode => _setting.get( - SettingBoxKey.secondDecode, - defaultValue: VideoDecodeFormatType.AV1.codes.first, - ); + final codecs = _setting.get(SettingBoxKey.preferCodecs); + if (codecs is List && codecs.isNotEmpty) { + return codecs.map((i) => VideoDecodeFormatType.values.byName(i)).toList(); + } + return const []; + } static String get hardwareDecoding => _setting.get( SettingBoxKey.hardwareDecoding,