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"; import { useTheme } from "../utils/theme"; interface Props { hlsUrl: string; flvUrl?: string; isLive: boolean; qualities?: { qn: number; desc: string }[]; currentQn?: number; onQualityChange?: (qn: number) => void; } const HIDE_DELAY = 3000; const HEADERS = {}; export function LivePlayer({ hlsUrl, flvUrl, 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 theme = useTheme(); 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 videoRef = useRef(null); const currentTimeRef = useRef(0); 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 fsW = Math.max(screenW, screenH); const fsH = Math.min(screenW, screenH); const containerStyle = isFullscreen ? { position: 'absolute' as const, top: 0, left: 0, width: fsW, height: fsH, zIndex: 999, elevation: 999 } : { width: screenW, height: videoH }; const currentQnDesc = qualities.find((q) => q.qn === currentQn)?.desc ?? ""; const videoContent = (