Files
PiliPlus/lib/pages/setting/models/video_settings.dart
dom 07843a5e77 drop
Signed-off-by: dom <githubaccount56556@proton.me>
2026-05-08 21:27:01 +08:00

402 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<SettingsModel> 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<void> _showCDNDialog(BuildContext context, VoidCallback setState) async {
final res = await showDialog<CDNService>(
context: context,
builder: (context) => const CdnSelectDialog(),
);
if (res != null) {
VideoUtils.cdnService = res;
await GStorage.setting.put(SettingBoxKey.CDNService, res.name);
setState();
}
}
Future<void> _showLiveCDNDialog(
BuildContext context,
VoidCallback setState,
) async {
String host = Pref.liveCdnUrl ?? '';
String? res = await showDialog<String>(
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<void> _showVideoQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showVideoCellularQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showAudioQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showAudioCellularQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showLiveQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showLiveCellularQaDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<int>(
context: context,
builder: (context) => SelectDialog<int>(
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<void> _showDecodeDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<String>(
context: context,
builder: (context) => SelectDialog<String>(
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<void> _showSecondDecodeDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<String>(
context: context,
builder: (context) => SelectDialog<String>(
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<void> _showAudioOutputDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<List<String>>(
context: context,
builder: (context) => OrderedMultiSelectDialog<String>(
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<void> _showVideoSyncDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<String>(
context: context,
builder: (context) => SelectDialog<String>(
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<void> _showHwDecDialog(
BuildContext context,
VoidCallback setState,
) async {
final res = await showDialog<List<String>>(
context: context,
builder: (context) => OrderedMultiSelectDialog<String>(
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();
}
}