mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 11:08:03 +08:00
497 lines
14 KiB
Dart
497 lines
14 KiB
Dart
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';
|
||
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';
|
||
|
||
List<SettingsModel> get videoSettings => [
|
||
const SwitchModel(
|
||
title: '开启硬解',
|
||
subtitle: '以较低功耗播放视频,若异常卡死请关闭',
|
||
leading: Icon(Icons.flash_on_outlined),
|
||
setKey: SettingBoxKey.enableHA,
|
||
defaultVal: true,
|
||
),
|
||
const SwitchModel(
|
||
title: '免登录1080P',
|
||
subtitle: '免登录查看1080P视频',
|
||
leading: Icon(Icons.hd_outlined),
|
||
setKey: SettingBoxKey.p1080,
|
||
defaultVal: true,
|
||
),
|
||
NormalModel(
|
||
title: 'B站定向流量支持',
|
||
subtitle: '若套餐含B站定向流量,则会自动使用。可查阅运营商的流量记录确认。',
|
||
leading: const Icon(Icons.perm_data_setting_outlined),
|
||
getTrailing: (theme) => IgnorePointer(
|
||
child: Transform.scale(
|
||
scale: 0.8,
|
||
alignment: Alignment.centerRight,
|
||
child: Switch(
|
||
value: true,
|
||
onChanged: (_) {},
|
||
thumbIcon: WidgetStateProperty.all(
|
||
const Icon(Icons.lock_outline_rounded),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
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,
|
||
),
|
||
SwitchModel(
|
||
title: '音频不跟随 CDN 设置',
|
||
subtitle: '直接采用备用 URL,可解决部分视频无声',
|
||
leading: const Icon(MdiIcons.musicNotePlus),
|
||
setKey: SettingBoxKey.disableAudioCDN,
|
||
defaultVal: false,
|
||
onChanged: (value) => VideoUtils.disableAudioCDN = value,
|
||
),
|
||
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,
|
||
),
|
||
const SwitchModel(
|
||
title: '扩大缓冲区',
|
||
leading: Icon(Icons.storage_outlined),
|
||
subtitle: '默认缓冲区为视频4MB/直播16MB,开启后为32MB/64MB,加载时间变长',
|
||
setKey: SettingBoxKey.expandBuffer,
|
||
defaultVal: false,
|
||
),
|
||
NormalModel(
|
||
title: '自动同步',
|
||
leading: const Icon(Icons.sync_rounded),
|
||
getSubtitle: () => '当前:${Pref.autosync}(此项即mpv的--autosync)',
|
||
onTap: _showAutoSyncDialog,
|
||
),
|
||
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();
|
||
}
|
||
}
|
||
|
||
void _showAutoSyncDialog(BuildContext context, VoidCallback setState) {
|
||
String autosync = Pref.autosync.toString();
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('自动同步'),
|
||
content: TextFormField(
|
||
autofocus: true,
|
||
initialValue: autosync,
|
||
keyboardType: TextInputType.number,
|
||
onChanged: (value) => autosync = value,
|
||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: Get.back,
|
||
child: Text(
|
||
'取消',
|
||
style: TextStyle(color: ColorScheme.of(context).outline),
|
||
),
|
||
),
|
||
TextButton(
|
||
onPressed: () async {
|
||
try {
|
||
// validate
|
||
int.parse(autosync);
|
||
Get.back();
|
||
await GStorage.setting.put(SettingBoxKey.autosync, autosync);
|
||
setState();
|
||
} catch (e) {
|
||
SmartDialog.showToast(e.toString());
|
||
}
|
||
},
|
||
child: const Text('确定'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|