Files
JKVideo/components/VideoPlayer.tsx

120 lines
4.0 KiB
TypeScript
Raw Normal View History

import React, { useState, useRef, useEffect } from 'react';
import { View, StyleSheet, Text, Platform, Modal, StatusBar, useWindowDimensions } from 'react-native';
// expo-screen-orientation requires a dev build; gracefully degrade in Expo Go
let ScreenOrientation: typeof import('expo-screen-orientation') | null = null;
try { ScreenOrientation = require('expo-screen-orientation'); } catch {}
2026-03-06 14:52:28 +08:00
import { NativeVideoPlayer } from './NativeVideoPlayer';
import type { PlayUrlResponse, DanmakuItem } from '../services/types';
interface Props {
2026-03-10 19:04:18 +08:00
playData: PlayUrlResponse | null;
qualities: { qn: number; desc: string }[];
currentQn: number;
onQualityChange: (qn: number) => void;
onMiniPlayer?: () => void;
bvid?: string;
cid?: number;
danmakus?: DanmakuItem[];
onTimeUpdate?: (t: number) => void;
}
export function VideoPlayer({ playData, qualities, currentQn, onQualityChange, onMiniPlayer, bvid, cid, danmakus, onTimeUpdate }: Props) {
2026-03-10 19:04:18 +08:00
const [fullscreen, setFullscreen] = useState(false);
const { width, height } = useWindowDimensions();
const VIDEO_HEIGHT = width * 0.5625;
// When ScreenOrientation is unavailable (Expo Go), simulate landscape via transform
const needsRotation = !ScreenOrientation && fullscreen;
const lastTimeRef = useRef(0);
const handleEnterFullscreen = async () => {
setFullscreen(true);
if (Platform.OS !== 'web')
await ScreenOrientation?.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT);
};
const handleExitFullscreen = async () => {
setFullscreen(false);
if (Platform.OS !== 'web')
await ScreenOrientation?.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
};
useEffect(() => {
return () => {
if (Platform.OS !== 'web')
ScreenOrientation?.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
};
}, []);
2026-03-10 19:04:18 +08:00
if (!playData) {
return (
<View style={[{ width, height: VIDEO_HEIGHT, backgroundColor: '#000' }, styles.placeholder]}>
<Text style={styles.placeholderText}>...</Text>
</View>
);
}
2026-03-06 12:58:41 +08:00
if (Platform.OS === 'web') {
2026-03-10 19:04:18 +08:00
const url = playData.durl?.[0]?.url ?? '';
2026-03-06 12:58:41 +08:00
return (
<View style={{ width, height: VIDEO_HEIGHT, backgroundColor: '#000' }}>
2026-03-06 12:58:41 +08:00
<video
2026-03-10 19:04:18 +08:00
src={url}
2026-03-06 12:58:41 +08:00
style={{ width: '100%', height: '100%', backgroundColor: '#000' } as any}
controls
playsInline
/>
</View>
);
}
2026-03-10 19:04:18 +08:00
return (
<>
{/* Portrait player: unmount when fullscreen */}
{!fullscreen && (
<NativeVideoPlayer
playData={playData}
qualities={qualities}
currentQn={currentQn}
onQualityChange={onQualityChange}
onFullscreen={handleEnterFullscreen}
onMiniPlayer={onMiniPlayer}
bvid={bvid}
cid={cid}
isFullscreen={false}
onTimeUpdate={(t) => { lastTimeRef.current = t; onTimeUpdate?.(t); }}
/>
)}
2026-03-10 19:04:18 +08:00
<Modal visible={fullscreen} animationType="fade" statusBarTranslucent>
<StatusBar hidden />
<View style={{ flex: 1, backgroundColor: '#000', justifyContent: 'center', alignItems: 'center' }}>
<View style={needsRotation
? { width: height, height: width, transform: [{ rotate: '90deg' }] }
: { flex: 1, width: '100%' }
}>
<NativeVideoPlayer
playData={playData}
qualities={qualities}
currentQn={currentQn}
onQualityChange={onQualityChange}
onFullscreen={handleExitFullscreen}
bvid={bvid}
cid={cid}
danmakus={danmakus}
isFullscreen={true}
initialTime={lastTimeRef.current}
onTimeUpdate={(t) => { lastTimeRef.current = t; onTimeUpdate?.(t); }}
style={{ width: '100%', height: '100%' }}
/>
</View>
2026-03-10 19:04:18 +08:00
</View>
</Modal>
</>
);
}
const styles = StyleSheet.create({
placeholder: { justifyContent: 'center', alignItems: 'center' },
placeholderText: { color: '#fff', fontSize: 14 },
});