|
|
@@ -7,6 +7,7 @@ interface VideoSource {
|
|
|
src: string;
|
|
|
label: string;
|
|
|
tagColor: string;
|
|
|
+ pos: string;
|
|
|
}
|
|
|
|
|
|
interface VideoState {
|
|
|
@@ -33,37 +34,68 @@ const SyncVideoPlayer: React.FC<SyncVideoPlayerProps> = ({
|
|
|
const rightVideoRef = useRef<HTMLVideoElement>(null);
|
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
|
const [videos, setVideos] = useState<{
|
|
|
- top: VideoSource;
|
|
|
- left: VideoSource;
|
|
|
- right: VideoSource;
|
|
|
+ '1': VideoSource;
|
|
|
+ '2': VideoSource;
|
|
|
+ '3': VideoSource;
|
|
|
}>({
|
|
|
- top: { src: topVideoSrc, label: '教室全景', tagColor: 'magenta' },
|
|
|
- left: { src: leftVideoSrc, label: '教师特写', tagColor: 'blue' },
|
|
|
- right: { src: rightVideoSrc, label: '课件板书', tagColor: 'green' }
|
|
|
+ '1': { src: topVideoSrc, label: '教师特写', tagColor: 'magenta', pos: 'top' },
|
|
|
+ '2': { src: leftVideoSrc, label: '课件板书', tagColor: 'blue', pos: 'left' },
|
|
|
+ '3': { src: rightVideoSrc, label: '教室全景', tagColor: 'green', pos: 'right' }
|
|
|
});
|
|
|
|
|
|
useEffect(()=> {
|
|
|
|
|
|
setVideos({
|
|
|
...videos,
|
|
|
- top: { src: topVideoSrc, label: '教室全景', tagColor: 'magenta' },
|
|
|
- left: { src: leftVideoSrc, label: '教师特写', tagColor: 'blue' },
|
|
|
- right: { src: rightVideoSrc, label: '课件板书', tagColor: 'green' }
|
|
|
+ '1': { src: topVideoSrc, label: '教师特写', tagColor: 'magenta' , pos: 'top'},
|
|
|
+ '2': { src: leftVideoSrc, label: '课件板书', tagColor: 'blue' , pos: 'left'},
|
|
|
+ '3': { src: rightVideoSrc, label: '教室全景', tagColor: 'green', pos: 'right' }
|
|
|
})
|
|
|
}, [topVideoSrc, leftVideoSrc, rightVideoSrc])
|
|
|
|
|
|
- useEffect(()=> {
|
|
|
- leftVideoRef.current?.load();
|
|
|
- rightVideoRef.current?.load();
|
|
|
- topVideoRef.current?.load();
|
|
|
- }, [videos])
|
|
|
-
|
|
|
- const handleSwitch = (position: 'left' | 'right') => {
|
|
|
- setVideos((prev: VideoState) => ({
|
|
|
- top: prev[position],
|
|
|
- left: position === 'left' ? prev.top : prev.left,
|
|
|
- right: position === 'right' ? prev.top : prev.right
|
|
|
- }));
|
|
|
+ // useEffect(()=> {
|
|
|
+ // console.log('load???');
|
|
|
+
|
|
|
+ // leftVideoRef.current?.load();
|
|
|
+ // rightVideoRef.current?.load();
|
|
|
+ // topVideoRef.current?.load();
|
|
|
+ // }, [videos])
|
|
|
+
|
|
|
+ const handleSwitch = (vKey: any, currentPos: any) => {
|
|
|
+
|
|
|
+ Object.keys(videos).forEach(v=> {
|
|
|
+ if(v === '1') {
|
|
|
+ const vItem = videos[1];
|
|
|
+ if(vItem.pos === 'top') {
|
|
|
+ videos[1].pos = currentPos;
|
|
|
+ }
|
|
|
+ } else if(v === '2') {
|
|
|
+ const vItem = videos[2];
|
|
|
+ if(vItem.pos === 'top') {
|
|
|
+ videos[2].pos = currentPos;
|
|
|
+ }
|
|
|
+ } else if(v === '3') {
|
|
|
+ const vItem = videos[3];
|
|
|
+ if(vItem.pos === 'top') {
|
|
|
+ videos[3].pos = currentPos;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if(vKey === '1') {
|
|
|
+ videos[1].pos = 'top'
|
|
|
+ }
|
|
|
+ if(vKey === '2') {
|
|
|
+ videos[2].pos = 'top'
|
|
|
+ }
|
|
|
+ if(vKey === '3') {
|
|
|
+ videos[3].pos = 'top'
|
|
|
+ }
|
|
|
+ console.log(videos,'???');
|
|
|
+
|
|
|
+ setVideos({
|
|
|
+ ...videos
|
|
|
+ })
|
|
|
};
|
|
|
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
@@ -402,95 +434,89 @@ const SyncVideoPlayer: React.FC<SyncVideoPlayerProps> = ({
|
|
|
|
|
|
return (
|
|
|
<div className={`video-container ${isFullscreen ? 'fullscreen' : ''}`}>
|
|
|
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
|
|
|
- <div className="video-wrapper">
|
|
|
- <video
|
|
|
- ref={topVideoRef}
|
|
|
+ <div className={`sync-play-wrapper${isFullscreen?'-full':''}`} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center'}}>
|
|
|
+ {
|
|
|
+ Object.keys(videos).map((vKey: string)=> {
|
|
|
+ const vItem = vKey === '1' ? videos[1] : (vKey === '2'? videos[2]:videos[3]);
|
|
|
+ const vRef = vKey === '1' ? topVideoRef : (vKey === '2'? leftVideoRef: rightVideoRef);
|
|
|
+ return (
|
|
|
+ <div key={`v-pos-${vKey}`} className={`video-wrapper sync-play${isFullscreen?'-full':''}-${vItem.pos}`}>
|
|
|
+ <video
|
|
|
+ preload="auto"
|
|
|
+ ref={vRef}
|
|
|
+ style={{ objectFit: 'contain', backgroundColor: '#000', width: '100%', height: '100%'}}
|
|
|
+ muted={vKey !== '1'}
|
|
|
+ controls={false}
|
|
|
+ disablePictureInPicture
|
|
|
+ >
|
|
|
+ <source src={vItem.src} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ <div className="video-tag">
|
|
|
+ <Tag color={vItem.tagColor}>
|
|
|
+ {vItem.label}
|
|
|
+ </Tag>
|
|
|
+ { (vItem.pos === 'left' || vItem.pos === 'right') && (
|
|
|
+ <Tag
|
|
|
+ color="orange"
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ onClick={() => handleSwitch(vKey, vItem.pos)}
|
|
|
+ >
|
|
|
+ 主屏显示
|
|
|
+ </Tag>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- onEnded={togglePlay}
|
|
|
- style={{ objectFit: 'contain',backgroundColor: '#000', width:'100%', height: 'calc((100vh - (88px + 64px + 52px))/3 * 2)' }}
|
|
|
- controls={false}
|
|
|
- preload="auto"
|
|
|
- disablePictureInPicture
|
|
|
- >
|
|
|
- <source src={videos.top.src} type="video/mp4" />
|
|
|
- </video>
|
|
|
- <Tag className="video-tag top-tag" color={videos.top.tagColor}>{videos.top.label}</Tag>
|
|
|
- </div>
|
|
|
- <div style={{ display: 'flex', justifyContent: 'center', gap: 10, width: '100%' }}>
|
|
|
- {['left', 'right'].map((pos, index) => (
|
|
|
- <div key={pos} className="video-wrapper">
|
|
|
- <video
|
|
|
- preload="auto"
|
|
|
- ref={pos === 'left' ? leftVideoRef : rightVideoRef}
|
|
|
- // width={isFullscreen ? '700' : '600'}
|
|
|
- style={{ objectFit: 'contain', backgroundColor: '#000', width: '100%',height: 'calc((100vh - (88px + 64px + 52px))/3)' }}
|
|
|
- muted
|
|
|
- controls={false}
|
|
|
- disablePictureInPicture
|
|
|
- >
|
|
|
- <source src={videos[pos].src} type="video/mp4" />
|
|
|
- </video>
|
|
|
- <div className="video-tag">
|
|
|
- <Tag color={videos[pos].tagColor}>
|
|
|
- {videos[pos].label}
|
|
|
- </Tag>
|
|
|
- { !isPlaying && (
|
|
|
- <Tag
|
|
|
- color="orange"
|
|
|
- style={{ cursor: 'pointer' }}
|
|
|
- onClick={() => handleSwitch(pos)}
|
|
|
- >
|
|
|
- 主屏显示
|
|
|
- </Tag>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
+ {/* <div style={{ display: 'flex', justifyContent: 'center', gap: 10, width: '100%' }}>
|
|
|
+
|
|
|
+ </div> */}
|
|
|
+ <div className="video-controls sync-play-control">
|
|
|
+ {
|
|
|
+ waiting ? (
|
|
|
+ <div className="play-button-loader"></div>
|
|
|
+ ) : (
|
|
|
+ <Button onClick={togglePlay}>
|
|
|
+ {
|
|
|
+ isPlaying ? '❚❚' : '▶'
|
|
|
+ }
|
|
|
+ </Button>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max={Math.floor(duration) || 100}
|
|
|
+ value={currentTime}
|
|
|
+ onChange={handleSeek}
|
|
|
+ onMouseDown={handleSeekMouseDown}
|
|
|
+ onMouseUp={handleSeekMouseUp}
|
|
|
+ className="progress"
|
|
|
+ />
|
|
|
+ <span className="time-display">
|
|
|
+ {formatTime(currentTime)} / {formatTime(duration)}
|
|
|
+ </span>
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max="1"
|
|
|
+ step="0.01"
|
|
|
+ value={volume}
|
|
|
+ onChange={handleVolumeChange}
|
|
|
+ className="volume"
|
|
|
+ />
|
|
|
+ <Button onClick={toggleFullscreen}>
|
|
|
+ {isFullscreen ? '⤢' : '⤡'}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
{/* <audio ref={audioRef} src={audioSrc} /> */}
|
|
|
|
|
|
{/* 自定义控制器 */}
|
|
|
- <div className="video-controls">
|
|
|
- {
|
|
|
- waiting ? (
|
|
|
- <div className="play-button-loader"></div>
|
|
|
- ) : (
|
|
|
- <Button onClick={togglePlay}>
|
|
|
- {
|
|
|
- isPlaying ? '❚❚' : '▶'
|
|
|
- }
|
|
|
- </Button>
|
|
|
- )
|
|
|
- }
|
|
|
- <input
|
|
|
- type="range"
|
|
|
- min="0"
|
|
|
- max={Math.floor(duration) || 100}
|
|
|
- value={currentTime}
|
|
|
- onChange={handleSeek}
|
|
|
- onMouseDown={handleSeekMouseDown}
|
|
|
- onMouseUp={handleSeekMouseUp}
|
|
|
- className="progress"
|
|
|
- />
|
|
|
- <span className="time-display">
|
|
|
- {formatTime(currentTime)} / {formatTime(duration)}
|
|
|
- </span>
|
|
|
- <input
|
|
|
- type="range"
|
|
|
- min="0"
|
|
|
- max="1"
|
|
|
- step="0.01"
|
|
|
- value={volume}
|
|
|
- onChange={handleVolumeChange}
|
|
|
- className="volume"
|
|
|
- />
|
|
|
- <Button onClick={toggleFullscreen}>
|
|
|
- {isFullscreen ? '⤢' : '⤡'}
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
+
|
|
|
</div>
|
|
|
);
|
|
|
};
|