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; // false → show offline placeholder
}
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 }: 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,
}: {
hlsUrl: string;
screenW: number;
screenH: number;
videoH: number;
}) {
// Lazy import to avoid web bundle issues
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 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); };
}, []);
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 videoContent = (
);
if (isFullscreen) {
return (
{videoContent}
);
}
return videoContent;
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
},
webHint: { color: '#fff', fontSize: 15 },
offlineText: { color: '#999', fontSize: 14, marginTop: 10 },
bufferingOverlay: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
},
bufferingText: { color: '#fff', fontSize: 13, opacity: 0.8 },
liveBadge: {
position: 'absolute',
top: 10,
left: 12,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
paddingHorizontal: 8,
paddingVertical: 3,
borderRadius: 4,
},
liveDot: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: '#f00',
marginRight: 5,
},
liveText: { color: '#fff', fontSize: 11, fontWeight: '700', letterSpacing: 1 },
centerBtn: {
position: 'absolute',
top: '50%',
left: '50%',
transform: [{ translateX: -28 }, { translateY: -28 }],
},
centerBtnBg: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: 'rgba(0,0,0,0.45)',
alignItems: 'center',
justifyContent: 'center',
},
bottomBar: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingBottom: 8,
paddingTop: 24,
backgroundColor: 'rgba(0,0,0,0)',
},
ctrlBtn: { paddingHorizontal: 8, paddingVertical: 4 },
fsModal: {
flex: 1,
backgroundColor: '#000',
alignItems: 'center',
justifyContent: 'center',
},
});