Files
PiliPlus/lib/http/download.dart
bggRGjQaUbCoE 041af37bb0 tweaks
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2026-01-02 12:06:09 +08:00

239 lines
8.4 KiB
Dart

import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/video/audio_quality.dart';
import 'package:PiliPlus/models/common/video/video_decode_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart';
import 'package:PiliPlus/models_new/download/bili_download_media_file_info.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/video_utils.dart';
abstract final class DownloadHttp {
static const String referer = "https://www.bilibili.com/";
static const String userAgent = "Bilibili Freedoooooom/MarkII";
static Future<BiliDownloadMediaInfo> getVideoUrl({
required BiliDownloadEntryInfo entry,
SourceInfo? source,
PageInfo? pageData,
EpInfo? ep,
}) async {
final isLogin = Accounts.get(AccountType.video).isLogin;
final res = await VideoHttp.videoUrl(
avid: entry.avid,
bvid: entry.bvid,
cid: entry.cid,
seasonId: entry.seasonId,
epid: ep?.episodeId,
qn: entry.preferedVideoQuality,
tryLook: !isLogin && Pref.p1080,
videoType: switch (ep?.from) {
'pugv' => VideoType.pugv,
!= null when isLogin => VideoType.pgc,
_ => VideoType.ugc,
},
);
if (res case Success(:final response)) {
final Dash? dash = response.dash;
if (dash != null) {
final List<VideoItem> videoList = dash.video!;
final curHighestVideoQa = videoList.first.quality.code;
final preferVideoQa = entry.preferedVideoQuality;
int targetVideoQa = curHighestVideoQa;
if (response.acceptQuality?.isNotEmpty == true &&
preferVideoQa <= curHighestVideoQa) {
// 如果预设的画质低于当前最高
targetVideoQa = response.acceptQuality!.findClosestTarget(
(e) => e <= preferVideoQa,
(a, b) => a > b ? a : b,
);
}
/// 取出符合当前画质的videoList
final List<VideoItem> videosList = videoList
.where((e) => e.quality.code == targetVideoQa)
.toList();
/// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
final List<FormatItem> supportFormats = response.supportFormats!;
// 根据画质选编码格式
final FormatItem targetSupportFormats = supportFormats.firstWhere(
(e) => e.quality == targetVideoQa,
orElse: () => supportFormats.first,
);
final List<String> supportDecodeFormats = targetSupportFormats.codecs!;
entry
..typeTag = targetVideoQa.toString()
..videoQuality = targetVideoQa
..preferedVideoQuality = targetVideoQa
..qualityPithyDescription =
targetSupportFormats.newDesc ??
VideoQuality.fromCode(targetVideoQa).desc;
String preferDecode = Pref.defaultDecode; // def avc
String preferSecondDecode = Pref.secondDecode; // def av1
// 默认从设置中取AV1
VideoDecodeFormatType currentDecodeFormats =
VideoDecodeFormatType.fromString(preferDecode);
VideoDecodeFormatType secondDecodeFormats =
VideoDecodeFormatType.fromString(preferSecondDecode);
// 当前视频没有对应格式返回第一个
int flag = 0;
for (final e in supportDecodeFormats) {
if (currentDecodeFormats.codes.any(e.startsWith)) {
flag = 1;
break;
} else if (secondDecodeFormats.codes.any(e.startsWith)) {
flag = 2;
}
}
if (flag == 2) {
currentDecodeFormats = secondDecodeFormats;
} else if (flag == 0) {
currentDecodeFormats = VideoDecodeFormatType.fromString(
supportDecodeFormats.first,
);
}
/// 取出符合当前解码格式的videoItem
final videoDash = videosList.firstWhere(
(e) => currentDecodeFormats.codes.any(e.codecs!.startsWith),
orElse: () => videosList.first,
);
final videoUrl = VideoUtils.getCdnUrl(videoDash.playUrls);
final Type2File videoFile = Type2File(
id: videoDash.id!,
baseUrl: videoUrl,
bandwidth: videoDash.bandWidth!,
codecid: videoDash.codecid!,
size: 0,
md5: '',
noRexcode: false,
frameRate: videoDash.frameRate ?? '',
width: videoDash.width!,
height: videoDash.height!,
dashDrmType: 0,
);
List<Type2File>? audioFileList;
final List<AudioItem>? audioDashList = dash.audio;
if (audioDashList != null && audioDashList.isNotEmpty) {
final preferAudioQa = Pref.defaultAudioQa;
final List<int> audioIds = audioDashList
.map((map) => map.id!)
.toList();
int closestNumber = audioIds.findClosestTarget(
(e) => e <= preferAudioQa,
(a, b) => a > b ? a : b,
);
if (!audioIds.contains(preferAudioQa) &&
audioIds.any((e) => e > preferAudioQa)) {
closestNumber = AudioQuality.k192.code;
}
final AudioItem audioDash = audioDashList.firstWhere(
(e) => e.id == closestNumber,
orElse: () => audioDashList.first,
);
final audioUrl = VideoUtils.getCdnUrl(
audioDash.playUrls,
isAudio: true,
);
audioFileList = [
Type2File(
id: audioDash.id!,
baseUrl: audioUrl,
bandwidth: audioDash.bandWidth!,
codecid: audioDash.codecid!,
size: 0,
md5: '',
noRexcode: false,
frameRate: audioDash.frameRate!,
width: audioDash.width!,
height: audioDash.height!,
dashDrmType: 0,
),
];
}
return Type2(
duration: dash.duration!,
video: [videoFile],
audio: audioFileList,
referer: referer,
userAgent: userAgent,
);
} else {
final first = response.durl!.first;
final List<Type1Segment> segmentList = [
Type1Segment(
backupUrls: [],
bytes: first.size!,
duration: first.length!,
md5: '',
metaUrl: '',
order: first.order!,
url: VideoUtils.getCdnUrl(first.playUrls),
),
];
final FormatItem? formatItem = response.supportFormats
?.firstWhereOrNull((e) => e.quality == response.quality);
final String description =
formatItem?.newDesc ?? VideoQuality.clear480.desc;
final int targetVideoQa =
formatItem?.quality ?? VideoQuality.clear480.code;
entry
..mediaType = 1
..typeTag = targetVideoQa.toString()
..videoQuality = targetVideoQa
..preferedVideoQuality = targetVideoQa
..qualityPithyDescription = description;
final List<Type1PlayerCodecConfig> playerCodecConfigList = [
Type1PlayerCodecConfig(
player: "IJK_PLAYER",
useIjkMediaCodec: false,
),
Type1PlayerCodecConfig(
player: "ANDROID_PLAYER",
useIjkMediaCodec: false,
),
];
return Type1(
from: pageData?.from ?? ep?.from,
quality: entry.preferedVideoQuality,
typeTag: entry.typeTag,
description: description,
playerCodecConfigList: playerCodecConfigList,
segmentList: segmentList,
parseTimestampMilli: 0,
availablePeriodMilli: 0,
isDownloaded: false,
isResolved: true,
timeLength: 0,
marlinToken: '',
videoCodecId: 0,
videoProject: true,
format: response.format!,
playerError: 0,
needVip: false,
needLogin: false,
intact: false,
referer: referer,
userAgent: userAgent,
);
}
} else {
throw res.toString();
}
}
}