mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt: select dialog & feat: select subtitle if muted (#564)
* opt: select dialog * opt: subtitle * feat: select subtitle if muted
This commit is contained in:
committed by
GitHub
parent
2ddfea5cf3
commit
82f9f48a8e
@@ -975,7 +975,7 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future subtitlesJson(
|
static Future<Map<String, dynamic>> subtitlesJson(
|
||||||
{String? aid, String? bvid, required int cid}) async {
|
{String? aid, String? bvid, required int cid}) async {
|
||||||
assert(aid != null || bvid != null);
|
assert(aid != null || bvid != null);
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
@@ -1016,22 +1016,25 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future vttSubtitles(subtile) async {
|
static Future vttSubtitles(Map<String, dynamic> subtile) async {
|
||||||
String subtitleTimecode(num seconds) {
|
String subtitleTimecode(num seconds) {
|
||||||
int h = (seconds / 3600).floor();
|
int h = seconds ~/ 3600;
|
||||||
int m = ((seconds % 3600) / 60).floor();
|
seconds %= 3600;
|
||||||
int s = (seconds % 60).floor();
|
int m = seconds ~/ 60;
|
||||||
int ms = ((seconds * 1000) % 1000).floor();
|
seconds %= 60;
|
||||||
if (h == 0) {
|
String sms = seconds.toStringAsFixed(3).padLeft(6, '0');
|
||||||
return "${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
|
return h == 0
|
||||||
}
|
? "${m.toString().padLeft(2, '0')}:$sms"
|
||||||
return "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
|
: "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms";
|
||||||
}
|
}
|
||||||
|
|
||||||
String processList(List list) {
|
String processList(List list) {
|
||||||
return list.fold('WEBVTT\n\n', (previous, item) {
|
final sb = StringBuffer('WEBVTT\n\n');
|
||||||
return '$previous${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}\n\n';
|
sb.writeAll(
|
||||||
});
|
list.map((item) =>
|
||||||
|
'${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}'),
|
||||||
|
'\n\n');
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await Request().get("https:${subtile['subtitle_url']}");
|
var res = await Request().get("https:${subtile['subtitle_url']}");
|
||||||
|
|||||||
@@ -39,5 +39,5 @@ extension VideoQualityDesc on LiveQuality {
|
|||||||
'高清',
|
'高清',
|
||||||
'流畅',
|
'流畅',
|
||||||
];
|
];
|
||||||
get description => _descList[index];
|
String get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ extension VideoQualityDesc on VideoQuality {
|
|||||||
'杜比视界',
|
'杜比视界',
|
||||||
'8K 超高清'
|
'8K 超高清'
|
||||||
];
|
];
|
||||||
get description => _descList[index];
|
String get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -89,7 +89,7 @@ extension AudioQualityDesc on AudioQuality {
|
|||||||
'杜比全景声',
|
'杜比全景声',
|
||||||
'Hi-Res无损',
|
'Hi-Res无损',
|
||||||
];
|
];
|
||||||
get description => _descList[index];
|
String get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VideoDecodeFormats {
|
enum VideoDecodeFormats {
|
||||||
@@ -101,12 +101,12 @@ enum VideoDecodeFormats {
|
|||||||
|
|
||||||
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
||||||
static final List<String> _descList = ['DVH1', 'AV1', 'HEVC', 'AVC'];
|
static final List<String> _descList = ['DVH1', 'AV1', 'HEVC', 'AVC'];
|
||||||
get description => _descList[index];
|
String get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
||||||
static final List<String> _codeList = ['dvh1', 'av01', 'hev1', 'avc1'];
|
static final List<String> _codeList = ['dvh1', 'av01', 'hev1', 'avc1'];
|
||||||
get code => _codeList[index];
|
String get code => _codeList[index];
|
||||||
|
|
||||||
static VideoDecodeFormats? fromCode(String code) {
|
static VideoDecodeFormats? fromCode(String code) {
|
||||||
final index = _codeList.indexOf(code);
|
final index = _codeList.indexOf(code);
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
enum SubtitlePreference { off, on, withoutAi }
|
enum SubtitlePreference { off, on, withoutAi, auto }
|
||||||
|
|
||||||
extension SubtitlePreferenceDesc on SubtitlePreference {
|
extension SubtitlePreferenceDesc on SubtitlePreference {
|
||||||
static final List<String> _descList = [
|
static final List<String> _descList = [
|
||||||
'默认不显示字幕',
|
'默认不显示字幕',
|
||||||
'选择第一个可用字幕',
|
'优先选择非自动生成(ai)字幕',
|
||||||
'跳过自动生成(ai)字幕,选择第一个可用字幕'
|
'跳过自动生成(ai)字幕,选择第一个可用字幕',
|
||||||
|
'静音时等同第二项,非静音时等同第三项'
|
||||||
];
|
];
|
||||||
get description => _descList[index];
|
String get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SubtitlePreferenceCode on SubtitlePreference {
|
extension SubtitlePreferenceCode on SubtitlePreference {
|
||||||
static final List<String> _codeList = ['off', 'on', 'withoutAi'];
|
static const List<String> _codeList = ['off', 'on', 'withoutAi', 'auto'];
|
||||||
String get code => _codeList[index];
|
String get code => _codeList[index];
|
||||||
|
|
||||||
static SubtitlePreference? fromCode(String code) {
|
static SubtitlePreference? fromCode(String code) {
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
|
|||||||
return SelectDialog<ThemeType>(
|
return SelectDialog<ThemeType>(
|
||||||
title: '主题模式',
|
title: '主题模式',
|
||||||
value: ctr.themeType.value,
|
value: ctr.themeType.value,
|
||||||
values: ThemeType.values.map((e) {
|
values: ThemeType.values
|
||||||
return {'title': e.description, 'value': e};
|
.map((e) => (e, e.description))
|
||||||
}).toList());
|
.toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
title: '动态页UP主显示位置',
|
title: '动态页UP主显示位置',
|
||||||
value: GStorage.upPanelPosition,
|
value: GStorage.upPanelPosition,
|
||||||
values: UpPanelPosition.values.map((e) {
|
values: UpPanelPosition.values.map((e) {
|
||||||
return {'title': e.labels, 'value': e};
|
return (e, e.labels);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -318,7 +318,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
title: '动态未读标记',
|
title: '动态未读标记',
|
||||||
value: GStorage.dynamicBadgeType,
|
value: GStorage.dynamicBadgeType,
|
||||||
values: DynamicBadgeMode.values.map((e) {
|
values: DynamicBadgeMode.values.map((e) {
|
||||||
return {'title': e.description, 'value': e};
|
return (e, e.description);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -350,7 +350,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
title: '消息未读标记',
|
title: '消息未读标记',
|
||||||
value: GStorage.msgBadgeMode,
|
value: GStorage.msgBadgeMode,
|
||||||
values: DynamicBadgeMode.values.map((e) {
|
values: DynamicBadgeMode.values.map((e) {
|
||||||
return {'title': e.description, 'value': e};
|
return (e, e.description);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -516,7 +516,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
value: GStorage.themeType,
|
value: GStorage.themeType,
|
||||||
values: ThemeType.values.map(
|
values: ThemeType.values.map(
|
||||||
(e) {
|
(e) {
|
||||||
return {'title': e.description, 'value': e};
|
return (e, e.description);
|
||||||
},
|
},
|
||||||
).toList());
|
).toList());
|
||||||
},
|
},
|
||||||
@@ -562,7 +562,7 @@ List<SettingsModel> get styleSettings => [
|
|||||||
title: '首页启动页',
|
title: '首页启动页',
|
||||||
value: GStorage.defaultHomePage,
|
value: GStorage.defaultHomePage,
|
||||||
values: defaultNavigationBars.map((e) {
|
values: defaultNavigationBars.map((e) {
|
||||||
return {'title': e['label'], 'value': e['id']};
|
return (e['id'] as int, e['label'] as String);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -766,9 +766,10 @@ List<SettingsModel> get playSettings => [
|
|||||||
title: '字幕选择偏好',
|
title: '字幕选择偏好',
|
||||||
value: GStorage.setting.get(SettingBoxKey.subtitlePreference,
|
value: GStorage.setting.get(SettingBoxKey.subtitlePreference,
|
||||||
defaultValue: SubtitlePreference.values.first.code),
|
defaultValue: SubtitlePreference.values.first.code),
|
||||||
values: SubtitlePreference.values.map((e) {
|
values: SubtitlePreference.values
|
||||||
return {'title': e.description, 'value': e.code};
|
.map((e) => (e.code, e.description))
|
||||||
}).toList());
|
.toList(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -879,7 +880,7 @@ List<SettingsModel> get playSettings => [
|
|||||||
title: '默认全屏方向',
|
title: '默认全屏方向',
|
||||||
value: GStorage.defaultFullScreenMode,
|
value: GStorage.defaultFullScreenMode,
|
||||||
values: FullScreenMode.values.map((e) {
|
values: FullScreenMode.values.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -903,7 +904,7 @@ List<SettingsModel> get playSettings => [
|
|||||||
title: '底部进度条展示',
|
title: '底部进度条展示',
|
||||||
value: GStorage.defaultBtmProgressBehavior,
|
value: GStorage.defaultBtmProgressBehavior,
|
||||||
values: BtmProgressBehavior.values.map((e) {
|
values: BtmProgressBehavior.values.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -972,12 +973,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
String? result = await showDialog(
|
String? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SelectDialog<String>(
|
return CdnSelectDialog();
|
||||||
title: 'CDN 设置',
|
|
||||||
value: GStorage.defaultCDNService,
|
|
||||||
values: CDNService.values.map((e) {
|
|
||||||
return {'title': e.description, 'value': e.code};
|
|
||||||
}).toList());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -1007,7 +1003,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '默认画质',
|
title: '默认画质',
|
||||||
leading: const Icon(Icons.video_settings_outlined),
|
leading: const Icon(Icons.video_settings_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQa)!.description!}',
|
'当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQa)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1015,9 +1011,9 @@ List<SettingsModel> get videoSettings => [
|
|||||||
return SelectDialog<int>(
|
return SelectDialog<int>(
|
||||||
title: '默认画质',
|
title: '默认画质',
|
||||||
value: GStorage.defaultVideoQa,
|
value: GStorage.defaultVideoQa,
|
||||||
values: VideoQuality.values.reversed.map((e) {
|
values: VideoQuality.values.reversed
|
||||||
return {'title': e.description, 'value': e.code};
|
.map((e) => (e.code, e.description))
|
||||||
}).toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1032,7 +1028,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '蜂窝网络画质',
|
title: '蜂窝网络画质',
|
||||||
leading: const Icon(Icons.video_settings_outlined),
|
leading: const Icon(Icons.video_settings_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQaCellular)!.description!}',
|
'当前画质:${VideoQualityCode.fromCode(GStorage.defaultVideoQaCellular)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1041,7 +1037,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '蜂窝网络画质',
|
title: '蜂窝网络画质',
|
||||||
value: GStorage.defaultVideoQaCellular,
|
value: GStorage.defaultVideoQaCellular,
|
||||||
values: VideoQuality.values.reversed.map((e) {
|
values: VideoQuality.values.reversed.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1058,7 +1054,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '默认音质',
|
title: '默认音质',
|
||||||
leading: const Icon(Icons.music_video_outlined),
|
leading: const Icon(Icons.music_video_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQa)!.description!}',
|
'当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQa)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1066,9 +1062,9 @@ List<SettingsModel> get videoSettings => [
|
|||||||
return SelectDialog<int>(
|
return SelectDialog<int>(
|
||||||
title: '默认音质',
|
title: '默认音质',
|
||||||
value: GStorage.defaultAudioQa,
|
value: GStorage.defaultAudioQa,
|
||||||
values: AudioQuality.values.reversed.map((e) {
|
values: AudioQuality.values.reversed
|
||||||
return {'title': e.description, 'value': e.code};
|
.map((e) => (e.code, e.description))
|
||||||
}).toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1083,7 +1079,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '蜂窝网络音质',
|
title: '蜂窝网络音质',
|
||||||
leading: const Icon(Icons.music_video_outlined),
|
leading: const Icon(Icons.music_video_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQaCellular)!.description!}',
|
'当前音质:${AudioQualityCode.fromCode(GStorage.defaultAudioQaCellular)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1092,7 +1088,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '蜂窝网络音质',
|
title: '蜂窝网络音质',
|
||||||
value: GStorage.defaultAudioQaCellular,
|
value: GStorage.defaultAudioQaCellular,
|
||||||
values: AudioQuality.values.reversed.map((e) {
|
values: AudioQuality.values.reversed.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1109,7 +1105,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '直播默认画质',
|
title: '直播默认画质',
|
||||||
leading: const Icon(Icons.video_settings_outlined),
|
leading: const Icon(Icons.video_settings_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前画质:${LiveQualityCode.fromCode(GStorage.liveQuality)!.description!}',
|
'当前画质:${LiveQualityCode.fromCode(GStorage.liveQuality)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1117,9 +1113,9 @@ List<SettingsModel> get videoSettings => [
|
|||||||
return SelectDialog<int>(
|
return SelectDialog<int>(
|
||||||
title: '直播默认画质',
|
title: '直播默认画质',
|
||||||
value: GStorage.liveQuality,
|
value: GStorage.liveQuality,
|
||||||
values: LiveQuality.values.map((e) {
|
values: LiveQuality.values
|
||||||
return {'title': e.description, 'value': e.code};
|
.map((e) => (e.code, e.description))
|
||||||
}).toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1134,7 +1130,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '蜂窝网络直播默认画质',
|
title: '蜂窝网络直播默认画质',
|
||||||
leading: const Icon(Icons.video_settings_outlined),
|
leading: const Icon(Icons.video_settings_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'当前画质:${LiveQualityCode.fromCode(GStorage.liveQualityCellular)!.description!}',
|
'当前画质:${LiveQualityCode.fromCode(GStorage.liveQualityCellular)!.description}',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
int? result = await showDialog(
|
int? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1143,7 +1139,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '直播默认画质',
|
title: '直播默认画质',
|
||||||
value: GStorage.liveQualityCellular,
|
value: GStorage.liveQualityCellular,
|
||||||
values: LiveQuality.values.map((e) {
|
values: LiveQuality.values.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -1160,7 +1156,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '首选解码格式',
|
title: '首选解码格式',
|
||||||
leading: const Icon(Icons.movie_creation_outlined),
|
leading: const Icon(Icons.movie_creation_outlined),
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'首选解码格式:${VideoDecodeFormatsCode.fromCode(GStorage.defaultDecode)!.description!},请根据设备支持情况与需求调整',
|
'首选解码格式:${VideoDecodeFormatsCode.fromCode(GStorage.defaultDecode)!.description},请根据设备支持情况与需求调整',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
String? result = await showDialog(
|
String? result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
@@ -1168,9 +1164,9 @@ List<SettingsModel> get videoSettings => [
|
|||||||
return SelectDialog<String>(
|
return SelectDialog<String>(
|
||||||
title: '默认解码格式',
|
title: '默认解码格式',
|
||||||
value: GStorage.defaultDecode,
|
value: GStorage.defaultDecode,
|
||||||
values: VideoDecodeFormats.values.map((e) {
|
values: VideoDecodeFormats.values
|
||||||
return {'title': e.description, 'value': e.code};
|
.map((e) => (e.code, e.description))
|
||||||
}).toList());
|
.toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -1183,7 +1179,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
settingsType: SettingsType.normal,
|
settingsType: SettingsType.normal,
|
||||||
title: '次选解码格式',
|
title: '次选解码格式',
|
||||||
getSubtitle: () =>
|
getSubtitle: () =>
|
||||||
'非杜比视频次选:${VideoDecodeFormatsCode.fromCode(GStorage.secondDecode)!.description!},仍无则选择首个提供的解码格式',
|
'非杜比视频次选:${VideoDecodeFormatsCode.fromCode(GStorage.secondDecode)!.description},仍无则选择首个提供的解码格式',
|
||||||
leading: const Icon(Icons.swap_horizontal_circle_outlined),
|
leading: const Icon(Icons.swap_horizontal_circle_outlined),
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
String? result = await showDialog(
|
String? result = await showDialog(
|
||||||
@@ -1193,7 +1189,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
title: '次选解码格式',
|
title: '次选解码格式',
|
||||||
value: GStorage.secondDecode,
|
value: GStorage.secondDecode,
|
||||||
values: VideoDecodeFormats.values.map((e) {
|
values: VideoDecodeFormats.values.map((e) {
|
||||||
return {'title': e.description, 'value': e.code};
|
return (e.code, e.description);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1245,7 +1241,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
'display-desync',
|
'display-desync',
|
||||||
'desync'
|
'desync'
|
||||||
].map((e) {
|
].map((e) {
|
||||||
return {'title': e, 'value': e};
|
return (e, e);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1269,7 +1265,7 @@ List<SettingsModel> get videoSettings => [
|
|||||||
value: GStorage.hardwareDecoding,
|
value: GStorage.hardwareDecoding,
|
||||||
values:
|
values:
|
||||||
['auto', 'auto-copy', 'auto-safe', 'no', 'yes'].map((e) {
|
['auto', 'auto-copy', 'auto-safe', 'no', 'yes'].map((e) {
|
||||||
return {'title': e, 'value': e};
|
return (e, e);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1872,18 +1868,18 @@ List<SettingsModel> get extraSettings => [
|
|||||||
return SelectDialog<String>(
|
return SelectDialog<String>(
|
||||||
title: '音量均衡',
|
title: '音量均衡',
|
||||||
value: audioNormalization,
|
value: audioNormalization,
|
||||||
values: values.map((e) {
|
values: values
|
||||||
return {
|
.map((e) => (
|
||||||
'title': switch (e) {
|
switch (e) {
|
||||||
'0' => AudioNormalization.disable.title,
|
'0' => AudioNormalization.disable.title,
|
||||||
'1' => AudioNormalization.dynaudnorm.title,
|
'1' => AudioNormalization.dynaudnorm.title,
|
||||||
'2' => AudioNormalization.loudnorm.title,
|
'2' => AudioNormalization.loudnorm.title,
|
||||||
'3' => AudioNormalization.custom.title,
|
'3' => AudioNormalization.custom.title,
|
||||||
_ => e,
|
_ => e,
|
||||||
},
|
},
|
||||||
'value': e,
|
e
|
||||||
};
|
))
|
||||||
}).toList());
|
.toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -1943,7 +1939,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
value:
|
value:
|
||||||
SuperResolutionType.values[GStorage.superResolutionType],
|
SuperResolutionType.values[GStorage.superResolutionType],
|
||||||
values: SuperResolutionType.values.map((e) {
|
values: SuperResolutionType.values.map((e) {
|
||||||
return {'title': e.title, 'value': e};
|
return (e, e.title);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -2268,7 +2264,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
title: '评论展示',
|
title: '评论展示',
|
||||||
value: GStorage.defaultReplySort,
|
value: GStorage.defaultReplySort,
|
||||||
values: ReplySortType.values.map((e) {
|
values: ReplySortType.values.map((e) {
|
||||||
return {'title': e.title, 'value': e.index};
|
return (e.index, e.title);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -2294,7 +2290,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
title: '动态展示',
|
title: '动态展示',
|
||||||
value: GStorage.defaultDynamicType,
|
value: GStorage.defaultDynamicType,
|
||||||
values: DynamicsType.values.sublist(0, 4).map((e) {
|
values: DynamicsType.values.sublist(0, 4).map((e) {
|
||||||
return {'title': e.labels, 'value': e.index};
|
return (e.index, e.labels);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -2319,7 +2315,7 @@ List<SettingsModel> get extraSettings => [
|
|||||||
title: '用户页默认展示TAB',
|
title: '用户页默认展示TAB',
|
||||||
value: GStorage.memberTab,
|
value: GStorage.memberTab,
|
||||||
values: MemberTabType.values.map((e) {
|
values: MemberTabType.values.map((e) {
|
||||||
return {'title': e.title, 'value': e};
|
return (e, e.title);
|
||||||
}).toList());
|
}).toList());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -2511,12 +2507,9 @@ SettingsModel _getVideoFilterSelectModel({
|
|||||||
values: (values
|
values: (values
|
||||||
..addIf(!values.contains(value), value)
|
..addIf(!values.contains(value), value)
|
||||||
..sort())
|
..sort())
|
||||||
.map((e) => {
|
.map((e) => (e, suffix == null ? e.toString() : '$e $suffix'))
|
||||||
'title': suffix == null ? e.toString() : '$e $suffix',
|
|
||||||
'value': e
|
|
||||||
})
|
|
||||||
.toList()
|
.toList()
|
||||||
..add({'title': '自定义', 'value': -1}));
|
..add((-1, '自定义')));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
|||||||
@@ -1,137 +1,200 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:PiliPlus/http/constants.dart';
|
import 'package:PiliPlus/http/constants.dart';
|
||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
import 'package:PiliPlus/models/video/play/CDN.dart';
|
import 'package:PiliPlus/models/video/play/CDN.dart';
|
||||||
import 'package:PiliPlus/models/video/play/url.dart';
|
import 'package:PiliPlus/models/video/play/url.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/video_utils.dart';
|
import 'package:PiliPlus/utils/video_utils.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get_utils/get_utils.dart';
|
|
||||||
|
|
||||||
class SelectDialog<T> extends StatefulWidget {
|
class SelectDialog<T> extends StatelessWidget {
|
||||||
final T value;
|
final T? value;
|
||||||
final String title;
|
final String title;
|
||||||
final List<dynamic> values;
|
final List<(T, String)> values;
|
||||||
|
final Widget Function(BuildContext, int)? subtitleBuilder;
|
||||||
|
|
||||||
const SelectDialog({
|
const SelectDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.value,
|
this.value,
|
||||||
required this.values,
|
required this.values,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.subtitleBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
State<SelectDialog<T>> createState() => _SelectDialogState<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
|
||||||
late T _tempValue;
|
|
||||||
late List _cdnResList;
|
|
||||||
late final cdnSpeedTest = GStorage.cdnSpeedTest;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_tempValue = widget.value;
|
|
||||||
if (widget.title == 'CDN 设置' && cdnSpeedTest) {
|
|
||||||
_cdnResList = List.generate(widget.values.length, (_) => null);
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
try {
|
|
||||||
dynamic result = await VideoHttp.videoUrl(
|
|
||||||
cid: 196018899,
|
|
||||||
bvid: 'BV1fK4y1t7hj',
|
|
||||||
);
|
|
||||||
if (result['status']) {
|
|
||||||
VideoItem videoItem = result['data'].dash.video.first;
|
|
||||||
|
|
||||||
for (CDNService item in CDNService.values) {
|
|
||||||
if (mounted.not) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
String videoUrl = VideoUtils.getCdnUrl(videoItem, item.code);
|
|
||||||
Dio dio = Dio()..options.headers['referer'] = HttpString.baseUrl;
|
|
||||||
int maxSize = 8 * 1024 * 1024;
|
|
||||||
int downloaded = 0;
|
|
||||||
int start = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
try {
|
|
||||||
await dio.get(
|
|
||||||
videoUrl,
|
|
||||||
onReceiveProgress: (int count, int total) {
|
|
||||||
downloaded += count;
|
|
||||||
int now = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
if (now - start > 15 * 1000) {
|
|
||||||
dio.close(force: true);
|
|
||||||
}
|
|
||||||
if (downloaded >= maxSize) {
|
|
||||||
dio.close(force: true);
|
|
||||||
_cdnResList[item.index] =
|
|
||||||
(maxSize / (now - start) / 1000).toPrecision(2);
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (_cdnResList[item.index] == null) {
|
|
||||||
_cdnResList[item.index] = '测速失败';
|
|
||||||
debugPrint('$e');
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('failed to check: $e');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
title: Text(widget.title),
|
title: Text(title),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: SingleChildScrollView(
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
widget.values.length,
|
values.length,
|
||||||
(index) => RadioListTile(
|
(index) => RadioListTile<T>(
|
||||||
dense: true,
|
dense: true,
|
||||||
value: widget.values[index]['value'],
|
value: values[index].$1,
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.values[index]['title'],
|
values[index].$2,
|
||||||
style: Theme.of(context).textTheme.titleMedium!,
|
style: Theme.of(context).textTheme.titleMedium!,
|
||||||
),
|
),
|
||||||
subtitle: widget.title == 'CDN 设置' && cdnSpeedTest
|
subtitle: subtitleBuilder?.call(context, index),
|
||||||
? Text(
|
groupValue: value,
|
||||||
_cdnResList[index] is double
|
onChanged: Navigator.of(context).pop,
|
||||||
? '${_cdnResList[index]} MB/s'
|
),
|
||||||
: _cdnResList[index] is String
|
),
|
||||||
? _cdnResList[index]
|
),
|
||||||
: '---',
|
),
|
||||||
style: TextStyle(fontSize: 13),
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CdnSelectDialog extends StatefulWidget {
|
||||||
|
final VideoItem? sample;
|
||||||
|
|
||||||
|
const CdnSelectDialog({
|
||||||
|
super.key,
|
||||||
|
this.sample,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CdnSelectDialog> createState() => _CdnSelectDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CdnSelectDialogState extends State<CdnSelectDialog> {
|
||||||
|
late final List<ValueNotifier<String?>> _cdnResList;
|
||||||
|
late final CancelToken _cancelToken;
|
||||||
|
bool _cdnSpeedTest = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_cdnSpeedTest = GStorage.cdnSpeedTest;
|
||||||
|
if (_cdnSpeedTest) {
|
||||||
|
_startSpeedTest();
|
||||||
|
_cdnResList = List.generate(
|
||||||
|
CDNService.values.length, (_) => ValueNotifier<String?>(null));
|
||||||
|
_cancelToken = CancelToken();
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (_cdnSpeedTest) {
|
||||||
|
_cancelToken.cancel();
|
||||||
|
for (final notifier in _cdnResList) {
|
||||||
|
notifier.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<VideoItem> _getSampleUrl() async {
|
||||||
|
final result =
|
||||||
|
await VideoHttp.videoUrl(cid: 196018899, bvid: 'BV1fK4y1t7hj');
|
||||||
|
if (!result['status']) throw Exception('无法获取视频流');
|
||||||
|
return result['data'].dash.video.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startSpeedTest() async {
|
||||||
|
try {
|
||||||
|
final videoItem = widget.sample ?? await _getSampleUrl();
|
||||||
|
await _testAllCdnServices(videoItem);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('CDN speed test failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testAllCdnServices(VideoItem videoItem) async {
|
||||||
|
for (final item in CDNService.values) {
|
||||||
|
if (!mounted) break;
|
||||||
|
await _testSingleCdn(item, videoItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testSingleCdn(CDNService item, VideoItem videoItem) async {
|
||||||
|
try {
|
||||||
|
final cdnUrl = VideoUtils.getCdnUrl(videoItem, item.code);
|
||||||
|
await _measureDownloadSpeed(cdnUrl, item.index);
|
||||||
|
} catch (e) {
|
||||||
|
_handleSpeedTestError(e, item.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _measureDownloadSpeed(String url, int index) async {
|
||||||
|
const maxSize = 8 * 1024 * 1024;
|
||||||
|
int downloaded = 0;
|
||||||
|
final dio = Dio()..options.headers['referer'] = HttpString.baseUrl;
|
||||||
|
final start = DateTime.now().microsecondsSinceEpoch;
|
||||||
|
|
||||||
|
await dio.get(
|
||||||
|
url,
|
||||||
|
cancelToken: _cancelToken,
|
||||||
|
onReceiveProgress: (count, total) {
|
||||||
|
if (!mounted) {
|
||||||
|
dio.close(force: true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final duration = DateTime.now().microsecondsSinceEpoch - start;
|
||||||
|
|
||||||
|
downloaded += count;
|
||||||
|
|
||||||
|
if (duration > 15000000) {
|
||||||
|
dio.close(force: true);
|
||||||
|
if (downloaded > 0) {
|
||||||
|
_updateSpeedResult(index, downloaded, duration);
|
||||||
|
downloaded = 0;
|
||||||
|
} else {
|
||||||
|
throw TimeoutException('测速超时');
|
||||||
|
}
|
||||||
|
} else if (downloaded >= maxSize) {
|
||||||
|
dio.close(force: true);
|
||||||
|
_updateSpeedResult(index, downloaded, duration);
|
||||||
|
downloaded = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSpeedResult(int index, int downloaded, int duration) {
|
||||||
|
final speed = (downloaded / duration).toStringAsPrecision(3);
|
||||||
|
_cdnResList[index].value = '${speed}MB/s';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSpeedTestError(dynamic error, int index) {
|
||||||
|
if (_cdnResList[index].value != null) return;
|
||||||
|
|
||||||
|
debugPrint('CDN speed test error: $error');
|
||||||
|
if (!mounted) return;
|
||||||
|
var message = error.toString();
|
||||||
|
if (message.length > 30) {
|
||||||
|
message = '${message.substring(0, 30)}...';
|
||||||
|
} else if (message.isEmpty) {
|
||||||
|
message = '测速失败';
|
||||||
|
}
|
||||||
|
_cdnResList[index].value = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SelectDialog<String>(
|
||||||
|
title: 'CDN 设置',
|
||||||
|
values: CDNService.values.map((i) => (i.code, i.description)).toList(),
|
||||||
|
value: GStorage.defaultCDNService,
|
||||||
|
subtitleBuilder: _cdnSpeedTest
|
||||||
|
? (context, index) => ValueListenableBuilder(
|
||||||
|
valueListenable: _cdnResList[index],
|
||||||
|
builder: (context, value, _) {
|
||||||
|
return Text(
|
||||||
|
_cdnResList[index].value ?? '---',
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
groupValue: _tempValue,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_tempValue = value as T;
|
|
||||||
});
|
|
||||||
Navigator.pop(context, _tempValue);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import 'package:PiliPlus/models/common/sponsor_block/segment_model.dart';
|
|||||||
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
|
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
|
||||||
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
|
import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart';
|
||||||
import 'package:PiliPlus/models/video/later.dart';
|
import 'package:PiliPlus/models/video/later.dart';
|
||||||
import 'package:PiliPlus/models/video/play/subtitle.dart';
|
|
||||||
import 'package:PiliPlus/models/video_detail_res.dart';
|
import 'package:PiliPlus/models/video_detail_res.dart';
|
||||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||||
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
||||||
@@ -29,6 +28,7 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
|||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:PiliPlus/http/constants.dart';
|
import 'package:PiliPlus/http/constants.dart';
|
||||||
import 'package:PiliPlus/http/video.dart';
|
import 'package:PiliPlus/http/video.dart';
|
||||||
@@ -1361,7 +1361,7 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RxList subtitles = [].obs;
|
RxList subtitles = RxList();
|
||||||
late final Map<int, String> _vttSubtitles = {};
|
late final Map<int, String> _vttSubtitles = {};
|
||||||
late final RxInt vttSubtitlesIndex = (-1).obs;
|
late final RxInt vttSubtitlesIndex = (-1).obs;
|
||||||
late bool showVP = true;
|
late bool showVP = true;
|
||||||
@@ -1412,7 +1412,7 @@ class VideoDetailController extends GetxController
|
|||||||
steinEdgeInfo = null;
|
steinEdgeInfo = null;
|
||||||
try {
|
try {
|
||||||
dynamic res = await Request().get(
|
dynamic res = await Request().get(
|
||||||
'https://api.bilibili.com/x/stein/edgeinfo_v2',
|
'/x/stein/edgeinfo_v2',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'graph_version': graphVersion,
|
'graph_version': graphVersion,
|
||||||
@@ -1432,7 +1432,7 @@ class VideoDetailController extends GetxController
|
|||||||
late bool continuePlayingPart = GStorage.continuePlayingPart;
|
late bool continuePlayingPart = GStorage.continuePlayingPart;
|
||||||
|
|
||||||
Future _querySubtitles() async {
|
Future _querySubtitles() async {
|
||||||
Map res = await VideoHttp.subtitlesJson(bvid: bvid, cid: cid.value);
|
var res = await VideoHttp.subtitlesJson(bvid: bvid, cid: cid.value);
|
||||||
// if (!res["status"]) {
|
// if (!res["status"]) {
|
||||||
// SmartDialog.showToast('查询字幕错误,${res["msg"]}');
|
// SmartDialog.showToast('查询字幕错误,${res["msg"]}');
|
||||||
// }
|
// }
|
||||||
@@ -1492,25 +1492,21 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res["subtitles"] is List && res["subtitles"].isNotEmpty) {
|
if (res["subtitles"] is List && res["subtitles"].isNotEmpty) {
|
||||||
vttSubtitlesIndex.value = 0;
|
int idx = 0;
|
||||||
subtitles.value = res["subtitles"];
|
subtitles.value = res["subtitles"];
|
||||||
|
|
||||||
String preference = setting.get(
|
String preference = GStorage.defaultSubtitlePreference;
|
||||||
SettingBoxKey.subtitlePreference,
|
if (preference != 'off') {
|
||||||
defaultValue: SubtitlePreference.values.first.code,
|
idx = subtitles.indexWhere((i) => !i['lan']!.startsWith('ai')) + 1;
|
||||||
);
|
if (idx == 0) {
|
||||||
if (preference == 'on') {
|
if (preference == 'on' ||
|
||||||
vttSubtitlesIndex.value = 1;
|
(preference == 'auto' &&
|
||||||
} else if (preference == 'withoutAi') {
|
(await FlutterVolumeController.getVolume() ?? 0) <= 0)) {
|
||||||
for (int i = 0; i < subtitles.length; i++) {
|
idx = 1;
|
||||||
if (subtitles[i]['lan']!.startsWith('ai')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
vttSubtitlesIndex.value = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSubtitle(vttSubtitlesIndex.value);
|
}
|
||||||
|
setSubtitle(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,12 +257,8 @@ class HeaderControlState extends State<HeaderControl> {
|
|||||||
String? result = await showDialog(
|
String? result = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SelectDialog<String>(
|
return CdnSelectDialog(
|
||||||
title: 'CDN 设置',
|
sample: videoInfo.dash?.video?.first);
|
||||||
value: defaultCDNService,
|
|
||||||
values: CDNService.values.map((e) {
|
|
||||||
return {'title': e.description, 'value': e.code};
|
|
||||||
}).toList());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@@ -1059,7 +1055,7 @@ class HeaderControlState extends State<HeaderControl> {
|
|||||||
contentPadding:
|
contentPadding:
|
||||||
const EdgeInsets.only(left: 20, right: 20),
|
const EdgeInsets.only(left: 20, right: 20),
|
||||||
title: Text(VideoDecodeFormatsCode.fromString(i)!
|
title: Text(VideoDecodeFormatsCode.fromString(i)!
|
||||||
.description!),
|
.description),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
i!,
|
i!,
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
|
|||||||
@@ -45,20 +45,16 @@ class VideoUtils {
|
|||||||
String defaultCDNHost = CDNServiceCode.fromCode(defaultCDNService)!.host;
|
String defaultCDNHost = CDNServiceCode.fromCode(defaultCDNService)!.host;
|
||||||
debugPrint("defaultCDNHost:$defaultCDNHost");
|
debugPrint("defaultCDNHost:$defaultCDNHost");
|
||||||
if (videoUrl!.contains("szbdyd.com")) {
|
if (videoUrl!.contains("szbdyd.com")) {
|
||||||
String hostname =
|
final uri = Uri.parse(videoUrl);
|
||||||
Uri.parse(videoUrl).queryParameters['xy_usource'] ?? defaultCDNHost;
|
String hostname = uri.queryParameters['xy_usource'] ?? defaultCDNHost;
|
||||||
videoUrl =
|
videoUrl = uri.replace(host: hostname, port: 443).toString();
|
||||||
Uri.parse(videoUrl).replace(host: hostname, port: 443).toString();
|
} else if (videoUrl.contains(".mcdn.bilivideo") ||
|
||||||
} else if (videoUrl.contains(".mcdn.bilivideo")) {
|
videoUrl.contains("/upgcxcode/")) {
|
||||||
videoUrl = Uri.parse(videoUrl)
|
videoUrl = Uri.parse(videoUrl)
|
||||||
.replace(host: defaultCDNHost, port: 443)
|
.replace(host: defaultCDNHost, port: 443)
|
||||||
.toString();
|
.toString();
|
||||||
// videoUrl =
|
// videoUrl =
|
||||||
// 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}';
|
// 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}';
|
||||||
} else if (videoUrl.contains("/upgcxcode/")) {
|
|
||||||
videoUrl = Uri.parse(videoUrl)
|
|
||||||
.replace(host: defaultCDNHost, port: 443)
|
|
||||||
.toString();
|
|
||||||
}
|
}
|
||||||
debugPrint("videoUrl:$videoUrl");
|
debugPrint("videoUrl:$videoUrl");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user