diff --git a/lib/models/video/play/CDN.dart b/lib/models/video/play/CDN.dart new file mode 100644 index 00000000..e3c07749 --- /dev/null +++ b/lib/models/video/play/CDN.dart @@ -0,0 +1,114 @@ +//https://github.com/yujincheng08/BiliRoaming/blob/master/app/src/main/res/values/strings_raw.xml +//https://github.com/yujincheng08/BiliRoaming/blob/master/app/src/main/res/values/arrays.xml +enum CDNService { + baseUrl, + backupUrl, + ali, + alib, + alio1, + cos, + cosb, + coso1, + hw, + hwb, + hwo1, + hw_08c, + hw_08h, + hw_08ct, + tf_hw, + tf_tx, + akamai, + aliov, + cosov, + hwov, + hk_bcache, +} + +extension CDNServiceDesc on CDNService { + static final List _descList = [ + '基础 URL(不推荐)', + '备用 URL', + 'ali(阿里云)', + 'alib(阿里云)', + 'alio1(阿里云)', + 'cos(腾讯云)', + 'cosb(腾讯云,VOD 加速类型)', + 'coso1(腾讯云)', + 'hw(华为云,融合 CDN)', + 'hwb(华为云,融合 CDN)', + 'hwo1(华为云,融合 CDN)', + '08c(华为云,融合 CDN)', + '08h(华为云,融合 CDN)', + '08ct(华为云,融合 CDN)', + 'tf_hw(华为云)', + 'tf_tx(腾讯云)', + 'akamai(Akamai 海外)', + 'aliov(阿里云海外)', + 'cosov(腾讯云海外)', + 'hwov(华为云海外)', + 'hk_bcache(Bilibili海外)', + ]; + String get description => _descList[index]; +} + +extension CDNServiceHost on CDNService { + static final List _hostList = [ + '', + '', + 'upos-sz-mirrorali.bilivideo.com', + 'upos-sz-mirroralib.bilivideo.com', + 'upos-sz-mirroralio1.bilivideo.com', + 'upos-sz-mirrorcos.bilivideo.com', + 'upos-sz-mirrorcosb.bilivideo.com', + 'upos-sz-mirrorcoso1.bilivideo.com', + 'upos-sz-mirrorhw.bilivideo.com', + 'upos-sz-mirrorhwb.bilivideo.com', + 'upos-sz-mirrorhwo1.bilivideo.com', + 'upos-sz-mirror08c.bilivideo.com', + 'upos-sz-mirror08h.bilivideo.com', + 'upos-sz-mirror08ct.bilivideo.com', + 'upos-tf-all-hw.bilivideo.com', + 'upos-tf-all-tx.bilivideo.com', + 'upos-hz-mirrorakam.akamaized.net', + 'upos-sz-mirroraliov.bilivideo.com', + 'upos-sz-mirrorcosov.bilivideo.com', + 'upos-sz-mirrorhwov.bilivideo.com', + 'cn-hk-eq-bcache-01.bilivideo.com', + ]; + String get host => _hostList[index]; +} + +extension CDNServiceCode on CDNService { + static final List _codeList = [ + 'baseUrl', + 'backupUrl', + 'ali', + 'alib', + 'alio1', + 'cos', + 'cosb', + 'coso1', + 'hw', + 'hwb', + 'hwo1', + 'hw_08c', + 'hw_08h', + 'hw_08ct', + 'tf_hw', + 'tf_tx', + 'akamai', + 'aliov', + 'cosov', + 'hwov', + 'hk_bcache', + ]; + String get code => _codeList[index]; + + static CDNService? fromCode(String code) { + final index = _codeList.indexOf(code); + if (index != -1) { + return CDNService.values[index]; + } + return null; + } +} diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 77739a04..9ca75779 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -18,7 +18,7 @@ class LiveRoomController extends GetxController { PlPlayerController plPlayerController = PlPlayerController.getInstance(videoType: 'live'); Rx roomInfoH5 = RoomInfoH5Model().obs; - late bool enableCDN; + // late bool enableCDN; @override void onInit() { @@ -35,7 +35,7 @@ class LiveRoomController extends GetxController { } } // CDN优化 - enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + // enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); } playerInit(source) async { @@ -62,11 +62,7 @@ class LiveRoomController extends GetxController { List codec = res['data'].playurlInfo.playurl.stream.first.format.first.codec; CodecItem item = codec.first; - String videoUrl = enableCDN - ? VideoUtils.getCdnUrl(item) - : (item.urlInfo?.first.host)! + - item.baseUrl! + - item.urlInfo!.first.extra!; + String videoUrl = VideoUtils.getCdnUrl(item); await playerInit(videoUrl); return res; } diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart index 91c40b24..33b309c5 100644 --- a/lib/pages/setting/video_setting.dart +++ b/lib/pages/setting/video_setting.dart @@ -3,8 +3,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:PiliPalaX/models/video/play/quality.dart'; +import 'package:PiliPalaX/models/video/play/CDN.dart'; import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPalaX/utils/storage.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'widgets/switch_item.dart'; @@ -23,6 +25,7 @@ class _VideoSettingState extends State { late dynamic secondDecode; late dynamic hardwareDecoding; late dynamic videoSync; + late dynamic defaultCDNService; @override void initState() { @@ -39,6 +42,8 @@ class _VideoSettingState extends State { defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto'); videoSync = setting.get(SettingBoxKey.videoSync, defaultValue: 'display-resample'); + defaultCDNService = setting.get(SettingBoxKey.CDNService, + defaultValue: CDNService.ali.code); } @override @@ -80,11 +85,37 @@ class _VideoSettingState extends State { setKey: SettingBoxKey.p1080, defaultVal: true, ), - const SetSwitchItem( - title: 'CDN优化', - subTitle: '使用优质CDN线路', - leading: Icon(Icons.network_check_outlined), - setKey: SettingBoxKey.enableCDN, + ListTile( + title: Text('CDN 设置', style: titleStyle), + leading: Icon(MdiIcons.cloudPlusOutline), + subtitle: Text( + '当前使用:${CDNServiceCode.fromCode(defaultCDNService)!.description},部分 CDN 可能失效,如无法播放请尝试切换', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: 'CDN 设置', + value: defaultCDNService, + values: CDNService.values.map((e) { + return {'title': e.description, 'value': e.code}; + }).toList()); + }, + ); + if (result != null) { + defaultCDNService = result; + setting.put(SettingBoxKey.CDNService, result); + setState(() {}); + } + }, + ), + SetSwitchItem( + title: '音频不跟随 CDN 设置', + subTitle: '直接采用备用 URL,可解决部分视频无声', + leading: Icon(MdiIcons.musicNotePlus), + setKey: SettingBoxKey.disableAudioCDN, defaultVal: true, ), ListTile( @@ -117,7 +148,7 @@ class _VideoSettingState extends State { ListTile( dense: false, title: Text('默认音质', style: titleStyle), - leading: const Icon(Icons.audiotrack_outlined), + leading: const Icon(Icons.music_video_outlined), subtitle: Text( '当前音质:${AudioQualityCode.fromCode(defaultAudioQa)!.description!}', style: subTitleStyle, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 565148ae..4d4da5d1 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -86,7 +86,7 @@ class VideoDetailController extends GetxController Floating? floating; late PreferredSizeWidget headerControl; - late bool enableCDN; + // late bool enableCDN; late int? cacheVideoQa; late String cacheDecode; late String cacheSecondDecode; @@ -138,7 +138,8 @@ class VideoDetailController extends GetxController heroTag: heroTag, ); // CDN优化 - enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + // enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); + // 预设的画质 cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa, defaultValue: VideoQuality.values.last.code); @@ -388,9 +389,10 @@ class VideoDetailController extends GetxController (e) => e.codecs!.startsWith(currentDecodeFormats.code), orElse: () => videosList.first); - videoUrl = enableCDN - ? VideoUtils.getCdnUrl(firstVideo) - : (firstVideo.backupUrl ?? firstVideo.baseUrl!); + // videoUrl = enableCDN + // ? VideoUtils.getCdnUrl(firstVideo) + // : (firstVideo.backupUrl ?? firstVideo.baseUrl!); + videoUrl = VideoUtils.getCdnUrl(firstVideo); /// 优先顺序 设置中指定质量 -> 当前可选的最高质量 late AudioItem? firstAudio; @@ -415,9 +417,10 @@ class VideoDetailController extends GetxController } firstAudio = audiosList.firstWhere((e) => e.id == closestNumber, orElse: () => audiosList.first); - audioUrl = enableCDN - ? VideoUtils.getCdnUrl(firstAudio) - : (firstAudio.backupUrl ?? firstAudio.baseUrl!); + // audioUrl = enableCDN + // ? VideoUtils.getCdnUrl(firstAudio) + // : (firstAudio.backupUrl ?? firstAudio.baseUrl!); + audioUrl = VideoUtils.getCdnUrl(firstAudio); if (firstAudio.id != null) { currentAudioQa = AudioQualityCode.fromCode(firstAudio.id!)!; } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 09a1d9cb..1a4924e5 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -115,7 +115,9 @@ class SettingBoxKey { allowRotateScreen = 'allowRotateScreen', horizontalScreen = 'horizontalScreen', p1080 = 'p1080', - enableCDN = 'enableCDN', + CDNService = 'CDNService', + disableAudioCDN = 'disableAudioCDN', + // enableCDN = 'enableCDN', autoPiP = 'autoPiP', pipNoDanmaku = 'pipNoDanmaku', enableAutoLongPressSpeed = 'enableAutoLongPressSpeed', diff --git a/lib/utils/video_utils.dart b/lib/utils/video_utils.dart index d6612aa5..ae895bea 100644 --- a/lib/utils/video_utils.dart +++ b/lib/utils/video_utils.dart @@ -1,44 +1,101 @@ +import 'package:PiliPalaX/models/video/play/CDN.dart'; import 'package:PiliPalaX/models/video/play/url.dart'; +import 'package:PiliPalaX/utils/storage.dart'; import '../models/live/room_info.dart'; class VideoUtils { - static String getCdnUrl(dynamic item) { - var backupUrl = ""; - var videoUrl = ""; + static bool isMCDNorPCDN(String url) { + return url.contains("szbdyd.com") || + url.contains(".mcdn.bilivideo") || + RegExp(r'^https?://\d{1,3}\.\d{1,3}').hasMatch(url); + } - /// 先获取backupUrl 一般是upgcxcode地址 播放更稳定 - if (item is VideoItem) { - backupUrl = item.backupUrl ?? ""; - videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); - } else if (item is AudioItem) { - backupUrl = item.backupUrl ?? ""; - videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); - } else if (item is CodecItem) { + static String getCdnUrl(dynamic item) { + String? backupUrl; + String? videoUrl; + String defaultCDNService = GStorage.setting + .get(SettingBoxKey.CDNService, defaultValue: CDNService.ali.code); + if (item is AudioItem) { + if (GStorage.setting + .get(SettingBoxKey.disableAudioCDN, defaultValue: true)) { + return item.backupUrl ?? item.baseUrl ?? ""; + } + } + if (defaultCDNService == CDNService.baseUrl.code) { + return item.baseUrl ?? ""; + } + if (item is CodecItem) { backupUrl = (item.urlInfo?.first.host)! + item.baseUrl! + item.urlInfo!.first.extra!; - videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); } else { + backupUrl = item.backupUrl; + } + if (defaultCDNService == CDNService.backupUrl.code) { + return backupUrl ?? item.baseUrl ?? ""; + } + videoUrl = (backupUrl == null || isMCDNorPCDN(backupUrl)) + ? item.baseUrl + : backupUrl; + + if (videoUrl == null) { return ""; } + print("videoUrl:$videoUrl"); - /// issues #70 - if (videoUrl.contains(".mcdn.bilivideo")) { + String defaultCDNHost = CDNServiceCode.fromCode(defaultCDNService)!.host; + print("defaultCDNHost:$defaultCDNHost"); + if (videoUrl.contains("szbdyd.com")) { + String hostname = + Uri.parse(videoUrl).queryParameters['xy_usource'] ?? defaultCDNHost; videoUrl = - 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}'; + Uri.parse(videoUrl).replace(host: hostname, port: 443).toString(); + } else if (videoUrl.contains(".mcdn.bilivideo")) { + videoUrl = Uri.parse(videoUrl) + .replace(host: defaultCDNHost, port: 443) + .toString(); + // videoUrl = + // 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}'; } else if (videoUrl.contains("/upgcxcode/")) { - //CDN列表 - var cdnList = { - 'ali': 'upos-sz-mirrorali.bilivideo.com', - 'cos': 'upos-sz-mirrorcos.bilivideo.com', - 'hw': 'upos-sz-mirrorhw.bilivideo.com', - }; - //取一个CDN - var cdn = cdnList['ali'] ?? ""; - var reg = RegExp(r'(http|https)://(.*?)/upgcxcode/'); - videoUrl = videoUrl.replaceAll(reg, "https://$cdn/upgcxcode/"); + videoUrl = Uri.parse(videoUrl) + .replace(host: defaultCDNHost, port: 443) + .toString(); } + print("videoUrl:$videoUrl"); + + // /// 先获取backupUrl 一般是upgcxcode地址 播放更稳定 + // if (item is VideoItem) { + // backupUrl = item.backupUrl ?? ""; + // videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); + // } else if (item is AudioItem) { + // backupUrl = item.backupUrl ?? ""; + // videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); + // } else if (item is CodecItem) { + // backupUrl = (item.urlInfo?.first.host)! + + // item.baseUrl! + + // item.urlInfo!.first.extra!; + // videoUrl = backupUrl.contains("http") ? backupUrl : (item.baseUrl ?? ""); + // } else { + // return ""; + // } + // + // /// issues #70 + // if (videoUrl.contains(".mcdn.bilivideo")) { + // videoUrl = + // 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}'; + // } else if (videoUrl.contains("/upgcxcode/")) { + // //CDN列表 + // var cdnList = { + // 'ali': 'upos-sz-mirrorali.bilivideo.com', + // 'cos': 'upos-sz-mirrorcos.bilivideo.com', + // 'hw': 'upos-sz-mirrorhw.bilivideo.com', + // }; + // //取一个CDN + // var cdn = cdnList['cos'] ?? ""; + // var reg = RegExp(r'(http|https)://(.*?)/upgcxcode/'); + // videoUrl = videoUrl.replaceAll(reg, "https://$cdn/upgcxcode/"); + // } return videoUrl; }