mirror of
https://gh-proxy.org/https://github.com/tiajinsha/JKVideo
synced 2026-07-08 07:28:37 +08:00
fix: switch DASH delivery from data: URI to local MPD file via expo-file-system
data: URI scheme for DASH manifests is unreliable in ExoPlayer. Write MPD XML to FileSystem.cacheDirectory/bili_dash.mpd and pass file:// URI to react-native-video instead. URL resolution is now async via useEffect+state in NativeVideoPlayer.
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
|||||||
import Video, { VideoRef } from 'react-native-video';
|
import Video, { VideoRef } from 'react-native-video';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
import type { PlayUrlResponse } from '../services/types';
|
import type { PlayUrlResponse } from '../services/types';
|
||||||
import { buildDashDataUri } from '../utils/dash';
|
import { buildDashMpdUri } from '../utils/dash';
|
||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
const VIDEO_HEIGHT = width * 0.5625;
|
const VIDEO_HEIGHT = width * 0.5625;
|
||||||
@@ -33,12 +33,21 @@ export function NativeVideoPlayer({
|
|||||||
onProgress, seekTo,
|
onProgress, seekTo,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [showQuality, setShowQuality] = useState(false);
|
const [showQuality, setShowQuality] = useState(false);
|
||||||
|
const [resolvedUrl, setResolvedUrl] = useState<string | undefined>();
|
||||||
const videoRef = useRef<VideoRef>(null);
|
const videoRef = useRef<VideoRef>(null);
|
||||||
const currentDesc = qualities.find(q => q.qn === currentQn)?.desc ?? (currentQn ? String(currentQn) : 'HD');
|
const currentDesc = qualities.find(q => q.qn === currentQn)?.desc ?? (currentQn ? String(currentQn) : 'HD');
|
||||||
const isDash = !!playData?.dash;
|
const isDash = !!playData?.dash;
|
||||||
const url = isDash
|
|
||||||
? buildDashDataUri(playData!, currentQn)
|
useEffect(() => {
|
||||||
: playData?.durl?.[0]?.url;
|
if (!playData) { setResolvedUrl(undefined); return; }
|
||||||
|
if (isDash) {
|
||||||
|
buildDashMpdUri(playData, currentQn).then(setResolvedUrl).catch(() => {
|
||||||
|
setResolvedUrl(playData.dash!.video[0]?.baseUrl);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setResolvedUrl(playData.durl?.[0]?.url);
|
||||||
|
}
|
||||||
|
}, [playData, currentQn]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (seekTo !== undefined) videoRef.current?.seek(seekTo.t);
|
if (seekTo !== undefined) videoRef.current?.seek(seekTo.t);
|
||||||
@@ -46,12 +55,12 @@ export function NativeVideoPlayer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]}>
|
<View style={[styles.container, style]}>
|
||||||
{url ? (
|
{resolvedUrl ? (
|
||||||
<Video
|
<Video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
source={isDash
|
source={isDash
|
||||||
? { uri: url, type: 'mpd', headers: BILIBILI_HEADERS }
|
? { uri: resolvedUrl, type: 'mpd', headers: BILIBILI_HEADERS }
|
||||||
: { uri: url, headers: BILIBILI_HEADERS }
|
: { uri: resolvedUrl, headers: BILIBILI_HEADERS }
|
||||||
}
|
}
|
||||||
style={styles.video}
|
style={styles.video}
|
||||||
resizeMode="contain"
|
resizeMode="contain"
|
||||||
|
|||||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"expo": "~55.0.5",
|
"expo": "~55.0.5",
|
||||||
"expo-av": "^16.0.8",
|
"expo-av": "^16.0.8",
|
||||||
"expo-dev-client": "~55.0.11",
|
"expo-dev-client": "~55.0.11",
|
||||||
|
"expo-file-system": "~55.0.10",
|
||||||
"expo-linear-gradient": "~55.0.8",
|
"expo-linear-gradient": "~55.0.8",
|
||||||
"expo-router": "~55.0.4",
|
"expo-router": "~55.0.4",
|
||||||
"expo-status-bar": "~55.0.4",
|
"expo-status-bar": "~55.0.4",
|
||||||
@@ -4397,6 +4398,16 @@
|
|||||||
"expo": "*"
|
"expo": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-file-system": {
|
||||||
|
"version": "55.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.10.tgz",
|
||||||
|
"integrity": "sha512-ysFdVdUgtfj2ApY0Cn+pBg+yK4xp+SNwcaH8j2B91JJQ4OXJmnyCSmrNZYz7J4mdYVuv2GzxIP+N/IGlHQG3Yw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-font": {
|
"node_modules/expo-font": {
|
||||||
"version": "55.0.4",
|
"version": "55.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz",
|
||||||
@@ -4974,16 +4985,6 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/expo/node_modules/expo-file-system": {
|
|
||||||
"version": "55.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.10.tgz",
|
|
||||||
"integrity": "sha512-ysFdVdUgtfj2ApY0Cn+pBg+yK4xp+SNwcaH8j2B91JJQ4OXJmnyCSmrNZYz7J4mdYVuv2GzxIP+N/IGlHQG3Yw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"expo": "*",
|
|
||||||
"react-native": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/expo/node_modules/expo-keep-awake": {
|
"node_modules/expo/node_modules/expo-keep-awake": {
|
||||||
"version": "55.0.4",
|
"version": "55.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"expo": "~55.0.5",
|
"expo": "~55.0.5",
|
||||||
"expo-av": "^16.0.8",
|
"expo-av": "^16.0.8",
|
||||||
"expo-dev-client": "~55.0.11",
|
"expo-dev-client": "~55.0.11",
|
||||||
|
"expo-file-system": "~55.0.10",
|
||||||
"expo-linear-gradient": "~55.0.8",
|
"expo-linear-gradient": "~55.0.8",
|
||||||
"expo-router": "~55.0.4",
|
"expo-router": "~55.0.4",
|
||||||
"expo-status-bar": "~55.0.4",
|
"expo-status-bar": "~55.0.4",
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
|
import * as FileSystem from 'expo-file-system/legacy';
|
||||||
import type { PlayUrlResponse } from '../services/types';
|
import type { PlayUrlResponse } from '../services/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 Bilibili DASH 响应生成 MPD data URI。
|
* 将 Bilibili DASH 响应写成 MPD 文件,返回 file:// URI 供 ExoPlayer 播放。
|
||||||
* 选取 id === qn 的视频流(找不到则取第一条),带宽最高的音频流。
|
* 选取 id === qn 的视频流(找不到则取第一条),带宽最高的音频流。
|
||||||
* 返回 "data:application/dash+xml;base64,..." 供 react-native-video (ExoPlayer) 使用。
|
|
||||||
*/
|
*/
|
||||||
export function buildDashDataUri(playData: PlayUrlResponse, qn: number): string {
|
export async function buildDashMpdUri(playData: PlayUrlResponse, qn: number): Promise<string> {
|
||||||
|
const xml = buildMpdXml(playData, qn);
|
||||||
|
const path = `${FileSystem.cacheDirectory}bili_dash.mpd`;
|
||||||
|
await FileSystem.writeAsStringAsync(path, xml, { encoding: FileSystem.EncodingType.UTF8 });
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMpdXml(playData: PlayUrlResponse, qn: number): string {
|
||||||
const dash = playData.dash!;
|
const dash = playData.dash!;
|
||||||
|
|
||||||
const video = dash.video.find(v => v.id === qn) ?? dash.video[0];
|
const video = dash.video.find(v => v.id === qn) ?? dash.video[0];
|
||||||
@@ -14,18 +21,17 @@ export function buildDashDataUri(playData: PlayUrlResponse, qn: number): string
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dur = dash.duration;
|
const dur = dash.duration;
|
||||||
|
|
||||||
const vSeg = video.segment_base;
|
const vSeg = video.segment_base;
|
||||||
const aSeg = audio.segment_base;
|
const aSeg = audio.segment_base;
|
||||||
|
|
||||||
const videoSegmentBase = vSeg
|
const videoSegmentBase = vSeg
|
||||||
? `\n <SegmentBase indexRange="${vSeg.index_range}">\n <Initialization range="${vSeg.initialization}"/>\n </SegmentBase>`
|
? `\n <SegmentBase indexRange="${vSeg.index_range}"><Initialization range="${vSeg.initialization}"/></SegmentBase>`
|
||||||
: '';
|
: '';
|
||||||
const audioSegmentBase = aSeg
|
const audioSegmentBase = aSeg
|
||||||
? `\n <SegmentBase indexRange="${aSeg.index_range}">\n <Initialization range="${aSeg.initialization}"/>\n </SegmentBase>`
|
? `\n <SegmentBase indexRange="${aSeg.index_range}"><Initialization range="${aSeg.initialization}"/></SegmentBase>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
|
||||||
profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
|
profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
|
||||||
type="static"
|
type="static"
|
||||||
@@ -43,8 +49,6 @@ export function buildDashDataUri(playData: PlayUrlResponse, qn: number): string
|
|||||||
</AdaptationSet>
|
</AdaptationSet>
|
||||||
</Period>
|
</Period>
|
||||||
</MPD>`;
|
</MPD>`;
|
||||||
|
|
||||||
return `data:application/dash+xml;base64,${btoa(xml)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeXml(s: string): string {
|
function escapeXml(s: string): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user