chore: 初始化仓库基线(AGENTS.md、git 规范、敏感文件排除)
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { StyleSheet, View, StatusBar, Alert, ActivityIndicator } from 'react-native';
|
||||
import { Text, IconButton, Surface } from 'react-native-paper';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||
import Video, { type VideoRef } from 'react-native-video';
|
||||
|
||||
export default function VideoPlayerScreen() {
|
||||
const route = useRoute<any>();
|
||||
const navigation = useNavigation<any>();
|
||||
const { cameraName, streamUrl } = route.params;
|
||||
const videoRef = useRef<VideoRef>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [muted, setMuted] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!streamUrl) {
|
||||
setError('无视频流地址');
|
||||
setLoading(false);
|
||||
}
|
||||
}, [streamUrl]);
|
||||
|
||||
const handleLoad = () => {
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handleError = (e: any) => {
|
||||
setLoading(false);
|
||||
const msg = e?.error?.errorString || e?.error?.localizedDescription || '视频加载失败';
|
||||
setError(msg);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar barStyle="light-content" />
|
||||
<SafeAreaView style={styles.header} edges={['top']}>
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
iconColor="white"
|
||||
size={24}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<Text style={styles.headerTitle}>{cameraName}</Text>
|
||||
<View style={{ width: 48 }} />
|
||||
</SafeAreaView>
|
||||
|
||||
<View style={styles.videoContainer}>
|
||||
{error ? (
|
||||
<View style={styles.errorContainer}>
|
||||
<IconButton icon="alert-circle-outline" size={48} iconColor="#B8B3AA" />
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
{streamUrl ? (
|
||||
<Video
|
||||
ref={videoRef}
|
||||
source={{ uri: streamUrl }}
|
||||
style={styles.video}
|
||||
resizeMode="contain"
|
||||
paused={paused}
|
||||
muted={muted}
|
||||
onLoad={handleLoad}
|
||||
onError={handleError}
|
||||
playInBackground={false}
|
||||
controls={false}
|
||||
bufferConfig={{
|
||||
minBufferMs: 1000,
|
||||
maxBufferMs: 3000,
|
||||
bufferForPlaybackMs: 500,
|
||||
bufferForPlaybackAfterRebufferMs: 1000,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{loading ? (
|
||||
<View style={styles.loadingOverlay}>
|
||||
<ActivityIndicator size="large" color="white" />
|
||||
<Text style={styles.loadingText}>加载中...</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{!loading && !error ? (
|
||||
<View style={styles.controlsOverlay}>
|
||||
<Surface style={styles.controlBar}>
|
||||
<IconButton
|
||||
icon={paused ? 'play' : 'pause'}
|
||||
iconColor="white"
|
||||
size={28}
|
||||
onPress={() => setPaused(!paused)}
|
||||
/>
|
||||
<IconButton
|
||||
icon={muted ? 'volume-off' : 'volume-high'}
|
||||
iconColor="white"
|
||||
size={24}
|
||||
onPress={() => setMuted(!muted)}
|
||||
/>
|
||||
</Surface>
|
||||
</View>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.footer}>
|
||||
<Text style={styles.streamUrl}>流地址: {streamUrl || 'N/A'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#1A1815',
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
headerTitle: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
textAlign: 'center',
|
||||
},
|
||||
videoContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
video: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
loadingOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(26,24,21,0.5)',
|
||||
},
|
||||
loadingText: {
|
||||
color: 'white',
|
||||
marginTop: 12,
|
||||
},
|
||||
controlsOverlay: {
|
||||
position: 'absolute',
|
||||
bottom: 24,
|
||||
left: 0,
|
||||
right: 0,
|
||||
alignItems: 'center',
|
||||
},
|
||||
controlBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(26,24,21,0.6)',
|
||||
borderRadius: 28,
|
||||
paddingHorizontal: 8,
|
||||
elevation: 0,
|
||||
},
|
||||
errorContainer: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
errorText: {
|
||||
color: '#B8B3AA',
|
||||
fontSize: 16,
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 32,
|
||||
},
|
||||
footer: {
|
||||
padding: 12,
|
||||
backgroundColor: '#1A1815',
|
||||
},
|
||||
streamUrl: {
|
||||
color: '#8C8780',
|
||||
fontSize: 11,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user