import React, { useState, useRef, useCallback, useEffect } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, TouchableWithoutFeedback, Modal, Platform, useWindowDimensions, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; interface Props { hlsUrl: string; isLive: boolean; qualities?: { qn: number; desc: string }[]; currentQn?: number; onQualityChange?: (qn: number) => void; } const HIDE_DELAY = 3000; const HEADERS = { Referer: 'https://live.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', }; export function LivePlayer({ hlsUrl, isLive, qualities = [], currentQn = 0, onQualityChange }: Props) { const { width: SCREEN_W, height: SCREEN_H } = useWindowDimensions(); const VIDEO_H = SCREEN_W * 0.5625; if (Platform.OS === 'web') { return ( 请在手机端观看直播 ); } if (!isLive || !hlsUrl) { return ( 暂未开播 ); } return ( ); } function NativeLivePlayer({ hlsUrl, screenW, screenH, videoH, qualities, currentQn, onQualityChange, }: { hlsUrl: string; screenW: number; screenH: number; videoH: number; qualities: { qn: number; desc: string }[]; currentQn: number; onQualityChange?: (qn: number) => void; }) { const Video = require('react-native-video').default; const [showControls, setShowControls] = useState(true); const [paused, setPaused] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [buffering, setBuffering] = useState(true); const [showQualityPanel, setShowQualityPanel] = useState(false); const hideTimer = useRef | null>(null); const resetHideTimer = useCallback(() => { if (hideTimer.current) clearTimeout(hideTimer.current); hideTimer.current = setTimeout(() => setShowControls(false), HIDE_DELAY); }, []); useEffect(() => { resetHideTimer(); return () => { if (hideTimer.current) clearTimeout(hideTimer.current); }; }, []); // Lock/unlock orientation on fullscreen toggle useEffect(() => { (async () => { try { const ScreenOrientation = require('expo-screen-orientation'); if (isFullscreen) { await ScreenOrientation.lockAsync( ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, ); } else { await ScreenOrientation.lockAsync( ScreenOrientation.OrientationLock.PORTRAIT_UP, ); } } catch { /* graceful degradation in Expo Go */ } })(); }, [isFullscreen]); // Restore portrait on unmount useEffect(() => { return () => { (async () => { try { const ScreenOrientation = require('expo-screen-orientation'); await ScreenOrientation.lockAsync( ScreenOrientation.OrientationLock.PORTRAIT_UP, ); } catch { /* ignore */ } })(); }; }, []); const handleTap = useCallback(() => { setShowControls(prev => { if (!prev) { resetHideTimer(); return true; } if (hideTimer.current) clearTimeout(hideTimer.current); return false; }); }, [resetHideTimer]); const containerStyle = isFullscreen ? { width: screenH, height: screenW } : { width: screenW, height: videoH }; const currentQnDesc = qualities.find(q => q.qn === currentQn)?.desc ?? ''; const videoContent = (