|
|
@@ -0,0 +1,117 @@
|
|
|
+
|
|
|
+import React, { useRef, useState, useEffect } from 'react';
|
|
|
+import { Button } from 'antd';
|
|
|
+
|
|
|
+interface VideoSyncPlayerProps {
|
|
|
+ videoUrls: string[];
|
|
|
+}
|
|
|
+
|
|
|
+const VideoSyncPlayer: React.FC<VideoSyncPlayerProps> = ({ videoUrls }) => {
|
|
|
+ const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
|
|
|
+ const [progress, setProgress] = useState(0);
|
|
|
+ const [isPlaying, setIsPlaying] = useState(false);
|
|
|
+ const [duration, setDuration] = useState(0);
|
|
|
+
|
|
|
+ // 同步所有视频状态
|
|
|
+ const syncVideos = () => {
|
|
|
+ const mainVideo = videoRefs.current[0];
|
|
|
+ if (!mainVideo) return;
|
|
|
+
|
|
|
+ const currentTime = mainVideo.currentTime;
|
|
|
+ setProgress((currentTime / duration) * 100);
|
|
|
+
|
|
|
+ videoRefs.current.forEach(video => {
|
|
|
+ if (video && video !== mainVideo) {
|
|
|
+ video.currentTime = currentTime;
|
|
|
+ if (isPlaying) {
|
|
|
+ video.play()
|
|
|
+ } else {
|
|
|
+ video.pause()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理播放/暂停
|
|
|
+ const togglePlay = () => {
|
|
|
+ setIsPlaying(!isPlaying);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理进度条拖动
|
|
|
+ const handleSeek = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
+ const seekTime = (Number(e.target.value) / 100) * duration;
|
|
|
+ videoRefs.current.forEach(video => {
|
|
|
+ if (video) video.currentTime = seekTime;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化视频设置
|
|
|
+ useEffect(() => {
|
|
|
+ const mainVideo = videoRefs.current[0];
|
|
|
+ if (!mainVideo) return;
|
|
|
+
|
|
|
+ const handleLoadedMetadata = () => {
|
|
|
+ setDuration(mainVideo.duration);
|
|
|
+ if (videoRefs.current.every(v => v!.readyState > 2)) {
|
|
|
+ setIsPlaying(true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ mainVideo.addEventListener('loadedmetadata', handleLoadedMetadata);
|
|
|
+ return () => {
|
|
|
+ mainVideo.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
|
|
+ };
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
|
+ {/* 主视频区域 */}
|
|
|
+ <div>
|
|
|
+ <video
|
|
|
+ ref={el => videoRefs.current[0] = el}
|
|
|
+ width="1150"
|
|
|
+ height="475"
|
|
|
+ style={{ objectFit: 'cover' }}
|
|
|
+ muted
|
|
|
+ onTimeUpdate={syncVideos}
|
|
|
+ >
|
|
|
+ <source src={videoUrls[0]} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 副视频区域 */}
|
|
|
+ <div style={{ display: 'flex', justifyContent: 'center', gap: 25 }}>
|
|
|
+ {[1, 2].map(index => (
|
|
|
+ <video
|
|
|
+ key={index}
|
|
|
+ ref={el => videoRefs.current[index] = el}
|
|
|
+ width="565"
|
|
|
+ height="375"
|
|
|
+ style={{ objectFit: 'cover' }}
|
|
|
+ muted
|
|
|
+ onTimeUpdate={syncVideos}
|
|
|
+ >
|
|
|
+ <source src={videoUrls[index]} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 控制区域 */}
|
|
|
+ <div style={{ padding: 20, display: 'flex', flexDirection: 'column' }}>
|
|
|
+ <Button onClick={togglePlay} style={{ marginBottom: 10 }}>
|
|
|
+ {isPlaying ? '暂停' : '播放'}
|
|
|
+ </Button>
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max="100"
|
|
|
+ value={progress}
|
|
|
+ onChange={handleSeek}
|
|
|
+ style={{ width: '100%' }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default VideoSyncPlayer;
|