import 'dart:io'; import 'package:PiliPlus/models/common/video/audio_quality.dart'; import 'package:PiliPlus/models/common/video/cdn_type.dart'; import 'package:PiliPlus/models/common/video/live_quality.dart'; import 'package:PiliPlus/models/common/video/video_decode_type.dart'; 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' show kDebugMode; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; List get videoSettings => [ NormalModel( title: 'CDN 设置', leading: const Icon(MdiIcons.cloudPlusOutline), getSubtitle: () => '当前使用:${VideoUtils.cdnService.desc},部分 CDN 可能失效,如无法播放请尝试切换', onTap: _showCDNDialog, ), NormalModel( title: '直播 CDN 设置', leading: const Icon(MdiIcons.cloudPlusOutline), getSubtitle: () => '当前使用:${Pref.liveCdnUrl ?? "默认"}', onTap: _showLiveCDNDialog, ), const SwitchModel( title: 'CDN 测速', leading: Icon(Icons.speed), subtitle: '测速通过模拟加载视频实现,注意流量消耗,结果仅供参考', setKey: SettingBoxKey.cdnSpeedTest, defaultVal: true, ), NormalModel( title: '默认画质', leading: const Icon(Icons.video_settings_outlined), getSubtitle: () => '当前画质:${VideoQuality.fromCode(Pref.defaultVideoQa).desc}', onTap: _showVideoQaDialog, ), NormalModel( title: '蜂窝网络画质', leading: const Icon(Icons.video_settings_outlined), getSubtitle: () => '当前画质:${VideoQuality.fromCode(Pref.defaultVideoQaCellular).desc}', onTap: _showVideoCellularQaDialog, ), NormalModel( title: '默认音质', leading: const Icon(Icons.music_video_outlined), getSubtitle: () => '当前音质:${AudioQuality.fromCode(Pref.defaultAudioQa).desc}', onTap: _showAudioQaDialog, ), NormalModel( title: '蜂窝网络音质', leading: const Icon(Icons.music_video_outlined), getSubtitle: () => '当前音质:${AudioQuality.fromCode(Pref.defaultAudioQaCellular).desc}', onTap: _showAudioCellularQaDialog, ), NormalModel( title: '直播默认画质', leading: const Icon(Icons.video_settings_outlined), getSubtitle: () => '当前画质:${LiveQuality.fromCode(Pref.liveQuality)?.desc}', onTap: _showLiveQaDialog, ), NormalModel( title: '蜂窝网络直播默认画质', leading: const Icon(Icons.video_settings_outlined), getSubtitle: () => '当前画质:${LiveQuality.fromCode(Pref.liveQualityCellular)?.desc}', onTap: _showLiveCellularQaDialog, ), 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, ), if (kDebugMode || Platform.isAndroid) NormalModel( title: '音频输出设备', leading: const Icon(Icons.speaker_outlined), getSubtitle: () => '当前:${Pref.audioOutput}', onTap: _showAudioOutputDialog, ), NormalModel( title: '视频同步', leading: const Icon(Icons.view_timeline_outlined), getSubtitle: () => '当前:${Pref.videoSync}(此项即mpv的--video-sync)', onTap: _showVideoSyncDialog, ), NormalModel( title: '硬解模式', leading: const Icon(Icons.memory_outlined), getSubtitle: () => '当前:${Pref.hardwareDecoding}(此项即mpv的--hwdec)', onTap: _showHwDecDialog, ), ]; Future _showCDNDialog(BuildContext context, VoidCallback setState) async { final res = await showDialog( context: context, builder: (context) => const CdnSelectDialog(), ); if (res != null) { VideoUtils.cdnService = res; await GStorage.setting.put(SettingBoxKey.CDNService, res.name); setState(); } } Future _showLiveCDNDialog( BuildContext context, VoidCallback setState, ) async { String host = Pref.liveCdnUrl ?? ''; String? res = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('输入CDN host'), content: TextFormField( initialValue: host, autofocus: true, onChanged: (value) => host = value, ), actions: [ TextButton( onPressed: Get.back, child: Text( '取消', style: TextStyle(color: ColorScheme.of(context).outline), ), ), TextButton( onPressed: () => Get.back(result: host), child: const Text('确定'), ), ], ), ); if (res != null) { if (res.isEmpty) { res = null; await GStorage.setting.delete(SettingBoxKey.liveCdnUrl); } else { if (!res.startsWith('http')) { res = 'https://$res'; } await GStorage.setting.put(SettingBoxKey.liveCdnUrl, res); } VideoUtils.liveCdnUrl = res; setState(); } } Future _showVideoQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '默认画质', value: Pref.defaultVideoQa, values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.defaultVideoQa, res); setState(); } } Future _showVideoCellularQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '蜂窝网络画质', value: Pref.defaultVideoQaCellular, values: VideoQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put( SettingBoxKey.defaultVideoQaCellular, res, ); setState(); } } Future _showAudioQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '默认音质', value: Pref.defaultAudioQa, values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.defaultAudioQa, res); setState(); } } Future _showAudioCellularQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '蜂窝网络音质', value: Pref.defaultAudioQaCellular, values: AudioQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put( SettingBoxKey.defaultAudioQaCellular, res, ); setState(); } } Future _showLiveQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '直播默认画质', value: Pref.liveQuality, values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.liveQuality, res); setState(); } } Future _showLiveCellularQaDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '蜂窝网络直播默认画质', value: Pref.liveQualityCellular, values: LiveQuality.values.map((e) => (e.code, e.desc)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.liveQualityCellular, res); setState(); } } Future _showDecodeDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '默认解码格式', value: Pref.defaultDecode, values: VideoDecodeFormatType.values .map((e) => (e.codes.first, e.description)) .toList(), ), ); 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); setState(); } } Future _showAudioOutputDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog>( context: context, builder: (context) => OrderedMultiSelectDialog( title: '音频输出设备', initValues: Pref.audioOutput.split(','), values: { for (final e in AudioOutput.values) e.name: e.label, }, ), ); if (res != null && res.isNotEmpty) { await GStorage.setting.put( SettingBoxKey.audioOutput, res.join(','), ); setState(); } } Future _showVideoSyncDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog( context: context, builder: (context) => SelectDialog( title: '视频同步', value: Pref.videoSync, values: const [ 'audio', 'display-resample', 'display-resample-vdrop', 'display-resample-desync', 'display-tempo', 'display-vdrop', 'display-adrop', 'display-desync', 'desync', ].map((e) => (e, e)).toList(), ), ); if (res != null) { await GStorage.setting.put(SettingBoxKey.videoSync, res); setState(); } } Future _showHwDecDialog( BuildContext context, VoidCallback setState, ) async { final res = await showDialog>( context: context, builder: (context) => OrderedMultiSelectDialog( title: '硬解模式', initValues: Pref.hardwareDecoding.split(','), values: { for (final e in HwDecType.values) e.hwdec: '${e.hwdec}\n${e.desc}', }, ), ); if (res != null && res.isNotEmpty) { await GStorage.setting.put( SettingBoxKey.hardwareDecoding, res.join(','), ); setState(); } }