import { ThemedText } from '@/components/themed-text'; import { VideoView, useVideoPlayer } from 'expo-video'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ActivityIndicator, Dimensions, Modal, PanResponder, Platform, StatusBar, StyleSheet, TouchableOpacity, View, BackHandler, } from 'react-native'; const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); export interface FullscreenVideoModalProps { visible: boolean; onClose: () => void; videoUrl: string; poster?: string; autoPlay?: boolean; isLooping?: boolean; isMuted?: boolean; onPrevious?: () => void; onNext?: () => void; hasNext?: boolean; hasPrevious?: boolean; } export function FullscreenVideoModal({ visible, onClose, videoUrl, poster, autoPlay = true, isLooping = true, isMuted = false, onPrevious, onNext, hasNext = false, hasPrevious = false, }: FullscreenVideoModalProps) { // 使用useRef存储player实例,避免无限循环 const playerRef = useRef(null); // 稳定videoUrl,使用useMemo const stableVideoUrl = useMemo(() => ({ uri: videoUrl }), [videoUrl]); // 始终调用useVideoPlayer,但只在videoUrl变化时更新player const newPlayer = useVideoPlayer( stableVideoUrl, (player) => { player.loop = isLooping; player.muted = isMuted; if (autoPlay) { player.play(); } } ); // 只有当newPlayer变化时才更新player useEffect(() => { playerRef.current = newPlayer; }, [newPlayer]); // 获取当前的player实例 const player = playerRef.current || newPlayer; const [isLoading, setIsLoading] = useState(true); // 重置状态当模态框打开/关闭时 useEffect(() => { if (visible) { setIsLoading(true); } else { const currentPlayer = playerRef.current; if (currentPlayer) { currentPlayer.pause(); } } }, [visible, autoPlay]); // 处理视频加载完成 const handleVideoReady = () => { setIsLoading(false); if (autoPlay) { const currentPlayer = playerRef.current; currentPlayer?.play(); } }; // 处理视频错误 const handleVideoError = (error: any) => { console.error('视频播放错误:', error); setIsLoading(false); }; const handleClose = useCallback(() => { onClose(); }, [onClose]); // 视频点击处理(直接关闭) const handleVideoPress = () => { handleClose(); }; // 创建手势处理器 const panResponder = React.useRef( PanResponder.create({ onMoveShouldSetPanResponder: (_, gestureState) => { return Math.abs(gestureState.dx) > Math.abs(gestureState.dy) && Math.abs(gestureState.dx) > 10; }, onPanResponderRelease: (_, gestureState) => { const threshold = screenWidth / 4; if (gestureState.dx > threshold && hasPrevious) { onPrevious?.(); } else if (gestureState.dx < -threshold && hasNext) { onNext?.(); } }, }) ).current; // 处理返回键(Android) useEffect(() => { const handleBackPress = () => { if (visible) { onClose(); return true; } return false; }; const subscription = BackHandler.addEventListener('hardwareBackPress', handleBackPress); return () => subscription.remove(); }, [visible, onClose]); if (!visible) return null; return ( {Platform.OS === 'android' && ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#000', }, videoContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, videoWrapper: { flex: 1, width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center', }, video: { width: screenWidth, height: screenHeight, backgroundColor: '#000', }, loadingOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.3)', }, leftIndicator: { position: 'absolute', left: 20, top: '50%', marginTop: -20, width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', zIndex: 10, }, rightIndicator: { position: 'absolute', right: 20, top: '50%', marginTop: -20, width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', zIndex: 10, }, indicatorIcon: { fontSize: 24, color: '#fff', fontWeight: 'bold', }, }); export default FullscreenVideoModal;