import * as FileSystem from 'expo-file-system/legacy'; import type { PlayUrlResponse, DashAudioItem } from '../services/types'; /** * 将 JKVideo DASH 响应写成 MPD 文件,返回 file:// URI 供 ExoPlayer 播放。 * 选取 id === qn 的视频流(找不到则取第一条),带宽最高的音频流。 */ export async function buildDashMpdUri(playData: PlayUrlResponse, qn: number): Promise { const xml = buildMpdXml(playData, qn); const path = `${FileSystem.cacheDirectory}bili_dash_${qn}.mpd`; await FileSystem.writeAsStringAsync(path, xml, { encoding: FileSystem.EncodingType.UTF8 }); return path; } function isDolbyVision(codecs: string): boolean { return /^(dvhe|dvh1)/.test(codecs); } function buildMpdXml(playData: PlayUrlResponse, qn: number): string { const dash = playData.dash!; const video = dash.video.find(v => v.id === qn) ?? dash.video[0]; // 优先使用杜比全景声音轨,回退到带宽最高的普通音轨 const dolbyAudios = playData.dolby?.audio; const audio: DashAudioItem = (dolbyAudios && dolbyAudios.length > 0) ? dolbyAudios.reduce((best, a) => a.bandwidth > best.bandwidth ? a : best) : dash.audio.reduce((best, a) => a.bandwidth > best.bandwidth ? a : best); const dur = dash.duration; const vSeg = video.segment_base; const aSeg = audio.segment_base; const videoSegmentBase = vSeg ? `\n ` : ''; const audioSegmentBase = aSeg ? `\n ` : ''; const isDV = isDolbyVision(video.codecs); const dvProperty = isDV ? `\n ` : ''; return ` ${dvProperty} ${escapeXml(video.baseUrl)}${videoSegmentBase} ${escapeXml(audio.baseUrl)}${audioSegmentBase} `; } function escapeXml(s: string): string { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }