diff --git a/app.json b/app.json index 1aa8150..2683ddd 100644 --- a/app.json +++ b/app.json @@ -36,7 +36,8 @@ "favicon": "./assets/favicon.png" }, "plugins": [ - "expo-router" + "expo-router", + "react-native-video" ], "experiments": { "typedRoutes": true diff --git a/components/NativeVideoPlayer.tsx b/components/NativeVideoPlayer.tsx index 811988b..01d8220 100644 --- a/components/NativeVideoPlayer.tsx +++ b/components/NativeVideoPlayer.tsx @@ -1,16 +1,21 @@ -import React, { useState } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { View, StyleSheet, Dimensions, TouchableOpacity, - Text, Modal, FlatList, + Text, Modal, } from 'react-native'; -import { WebView } from 'react-native-webview'; +import Video, { VideoRef } from 'react-native-video'; import { Ionicons } from '@expo/vector-icons'; -import { buildMpd } from '../utils/buildMpd'; import type { PlayUrlResponse } from '../services/types'; +import { buildDashDataUri } from '../utils/dash'; const { width } = Dimensions.get('window'); const VIDEO_HEIGHT = width * 0.5625; +const BILIBILI_HEADERS = { + Referer: 'https://www.bilibili.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', +}; + interface Props { playData: PlayUrlResponse | null; qualities: { qn: number; desc: string }[]; @@ -19,76 +24,46 @@ interface Props { onFullscreen: () => void; onMiniPlayer?: () => void; style?: object; -} - -function buildMp4Html(url: string): string { - return ` - - - - - - - - - -`; -} - -function buildDashHtml(mpdStr: string): string { - const mpdBase64 = `data:application/dash+xml;base64,${btoa(unescape(encodeURIComponent(mpdStr)))}`; - return ` - - - - - - - - - - -`; -} - -function getHtml(playData: PlayUrlResponse | null): string { - if (!playData) return ''; - if (playData.dash) { - const v = playData.dash.video[0]; - const a = playData.dash.audio[0]; - if (v && a) { - const mpd = buildMpd(v.baseUrl, v.codecs, v.bandwidth, a.baseUrl, a.codecs, a.bandwidth); - return buildDashHtml(mpd); - } - } - const url = playData.durl?.[0]?.url; - if (url) return buildMp4Html(url); - return ''; + onProgress?: (currentTime: number, duration: number) => void; + seekTo?: { t: number; v: number }; } export function NativeVideoPlayer({ playData, qualities, currentQn, onQualityChange, onFullscreen, onMiniPlayer, style, + onProgress, seekTo, }: Props) { const [showQuality, setShowQuality] = useState(false); + const videoRef = useRef(null); const currentDesc = qualities.find(q => q.qn === currentQn)?.desc ?? (currentQn ? String(currentQn) : 'HD'); - const html = getHtml(playData); + const isDash = !!playData?.dash; + const url = isDash + ? buildDashDataUri(playData!, currentQn) + : playData?.durl?.[0]?.url; + + useEffect(() => { + if (seekTo !== undefined) videoRef.current?.seek(seekTo.t); + }, [seekTo]); return ( - + {url ? ( +