fix: bug
This commit is contained in:
294
components/video/fullscreen-video-modal.tsx
Normal file
294
components/video/fullscreen-video-modal.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { AVPlaybackStatus, ResizeMode, Video } from 'expo-av';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
Modal,
|
||||
PanResponder,
|
||||
Platform,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} 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) {
|
||||
const videoRef = useRef<Video>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isPlaying, setIsPlaying] = useState(autoPlay);
|
||||
const [showControls, setShowControls] = useState(true);
|
||||
|
||||
// 重置状态当模态框打开/关闭时
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setIsLoading(true);
|
||||
setIsPlaying(autoPlay);
|
||||
setShowControls(true);
|
||||
} else {
|
||||
// 停止播放当关闭时
|
||||
if (videoRef.current) {
|
||||
videoRef.current.stopAsync();
|
||||
}
|
||||
}
|
||||
}, [visible, autoPlay]);
|
||||
|
||||
// 处理视频加载完成
|
||||
const handleVideoReady = (status: AVPlaybackStatus) => {
|
||||
if (status.isLoaded) {
|
||||
setIsLoading(false);
|
||||
if (autoPlay) {
|
||||
videoRef.current?.playAsync();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 处理视频播放状态变化
|
||||
const handlePlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||
if (status.isLoaded) {
|
||||
setIsPlaying(status.isPlaying);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理视频错误
|
||||
const handleVideoError = (error: any) => {
|
||||
console.error('视频播放错误:', error);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
// 视频点击处理(直接关闭)
|
||||
const handleVideoPress = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
// 关闭模态框
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 创建手势处理器
|
||||
const panResponder = 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; // 滑动屏幕宽度的1/4触发切换
|
||||
|
||||
if (gestureState.dx > threshold && hasPrevious) {
|
||||
// 向右滑动,显示上一个
|
||||
onPrevious?.();
|
||||
} else if (gestureState.dx < -threshold && hasNext) {
|
||||
// 向左滑动,显示下一个
|
||||
onNext?.();
|
||||
}
|
||||
},
|
||||
})
|
||||
).current;
|
||||
|
||||
// 处理返回键(Android)
|
||||
useEffect(() => {
|
||||
const backHandler = () => {
|
||||
if (visible) {
|
||||
handleClose();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 在实际应用中,这里需要添加返回键监听
|
||||
// 对于演示,我们只做概念性实现
|
||||
|
||||
return () => {
|
||||
// 清理返回键监听
|
||||
};
|
||||
}, [visible]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
transparent={false}
|
||||
animationType="fade"
|
||||
onRequestClose={handleClose}
|
||||
statusBarTranslucent={true}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
{/* 状态栏处理 */}
|
||||
{Platform.OS === 'android' && <StatusBar hidden />}
|
||||
|
||||
{/* 视频容器 */}
|
||||
<TouchableOpacity
|
||||
style={styles.videoContainer}
|
||||
onPress={handleVideoPress}
|
||||
activeOpacity={1}
|
||||
>
|
||||
<View
|
||||
style={styles.videoWrapper}
|
||||
{...panResponder.panHandlers}
|
||||
>
|
||||
{/* 视频播放器 */}
|
||||
{Platform.OS === 'web' ? (
|
||||
<video
|
||||
ref={videoRef as any}
|
||||
src={videoUrl}
|
||||
style={styles.video}
|
||||
autoPlay={autoPlay}
|
||||
loop={isLooping}
|
||||
muted={isMuted}
|
||||
playsInline
|
||||
onLoadedData={() => setIsLoading(false)}
|
||||
onError={handleVideoError}
|
||||
/>
|
||||
) : (
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={{ uri: videoUrl }}
|
||||
style={styles.video}
|
||||
resizeMode={ResizeMode.CONTAIN}
|
||||
shouldPlay={autoPlay}
|
||||
isLooping={isLooping}
|
||||
isMuted={isMuted}
|
||||
useNativeControls={false}
|
||||
onReadyForDisplay={handleVideoReady}
|
||||
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
|
||||
onError={handleVideoError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 加载指示器 */}
|
||||
{isLoading && (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color="#4ECDC4" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 左右导航指示器 */}
|
||||
{hasPrevious && (
|
||||
<TouchableOpacity
|
||||
style={styles.leftIndicator}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
onPrevious?.();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<ThemedText style={styles.indicatorIcon}>‹</ThemedText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
{hasNext && (
|
||||
<TouchableOpacity
|
||||
style={styles.rightIndicator}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
onNext?.();
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<ThemedText style={styles.indicatorIcon}>›</ThemedText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
objectFit: 'cover'
|
||||
},
|
||||
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;
|
||||
Reference in New Issue
Block a user