Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
146 lines
3.3 KiB
TypeScript
146 lines
3.3 KiB
TypeScript
/**
|
|
* VideoPlayerModal 视频播放弹窗组件
|
|
* 全屏模态播放视频,基于 expo-video
|
|
*/
|
|
|
|
import React, { useCallback } from 'react';
|
|
import {
|
|
Modal,
|
|
View,
|
|
StyleSheet,
|
|
Dimensions,
|
|
TouchableOpacity,
|
|
Text,
|
|
StatusBar,
|
|
} from 'react-native';
|
|
import { VideoView, useVideoPlayer } from 'expo-video';
|
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { spacing, fontSizes, borderRadius } from '../../theme';
|
|
|
|
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
|
|
export interface VideoPlayerModalProps {
|
|
/** 是否可见 */
|
|
visible: boolean;
|
|
/** 视频 URL */
|
|
url: string;
|
|
/** 关闭回调 */
|
|
onClose: () => void;
|
|
}
|
|
|
|
/**
|
|
* 视频播放器内容 - 独立组件,仅在弹窗可见时挂载,避免预加载
|
|
*/
|
|
const VideoPlayerContent: React.FC<{ url: string; onClose: () => void }> = ({ url, onClose }) => {
|
|
const insets = useSafeAreaInsets();
|
|
|
|
const player = useVideoPlayer(url, (p) => {
|
|
p.loop = false;
|
|
p.play();
|
|
});
|
|
|
|
const handleClose = useCallback(() => {
|
|
player.pause();
|
|
onClose();
|
|
}, [player, onClose]);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<StatusBar hidden />
|
|
|
|
{/* 顶部关闭按钮 */}
|
|
<View style={[styles.header, { paddingTop: insets.top + spacing.md }]}>
|
|
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
|
<MaterialCommunityIcons name="close" size={24} color="#FFF" />
|
|
</TouchableOpacity>
|
|
<Text style={styles.title}>视频</Text>
|
|
<View style={styles.placeholder} />
|
|
</View>
|
|
|
|
{/* 视频播放区域 */}
|
|
<View style={styles.videoContainer}>
|
|
<VideoView
|
|
player={player}
|
|
style={styles.video}
|
|
contentFit="contain"
|
|
nativeControls
|
|
/>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 视频播放弹窗 - 仅在可见时渲染播放器,避免提前加载视频资源
|
|
*/
|
|
export const VideoPlayerModal: React.FC<VideoPlayerModalProps> = ({
|
|
visible,
|
|
url,
|
|
onClose,
|
|
}) => {
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
transparent={false}
|
|
animationType="fade"
|
|
onRequestClose={onClose}
|
|
statusBarTranslucent
|
|
supportedOrientations={['portrait', 'landscape']}
|
|
>
|
|
{visible && url ? (
|
|
<VideoPlayerContent url={url} onClose={onClose} />
|
|
) : (
|
|
<View style={styles.container} />
|
|
)}
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#000',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: spacing.md,
|
|
paddingBottom: spacing.md,
|
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
zIndex: 10,
|
|
},
|
|
closeButton: {
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: borderRadius.full,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
title: {
|
|
color: '#FFF',
|
|
fontSize: fontSizes.md,
|
|
fontWeight: '600',
|
|
},
|
|
placeholder: {
|
|
width: 40,
|
|
},
|
|
videoContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
video: {
|
|
width: SCREEN_WIDTH,
|
|
height: SCREEN_HEIGHT,
|
|
},
|
|
});
|
|
|
|
export default VideoPlayerModal;
|