176 lines
5.7 KiB
TypeScript
176 lines
5.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { View, Text, Video } from '@tarojs/components';
|
|
import { useRouter } from '@tarojs/taro';
|
|
import styles from './index.module.scss';
|
|
import { getCameras, playCamera } from '@/api/video';
|
|
import { resolveUrl } from '@/api/config';
|
|
import type { Camera } from '@/types';
|
|
|
|
const VideoPlayerPage: React.FC = () => {
|
|
const router = useRouter();
|
|
const cameraId = router.params.id ? Number(router.params.id) : 0;
|
|
const cameraName = router.params.name ? decodeURIComponent(router.params.name) : '视频播放';
|
|
const directUrl = router.params.url ? decodeURIComponent(router.params.url) : '';
|
|
|
|
const [camera, setCamera] = useState<Camera | null>(null);
|
|
const [videoUrl, setVideoUrl] = useState<string>(directUrl);
|
|
const [format, setFormat] = useState<'hls' | 'flv' | 'webrtc'>('hls');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (directUrl) {
|
|
setVideoUrl(resolveUrl(directUrl));
|
|
return;
|
|
}
|
|
|
|
const fetchCamera = async () => {
|
|
try {
|
|
const cameras = await getCameras().catch(() => [] as Camera[]);
|
|
const found = cameras.find((c) => c.id === cameraId);
|
|
if (found) {
|
|
setCamera(found);
|
|
}
|
|
} catch (err) {
|
|
console.error('[VideoPlayer] 获取摄像头信息失败:', err);
|
|
}
|
|
};
|
|
|
|
fetchCamera();
|
|
}, [cameraId, directUrl]);
|
|
|
|
const handlePlay = async () => {
|
|
const id = cameraId || camera?.id;
|
|
if (!id) return;
|
|
|
|
setLoading(true);
|
|
setError('');
|
|
try {
|
|
const info = await playCamera(id, { format: 'flv' });
|
|
const streamUrl = resolveUrl(info.url);
|
|
console.log('[VideoPlayer] 获取播放地址成功:', streamUrl);
|
|
setVideoUrl(streamUrl);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : '获取播放地址失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const formats: { key: 'hls' | 'flv' | 'webrtc'; label: string }[] = [
|
|
{ key: 'hls', label: 'HLS' },
|
|
{ key: 'flv', label: 'FLV' },
|
|
{ key: 'webrtc', label: 'WebRTC' },
|
|
];
|
|
|
|
const isLive = !directUrl;
|
|
|
|
return (
|
|
<View className={styles.playerPage}>
|
|
{/* 视频播放区域 */}
|
|
<View className={styles.videoContainer}>
|
|
{videoUrl ? (
|
|
<Video
|
|
className={styles.video}
|
|
src={videoUrl}
|
|
autoplay
|
|
controls
|
|
loop={!isLive}
|
|
muted={false}
|
|
showFullscreenBtn
|
|
showPlayBtn
|
|
showCenterPlayBtn
|
|
objectFit="contain"
|
|
onError={(e) => {
|
|
console.error('[VideoPlayer] 视频播放错误:', e);
|
|
setError('视频播放失败');
|
|
}}
|
|
/>
|
|
) : (
|
|
<View className={styles.videoPlaceholder}>
|
|
<Text className={styles.placeholderIcon}>📹</Text>
|
|
<Text className={styles.placeholderText}>
|
|
{loading ? '正在获取视频流...' : '点击下方按钮开始播放'}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{loading && videoUrl && (
|
|
<View className={styles.loadingOverlay}>
|
|
<Text className={styles.loadingText}>加载中...</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{/* 控制区域 */}
|
|
{isLive && (
|
|
<View className={styles.controls}>
|
|
<Text className={styles.cameraTitle}>{camera?.name || cameraName}</Text>
|
|
|
|
<View className={styles.formatSelector}>
|
|
{formats.map((f) => (
|
|
<View
|
|
key={f.key}
|
|
className={`${styles.formatChip} ${format === f.key ? styles.formatChipActive : ''}`}
|
|
onClick={() => setFormat(f.key)}
|
|
>
|
|
<Text>{f.label}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
|
|
<View
|
|
className={styles.playBtn}
|
|
onClick={handlePlay}
|
|
>
|
|
<Text className={styles.playBtnText}>
|
|
{loading ? '加载中...' : videoUrl ? '重新播放' : '开始播放'}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* 摄像头信息 */}
|
|
{camera && (
|
|
<View className={styles.infoSection}>
|
|
<Text className={styles.infoTitle}>设备信息</Text>
|
|
<View className={styles.infoRow}>
|
|
<Text className={styles.infoLabel}>编码</Text>
|
|
<Text className={styles.infoValue}>{camera.code}</Text>
|
|
</View>
|
|
<View className={styles.infoRow}>
|
|
<Text className={styles.infoLabel}>状态</Text>
|
|
<Text className={styles.infoValue}>{camera.isOnline ? '在线' : '离线'}</Text>
|
|
</View>
|
|
{camera.position && (
|
|
<View className={styles.infoRow}>
|
|
<Text className={styles.infoLabel}>位置</Text>
|
|
<Text className={styles.infoValue}>{camera.position}</Text>
|
|
</View>
|
|
)}
|
|
{camera.resolution && (
|
|
<View className={styles.infoRow}>
|
|
<Text className={styles.infoLabel}>分辨率</Text>
|
|
<Text className={styles.infoValue}>{camera.resolution}</Text>
|
|
</View>
|
|
)}
|
|
{camera.gbDeviceId && (
|
|
<View className={styles.infoRow}>
|
|
<Text className={styles.infoLabel}>国标ID</Text>
|
|
<Text className={styles.infoValue}>{camera.gbDeviceId}</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
{error && (
|
|
<View className={styles.errorWrap}>
|
|
<Text className={styles.errorIcon}>⚠️</Text>
|
|
<Text className={styles.errorText}>{error}</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default VideoPlayerPage;
|