* refa: cdn

* feat: live cdn (WIP)

* tweaks

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* add live quality [skip ci]

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* mod: replace durl host

* tweak [skip ci]

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
My-Responsitories
2025-11-15 20:12:21 +08:00
committed by GitHub
parent e589f27195
commit 27ae296b28
13 changed files with 297 additions and 156 deletions

View File

@@ -109,7 +109,7 @@ abstract final class DownloadHttp {
orElse: () => videosList.first,
);
final videoUrl = VideoUtils.getCdnUrl(videoDash);
final videoUrl = VideoUtils.getCdnUrl(videoDash.playUrls);
final Type2File videoFile = Type2File(
id: videoDash.id!,
@@ -143,7 +143,10 @@ abstract final class DownloadHttp {
(e) => e.id == closestNumber,
orElse: () => audioDashList.first,
);
final audioUrl = VideoUtils.getCdnUrl(audioDash);
final audioUrl = VideoUtils.getCdnUrl(
audioDash.playUrls,
isAudio: true,
);
audioFileList = [
Type2File(
id: audioDash.id!,
@@ -177,7 +180,7 @@ abstract final class DownloadHttp {
md5: '',
metaUrl: '',
order: first.order!,
url: first.backupUrl?.lastOrNull ?? first.url!,
url: VideoUtils.getCdnUrl(first.playUrls),
),
];
final FormatItem? formatItem = data.supportFormats?.firstWhereOrNull(
@@ -189,6 +192,7 @@ abstract final class DownloadHttp {
formatItem?.quality ?? VideoQuality.clear480.code;
entry
..mediaType = 1
..typeTag = targetVideoQa.toString()
..videoQuality = targetVideoQa
..preferedVideoQuality = targetVideoQa
@@ -204,7 +208,7 @@ abstract final class DownloadHttp {
useIjkMediaCodec: false,
),
];
entry.mediaType = 1;
return Type1(
from: pageData?.from ?? ep?.from,
quality: entry.preferedVideoQuality,

View File

@@ -27,11 +27,84 @@ enum CDNService {
hk_bcache('hk_bcacheBilibili海外', 'cn-hk-eq-bcache-01.bilivideo.com')
;
String get code => name;
static final fromCode = values.byName;
final String desc;
final String host;
final String? host;
const CDNService(this.desc, [this.host = '']);
const CDNService(this.desc, [this.host]);
}
// from https://rec.danmuji.org/dev/cdn-info/
// {
// 'cn-ahwh-ct-': {'01': 16},
// 'cn-cq-ct-': {'01': 35, '02': 2},
// 'cn-gddg-ct-': {'01': 36},
// 'cn-gdfs-ct-': {'01': 28},
// 'cn-hblf-ct-': {'01': 21},
// 'cn-hbyc-ct-': {'02': 35},
// 'cn-hljheb-ct-': {'01': 12},
// 'cn-hnld-ct-': {'01': 56},
// 'cn-jsnt-ct-': {'01': 52},
// 'cn-jsyz-ct-': {'03': 52},
// 'cn-jxjj-ct-': {'01': 14},
// 'cn-sccd-ct-': {'01': 32},
// 'cn-sxxa-ct-': {'03': 14},
// 'cn-xj-ct-': {'01': 6},
// 'cn-zjjh-ct-': {'04': 37},
// 'cn-gddg-cu-': {'01': 15},
// 'cn-hncs-cu-': {'01': 14, 'v': 6},
// 'cn-hnly-cu-': {'01': 35},
// 'cn-jlcc-cu-': {'03': 16},
// 'cn-jstz-cu-': {'01': 14},
// 'cn-lnsy-cu-': {'01': 9, 'v': 4},
// 'cn-nmghhht-cu-': {'01': 15, 'v': 11},
// 'cn-sccd-cu-': {'01': 13},
// 'cn-sdqd-cu-': {'01': 25},
// 'cn-sxty-cu-': {'03': 10},
// 'cn-sxxa-cu-': {'02': 8},
// 'cn-zjhz-cu-': {'01': 8, 'v': 6},
// 'cn-cq-cm-': {'01': 30},
// 'cn-fjqz-cm-': {'01': 10},
// 'cn-gddg-cm-': {'01': 14},
// 'cn-gdst-cm-': {'01': 17},
// 'cn-hbsjz-cm-': {'02': 16},
// 'cn-hbwh-cm-': {'01': 23},
// 'cn-hncs-cm-': {'03': 24},
// 'cn-hnzz-cm-': {'01': 16},
// 'cn-jssz-cm-': {'01': 24, '02': 62},
// 'cn-jxnc-cm-': {'01': 20},
// 'cn-lnsy-cm-': {'01': 11},
// 'cn-nmghhht-cm-': {'01': 5},
// 'cn-sccd-cm-': {'03': 26},
// 'cn-sdjn-cm-': {'02': 14},
// 'cn-sxxa-cm-': {'01': 14},
// 'cn-tj-cm-': {'02': 16},
// 'cn-xj-cm-': {'02': 6},
// 'cn-zjhz-cm-': {'01': 29},
// 'cn-cq-gd-': {'01': 20},
// 'cn-gdgz-gd-': {'01': 20},
// 'cn-gzgy-gd-': {'01': 6},
// 'cn-hb-gd-': {'01': 4},
// 'cn-hbwh-gd-': {'01': 6},
// 'cn-hljheb-gd-': {'01': 2},
// 'cn-hncs-gd-': {'01': 8},
// 'cn-jlcc-gd-': {'01': 5},
// 'cn-jsnj-gd-': {'01': 8},
// 'cn-zjhz-gd-': {'02': 2},
// 'cn-bj-fx-': {'01': 6},
// 'cn-fjfz-fx-': {'01': 6},
// 'cn-gdgz-fx-': {'01': 18},
// 'cn-hbwh-fx-': {'01': 16},
// 'cn-hncs-fx-': {'01': 6},
// 'cn-hnzz-fx-': {'01': 8},
// 'cn-jsnj-fx-': {'02': 6},
// 'cn-sccd-fx-': {'01': 6},
// 'cn-sdjn-fx-': {'01': 6},
// 'cn-sh-fx-': {'01': 10},
// 'cn-tj-fx-': {'01': 6},
// 'cn-bj-se-': {'01': 8},
// 'cn-bj-cc-': {'03': 18},
// 'cn-gdfs-cc-': {'02': 21},
// 'cn-sh-cc-': {'01': 15},
// 'cn-zjhz-wasu-': {'03': 21, '04': 12},
// 'cn-sh-ix-': {'01': 13},
// 'cn-hk-eq-': {'01': 14}
// }

View File

@@ -2,6 +2,7 @@ enum LiveQuality {
dolby(30000, '杜比'),
origin4K(25000, '4K 原画'),
super4K(20000, '4K'),
super2K(15000, '2K'),
origin(10000, '原画'),
bluRay(400, '蓝光'),
superHD(250, '超清'),

View File

@@ -196,23 +196,17 @@ class Durl {
backupUrl: (json['backup_url'] as List?)?.fromCast<String>(),
);
}
}
final _ipRegExp = RegExp(r'^https?://\d{1,3}\.\d{1,3}');
bool _isMCDNorPCDN(String url) {
return url.contains("upos-sz-302") ||
url.contains("nexusedgeio.com") ||
url.contains("ahdohpiechei.com") ||
url.contains("szbdyd.com") ||
url.contains(".mcdn.bilivideo") ||
_ipRegExp.hasMatch(url);
Iterable<String> get playUrls sync* {
if (url?.isNotEmpty == true) yield url!;
if (backupUrl?.isNotEmpty == true) yield* backupUrl!;
}
}
abstract class BaseItem {
int? id;
String? baseUrl;
String? backupUrl;
List<String>? backupUrl;
int? bandWidth;
String? mimeType;
String? codecs;
@@ -243,14 +237,8 @@ abstract class BaseItem {
BaseItem.fromJson(Map<String, dynamic> json) {
id = json['id'];
baseUrl = json['baseUrl'] ?? json['base_url'];
final backupUrls = ((json['backupUrl'] ?? json['backup_url']) as List?)
backupUrl = ((json['backupUrl'] ?? json['backup_url']) as List?)
?.fromCast<String>();
backupUrl = backupUrls != null && backupUrls.isNotEmpty
? backupUrls.firstWhere(
(i) => !_isMCDNorPCDN(i),
orElse: () => backupUrls.first,
)
: null;
bandWidth = json['bandWidth'] ?? json['bandwidth'];
mimeType = json['mime_type'];
codecs = json['codecs'];
@@ -262,6 +250,11 @@ abstract class BaseItem {
segmentBase = json['segmentBase'] ?? json['segment_base'];
codecid = json['codecid'];
}
Iterable<String> get playUrls sync* {
if (baseUrl?.isNotEmpty == true) yield baseUrl!;
if (backupUrl?.isNotEmpty == true) yield* backupUrl!;
}
}
class VideoItem extends BaseItem {

View File

@@ -10,7 +10,9 @@ import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pb.dart'
PlaylistSource,
PlayInfo,
ThumbUpReq_ThumbType,
ListOrder;
ListOrder,
DashItem,
ResponseUrl;
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/ua_type.dart';
import 'package:PiliPlus/pages/common/common_intro_controller.dart'
@@ -226,7 +228,7 @@ class AudioController extends GetxController
(e) => e.id <= cacheAudioQa,
(a, b) => a.id > b.id ? a : b,
);
_onOpenMedia(VideoUtils.getCdnUrl(audio));
_onOpenMedia(VideoUtils.getCdnUrl(audio.playUrls));
} else if (playInfo.hasPlayUrl()) {
final playUrl = playInfo.playUrl;
final durls = playUrl.durl;
@@ -235,7 +237,7 @@ class AudioController extends GetxController
}
final durl = durls.first;
position.value = Duration.zero;
_onOpenMedia(VideoUtils.getDurlCdnUrl(durl));
_onOpenMedia(VideoUtils.getCdnUrl(durl.playUrls));
}
}
}
@@ -708,3 +710,17 @@ class AudioController extends GetxController
super.onClose();
}
}
extension on DashItem {
Iterable<String> get playUrls sync* {
yield baseUrl;
yield* backupUrl;
}
}
extension on ResponseUrl {
Iterable<String> get playUrls sync* {
yield url;
yield* backupUrl;
}
}

