189 lines
5.0 KiB
TypeScript
189 lines
5.0 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { StyleSheet, View, StatusBar, 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,
|
|
},
|
|
});
|