|
|
@@ -1,18 +1,28 @@
|
|
|
|
|
|
import React, { useRef, useState, useEffect } from 'react';
|
|
|
import './style/index.less';
|
|
|
-import { Button } from 'antd';
|
|
|
+import { Button, Tag } from 'antd';
|
|
|
|
|
|
-interface VideoPlayerProps {
|
|
|
- syncPlay: boolean;
|
|
|
+interface VideoSource {
|
|
|
+ src: string;
|
|
|
+ label: string;
|
|
|
+ tagColor: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface VideoState {
|
|
|
+ top: VideoSource;
|
|
|
+ left: VideoSource;
|
|
|
+ right: VideoSource;
|
|
|
+}
|
|
|
+
|
|
|
+interface SyncVideoPlayerProps {
|
|
|
topVideoSrc: string;
|
|
|
leftVideoSrc: string;
|
|
|
rightVideoSrc: string;
|
|
|
audioSrc: string;
|
|
|
}
|
|
|
|
|
|
-const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
|
- syncPlay,
|
|
|
+const SyncVideoPlayer: React.FC<SyncVideoPlayerProps> = ({
|
|
|
topVideoSrc,
|
|
|
leftVideoSrc,
|
|
|
rightVideoSrc,
|
|
|
@@ -21,6 +31,31 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
|
const topVideoRef = useRef<HTMLVideoElement>(null);
|
|
|
const leftVideoRef = useRef<HTMLVideoElement>(null);
|
|
|
const rightVideoRef = useRef<HTMLVideoElement>(null);
|
|
|
+ const [videos, setVideos] = useState<{
|
|
|
+ top: VideoSource;
|
|
|
+ left: VideoSource;
|
|
|
+ right: VideoSource;
|
|
|
+ }>({
|
|
|
+ top: { src: topVideoSrc, label: '教室全景', tagColor: 'magenta' },
|
|
|
+ left: { src: leftVideoSrc, label: '教师特写', tagColor: 'blue' },
|
|
|
+ right: { src: rightVideoSrc, label: '课件板书', tagColor: 'green' }
|
|
|
+ });
|
|
|
+
|
|
|
+ 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
|
|
|
+ }));
|
|
|
+ if (position === 'left') {
|
|
|
+ leftVideoRef.current?.load();
|
|
|
+ } else {
|
|
|
+ rightVideoRef.current?.load();
|
|
|
+ }
|
|
|
+ topVideoRef.current?.load();
|
|
|
+ };
|
|
|
+
|
|
|
+
|
|
|
const audioRef = useRef<HTMLAudioElement>(null);
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
|
const [currentTime, setCurrentTime] = useState(0);
|
|
|
@@ -129,80 +164,86 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
|
|
|
|
return (
|
|
|
<div className={`video-container ${isFullscreen ? 'fullscreen' : ''}`}>
|
|
|
- <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
|
- <div>
|
|
|
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
|
+ <div className="video-wrapper">
|
|
|
<video
|
|
|
ref={topVideoRef}
|
|
|
- width='1150'
|
|
|
+ onEnded={togglePlay}
|
|
|
+ width={isFullscreen ? '1310' : '1150'}
|
|
|
height={isFullscreen ? '550' : '380'}
|
|
|
- style={{ objectFit: 'contain',backgroundColor: 'black' }}
|
|
|
- muted={syncPlay}
|
|
|
- controls={!syncPlay}
|
|
|
+ style={{ objectFit: 'contain',backgroundColor: 'grey' }}
|
|
|
+ muted
|
|
|
+ controls={false}
|
|
|
>
|
|
|
- <source src={topVideoSrc} type="video/mp4" />
|
|
|
+ <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 }}>
|
|
|
- <video
|
|
|
- ref={leftVideoRef}
|
|
|
- width={isFullscreen ? '650' : '570'}
|
|
|
- height={isFullscreen ? '480' : '240'}
|
|
|
- style={{ objectFit: 'contain',backgroundColor: 'black' }}
|
|
|
- muted={syncPlay}
|
|
|
- controls={!syncPlay}
|
|
|
- >
|
|
|
- <source src={leftVideoSrc} type="video/mp4" />
|
|
|
- </video>
|
|
|
- <video
|
|
|
- ref={rightVideoRef}
|
|
|
- width={isFullscreen ? '650' : '570'}
|
|
|
- height={isFullscreen ? '480' : '240'}
|
|
|
- style={{ objectFit: 'contain',backgroundColor: 'black' }}
|
|
|
- muted={syncPlay}
|
|
|
- controls={!syncPlay}
|
|
|
- >
|
|
|
- <source src={rightVideoSrc} type="video/mp4" />
|
|
|
- </video>
|
|
|
+ {['left', 'right'].map(pos => (
|
|
|
+ <div key={pos} className="video-wrapper">
|
|
|
+ <video
|
|
|
+ ref={pos === 'left' ? leftVideoRef : rightVideoRef}
|
|
|
+ width={isFullscreen ? '650' : '570'}
|
|
|
+ height={isFullscreen ? '420' : '240'}
|
|
|
+ style={{ objectFit: 'contain', backgroundColor: 'grey' }}
|
|
|
+ muted
|
|
|
+ controls={false}
|
|
|
+ >
|
|
|
+ <source src={videos[pos].src} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ <div className="video-tag">
|
|
|
+ <Tag color={videos[pos].tagColor}>
|
|
|
+ {videos[pos].label}
|
|
|
+ </Tag>
|
|
|
+ <Tag
|
|
|
+ color="orange"
|
|
|
+ style={{ cursor: 'pointer' }}
|
|
|
+ onClick={() => handleSwitch(pos)}
|
|
|
+ >
|
|
|
+ 主屏显示
|
|
|
+ </Tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<audio ref={audioRef} src={audioSrc} />
|
|
|
|
|
|
{/* 自定义控制器 */}
|
|
|
- {syncPlay && (
|
|
|
- <div className="video-controls">
|
|
|
- <Button onClick={togglePlay}>
|
|
|
- {isPlaying ? '❚❚' : '▶'}
|
|
|
- </Button>
|
|
|
- <input
|
|
|
- type="range"
|
|
|
- min="0"
|
|
|
- max="100"
|
|
|
- value={duration ? (currentTime / duration) * 100 : 0}
|
|
|
- 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 className="video-controls">
|
|
|
+ <Button onClick={togglePlay}>
|
|
|
+ {isPlaying ? '❚❚' : '▶'}
|
|
|
+ </Button>
|
|
|
+ <input
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max="100"
|
|
|
+ value={duration ? (currentTime / duration) * 100 : 0}
|
|
|
+ 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>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default VideoPlayer;
|
|
|
+export default SyncVideoPlayer;
|