View File

@@ -164,7 +164,7 @@ class LiveRoomController extends GetxController {
}).toList();
currentQnDesc.value =
LiveQuality.fromCode(currentQn)?.desc ?? currentQn.toString();
videoUrl = VideoUtils.getCdnUrl(item);
videoUrl = VideoUtils.getLiveCdnUrl(item);
await playerInit();
isLoaded.value = true;
} else {

View File

@@ -54,9 +54,9 @@ List<SettingsModel> get videoSettings => [
title: 'CDN 设置',
leading: const Icon(MdiIcons.cloudPlusOutline),
getSubtitle: () =>
'当前使用:${CDNService.fromCode(VideoUtils.cdnService).desc},部分 CDN 可能失效,如无法播放请尝试切换',
'当前使用:${VideoUtils.cdnService.desc},部分 CDN 可能失效,如无法播放请尝试切换',
onTap: (setState) async {
String? result = await showDialog(
CDNService? result = await showDialog(
context: Get.context!,
builder: (context) {
return const CdnSelectDialog();
@@ -64,7 +64,57 @@ List<SettingsModel> get videoSettings => [
);
if (result != null) {
VideoUtils.cdnService = result;
await GStorage.setting.put(SettingBoxKey.CDNService, result);
await GStorage.setting.put(SettingBoxKey.CDNService, result.name);
setState();
}
},
),
SettingsModel(
settingsType: SettingsType.normal,
title: '直播 CDN 设置',
leading: const Icon(MdiIcons.cloudPlusOutline),
getSubtitle: () => '当前使用:${Pref.liveCdnUrl ?? "默认"}',
onTap: (setState) async {
String? result = await showDialog<String>(
context: Get.context!,
builder: (context) {
String host = Pref.liveCdnUrl ?? '';
return 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 (result != null) {
if (result.isEmpty) {
result = null;
await GStorage.setting.delete(SettingBoxKey.liveCdnUrl);
} else {
if (!result.startsWith('http')) {
result = 'https://${result}';
}
await GStorage.setting.put(SettingBoxKey.liveCdnUrl, result);
}
VideoUtils.liveCdnUrl = result;
setState();
}
},

View File

@@ -68,7 +68,7 @@ class SelectDialog<T> extends StatelessWidget {
}
class CdnSelectDialog extends StatefulWidget {
final VideoItem? sample;
final BaseItem? sample;
const CdnSelectDialog({
super.key,
@@ -113,7 +113,7 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
super.dispose();
}
Future<VideoItem> _getSampleUrl() async {
Future<BaseItem> _getSampleUrl() async {
final result = await VideoHttp.videoUrl(
cid: 196018899,
bvid: 'BV1fK4y1t7hj',
@@ -134,16 +134,19 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
}
}
Future<void> _testAllCdnServices(VideoItem videoItem) async {
Future<void> _testAllCdnServices(BaseItem videoItem) async {
for (final item in CDNService.values) {
if (!mounted) break;
await _testSingleCdn(item, videoItem);
}
}
Future<void> _testSingleCdn(CDNService item, VideoItem videoItem) async {
Future<void> _testSingleCdn(CDNService item, BaseItem videoItem) async {
try {
final cdnUrl = VideoUtils.getCdnUrl(videoItem, item.code);
final cdnUrl = VideoUtils.getCdnUrl(
videoItem.playUrls,
defaultCDNService: item,
);
await _measureDownloadSpeed(cdnUrl, item.index);
} catch (e) {
_handleSpeedTestError(e, item.index);
@@ -211,7 +214,17 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
if (kDebugMode) debugPrint('CDN speed test error: $error');
if (!mounted) return;
var message = error.toString();
String message;
if (error is DioException) {
final statusCode = error.response?.statusCode;
if (statusCode != null && 400 <= statusCode && statusCode < 500) {
message = '此视频可能无法替换为该CDN';
} else {
message = error.toString();
}
} else {
message = error.toString();
}
if (message.isEmpty) {
message = '测速失败';
}
@@ -220,9 +233,9 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
@override
Widget build(BuildContext context) {
return SelectDialog<String>(
return SelectDialog<CDNService>(
title: 'CDN 设置',
values: CDNService.values.map((i) => (i.code, i.desc)).toList(),
values: CDNService.values.map((i) => (i, i.desc)).toList(),
value: VideoUtils.cdnService,
subtitleBuilder: _cdnSpeedTest
? (context, index) {

View File

@@ -1125,7 +1125,7 @@ class VideoDetailController extends GetxController
currentDecodeFormats = VideoDecodeFormatType.fromString(video.codecs!);
}
firstVideo = video;
videoUrl = VideoUtils.getCdnUrl(firstVideo);
videoUrl = VideoUtils.getCdnUrl(firstVideo.playUrls);
/// 根据currentAudioQa 重新设置audioUrl
if (currentAudioQa != null) {
@@ -1133,7 +1133,7 @@ class VideoDetailController extends GetxController
(i) => i.id == currentAudioQa!.code,
orElse: () => data.dash!.audio!.first,
);
audioUrl = VideoUtils.getCdnUrl(firstAudio);
audioUrl = VideoUtils.getCdnUrl(firstAudio.playUrls, isAudio: true);
}
playerInit();
@@ -1308,7 +1308,7 @@ class VideoDetailController extends GetxController
}
if (data.dash == null && data.durl != null) {
final first = data.durl!.first;
videoUrl = first.backupUrl?.lastOrNull ?? first.url!;
videoUrl = VideoUtils.getCdnUrl(first.playUrls);
audioUrl = '';
// 实际为FLV/MP4格式但已被淘汰这里仅做兜底处理
@@ -1395,7 +1395,7 @@ class VideoDetailController extends GetxController
);
setVideoHeight();
videoUrl = VideoUtils.getCdnUrl(firstVideo);
videoUrl = VideoUtils.getCdnUrl(firstVideo.playUrls);
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
AudioItem? firstAudio;
@@ -1414,7 +1414,7 @@ class VideoDetailController extends GetxController
(e) => e.id == closestNumber,
orElse: () => audioList.first,
);
audioUrl = VideoUtils.getCdnUrl(firstAudio);
audioUrl = VideoUtils.getCdnUrl(firstAudio.playUrls, isAudio: true);
if (firstAudio.id case final int id?) {
currentAudioQa = AudioQuality.fromCode(id);
}
@@ -2001,13 +2001,14 @@ class VideoDetailController extends GetxController
);
SmartDialog.dismiss();
if (res.isSuccess) {
final PlayUrlModel data = res.data;
final data = res.data;
final first = data.durl?.firstOrNull;
final url = first?.backupUrl?.lastOrNull ?? first?.url;
if (url == null || url.isEmpty) {
if (first == null || first.playUrls.isEmpty) {
SmartDialog.showToast('不支持投屏');
return;
}
final url = VideoUtils.getCdnUrl(first.playUrls);
String? title;
try {
if (isUgc) {

View File

@@ -427,25 +427,23 @@ class HeaderControlState extends State<HeaderControl> {
title: const Text('CDN 设置', style: titleStyle),
leading: const Icon(MdiIcons.cloudPlusOutline, size: 20),
subtitle: Text(
'当前:${CDNService.fromCode(VideoUtils.cdnService).desc},无法播放请切换',
'当前:${VideoUtils.cdnService.desc},无法播放请切换',
style: subTitleStyle,
),
onTap: () async {
Get.back();
String? result = await showDialog(
CDNService? result = await showDialog(
context: context,
builder: (context) {
return CdnSelectDialog(
sample: videoInfo.dash?.video?.first,
sample: videoInfo.dash?.video?.firstOrNull,
);
},
);
if (result != null) {
VideoUtils.cdnService = result;
setting.put(SettingBoxKey.CDNService, result);
SmartDialog.showToast(
'已设置为 ${CDNService.fromCode(result).desc},正在重载视频',
);
setting.put(SettingBoxKey.CDNService, result.name);
SmartDialog.showToast('已设置为 ${result.desc},正在重载视频');
videoDetailCtr.queryVideoUrl(
defaultST: videoDetailCtr.playedTime,
fromReset: true,

View File

@@ -224,7 +224,8 @@ abstract class SettingBoxKey {
enableGradientBg = 'enableGradientBg',
navBarSort = 'navBarSort',
tempPlayerConf = 'tempPlayerConf',
reduceLuxColor = 'reduceLuxColor';
reduceLuxColor = 'reduceLuxColor',
liveCdnUrl = 'liveCdnUrl';
}
abstract class LocalCacheKey {

View File

@@ -238,10 +238,12 @@ abstract class Pref {
static String get videoSync =>
_setting.get(SettingBoxKey.videoSync, defaultValue: 'display-resample');
static String get defaultCDNService => _setting.get(
SettingBoxKey.CDNService,
defaultValue: CDNService.backupUrl.code,
);
static CDNService get defaultCDNService {
if (_setting.get(SettingBoxKey.CDNService) case final String cdnName) {
return CDNService.values.byName(cdnName);
}
return CDNService.backupUrl;
}
static String get banWordForRecommend =>
_setting.get(SettingBoxKey.banWordForRecommend, defaultValue: '');
@@ -870,4 +872,6 @@ abstract class Pref {
_setting.get(SettingBoxKey.setSystemBrightness, defaultValue: false);
static String? get downloadPath => _setting.get(SettingBoxKey.downloadPath);
static String? get liveCdnUrl => _setting.get(SettingBoxKey.liveCdnUrl);
}

View File

@@ -1,109 +1,96 @@
import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pb.dart'
show DashItem, ResponseUrl;
import 'package:PiliPlus/models/common/video/cdn_type.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/foundation.dart';
abstract final class VideoUtils {
static String cdnService = Pref.defaultCDNService;
static CDNService cdnService = Pref.defaultCDNService;
static String? liveCdnUrl = Pref.liveCdnUrl;
static bool disableAudioCDN = Pref.disableAudioCDN;
/// [DashItem] audio
/// [VideoItem] [AudioItem] video
/// [CodecItem] live
static String getCdnUrl(dynamic item, [String? defaultCDNService]) {
String? backupUrl;
String? videoUrl;
static const _proxyTf = 'proxy-tf-all-ws.bilivideo.com';
static final _mirrorRegex = RegExp(
r'^https?://(?:upos-\w+-(?!302)\w+|(?:upos|proxy)-tf-[^/]+)\.(?:bilivideo|akamaized)\.(?:com|net)/upgcxcode',
);
static final _mCdnTfRegex = RegExp(
r'^https?://(?:(?:(?:\d{1,3}\.){3}\d{1,3}|[^/]+\.mcdn\.bilivideo\.(?:com|cn|net))(?:\:\d{1,5})?/v\d/resource)',
);
static String getCdnUrl(
Iterable<String> urls, {
CDNService? defaultCDNService,
bool isAudio = false,
}) {
defaultCDNService ??= cdnService;
if (item case final AudioItem e) {
if (disableAudioCDN) {
return e.backupUrl?.isNotEmpty == true ? e.backupUrl! : e.baseUrl ?? '';
if (defaultCDNService == CDNService.baseUrl) {
return urls.first;
}
String? mcdnTf;
String? mcdnUpgcxcode;
String last = '';
for (var url in urls) {
last = url;
if (_mirrorRegex.hasMatch(url)) {
final uri = Uri.parse(url);
if (uri.queryParameters['os'] == 'mcdn') {
// upos-sz-mirrorcoso1.bilivideo.com os=mcdn
mcdnUpgcxcode = url;
} else {
if (defaultCDNService == CDNService.backupUrl ||
(isAudio && disableAudioCDN)) {
return url;
}
return uri.replace(host: defaultCDNService.host).toString();
}
}
if (_mCdnTfRegex.hasMatch(url)) {
mcdnTf = url;
continue;
}
// upos-\w*-302.* & bcache & mcdn host but upgcxcode path
if (url.contains('/upgcxcode/')) {
mcdnUpgcxcode = url;
continue;
}
// may be deprecated
if (url.contains('szbdyd.com')) {
final uri = Uri.parse(url);
final hostname =
uri.queryParameters['xy_usource'] ?? defaultCDNService.host;
return uri
.replace(scheme: 'https', host: hostname, port: 443)
.toString();
}
if (kDebugMode) {
debugPrint('unknown cdn type: $url');
}
}
if (defaultCDNService == CDNService.baseUrl.code) {
if (item case final BaseItem e) {
return e.baseUrl?.isNotEmpty == true ? e.baseUrl! : e.backupUrl ?? '';
}
}
if (item case final CodecItem e) {
backupUrl = e.urlInfo!.first.host! + e.baseUrl! + e.urlInfo!.first.extra!;
} else if (item case final DashItem e) {
backupUrl = e.backupUrl.lastOrNull;
} else {
backupUrl = item.backupUrl;
}
if (defaultCDNService == CDNService.backupUrl.code) {
return backupUrl?.isNotEmpty == true ? backupUrl! : item.baseUrl ?? '';
}
videoUrl = backupUrl?.isNotEmpty == true ? backupUrl : item.baseUrl;
if (videoUrl == null || videoUrl.isEmpty) {
return '';
}
// if (kDebugMode) debugPrint('videoUrl:$videoUrl');
String defaultCDNHost = CDNService.fromCode(defaultCDNService).host;
// if (kDebugMode) debugPrint('defaultCDNHost:$defaultCDNHost');
if (videoUrl.contains('szbdyd.com')) {
final uri = Uri.parse(videoUrl);
String hostname = uri.queryParameters['xy_usource'] ?? defaultCDNHost;
videoUrl = uri.replace(host: hostname, port: 443).toString();
} else if (videoUrl.contains('.mcdn.bilivideo') ||
videoUrl.contains('/upgcxcode/')) {
videoUrl = Uri.parse(
videoUrl,
).replace(host: defaultCDNHost, port: 443).toString();
// videoUrl =
// 'https://proxy-tf-all-ws.bilivideo.com/?url=${Uri.encodeComponent(videoUrl)}';
}
// if (kDebugMode) debugPrint('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;
return mcdnUpgcxcode == null
? mcdnTf == null
? last
: Uri(
scheme: 'https',
host: _proxyTf,
queryParameters: {'url': mcdnTf},
).toString()
: Uri.parse(mcdnUpgcxcode)
.replace(host: defaultCDNService.host ?? CDNService.ali.host)
.toString();
}
static String getDurlCdnUrl(ResponseUrl item) {
if (disableAudioCDN || cdnService == CDNService.backupUrl.code) {
return item.backupUrl.lastOrNull ?? item.url;
}
if (cdnService == CDNService.baseUrl.code) {
return item.url;
}
return Uri.parse(
item.backupUrl.lastOrNull ?? item.url,
).replace(host: CDNService.fromCode(cdnService).host, port: 443).toString();
static String getLiveCdnUrl(CodecItem e) {
return (liveCdnUrl ?? e.urlInfo!.first.host!) +
e.baseUrl! +
e.urlInfo!.first.extra!;
}
}