mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-14 21:24:02 +08:00
refa: cdn (#1743)
* 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:
committed by
GitHub
parent
e589f27195
commit
27ae296b28
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user