training_data/apps/web/src/components/presentation/video-player/VideoDisplay.tsx

213 lines
5.6 KiB
TypeScript
Executable File

import React, { useContext, useEffect, useRef, useState } from "react";
import Hls from "hls.js";
import { VideoPlayerContext } from "./VideoPlayer";
interface VideoDisplayProps {
autoPlay?: boolean;
}
export const VideoDisplay: React.FC<VideoDisplayProps> = ({
autoPlay = false,
}) => {
const {
src,
poster,
onError,
videoRef,
setIsReady,
setIsPlaying,
setError,
setBufferingState,
isMuted,
setLoadingProgress,
setCurrentTime,
setDuration,
brightness,
isDragging,
setIsDragging,
progressRef,
} = useContext(VideoPlayerContext);
// 处理进度条拖拽
const handleProgressDrag = (e: MouseEvent) => {
if (!isDragging || !videoRef.current || !progressRef.current) return;
const rect = progressRef.current.getBoundingClientRect();
const percent = Math.max(
0,
Math.min(1, (e.clientX - rect.left) / rect.width)
);
videoRef.current.currentTime = percent * videoRef.current.duration;
};
// 添加拖拽事件监听
useEffect(() => {
const handleMouseUp = () => setIsDragging(false);
const handleMouseMove = (e: MouseEvent) => handleProgressDrag(e);
if (isDragging) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [isDragging]);
// 初始化 HLS 和事件监听
useEffect(() => {
let hls: Hls;
const initializeHls = async () => {
if (!videoRef.current) return;
// Reset states
setIsReady(false);
setError(null);
setLoadingProgress(0);
setBufferingState(false);
// Check for native HLS support (Safari)
if (videoRef.current.canPlayType("application/vnd.apple.mpegurl")) {
videoRef.current.src = src;
setIsReady(true);
// 设置视频时长
setDuration(videoRef.current.duration);
if (autoPlay) {
try {
await videoRef.current.play();
setIsPlaying(true);
} catch (error) {
console.log("Auto-play prevented:", error);
}
}
return;
}
if (!Hls.isSupported()) {
const errorMessage = "您的浏览器不支持 HLS 视频播放";
setError(errorMessage);
onError?.(errorMessage);
return;
}
hls = new Hls({
maxBufferLength: 30,
maxMaxBufferLength: 600,
enableWorker: true,
debug: false,
});
hls.loadSource(src);
hls.attachMedia(videoRef.current);
hls.on(Hls.Events.MANIFEST_PARSED, async () => {
setIsReady(true);
// 设置视频时长
setDuration(videoRef.current?.duration || 0);
if (autoPlay && videoRef.current) {
try {
await videoRef.current.play();
setIsPlaying(true);
} catch (error) {
console.log("Auto-play prevented:", error);
}
}
});
hls.on(Hls.Events.BUFFER_APPENDING, () => {
setBufferingState(true);
});
hls.on(Hls.Events.FRAG_BUFFERED, (_, data) => {
setBufferingState(false);
if (data.stats) {
const progress =
(data.stats.loaded / data.stats.total) * 100;
setLoadingProgress(Math.round(progress));
}
});
let fatalError;
let networkError;
hls.on(Hls.Events.ERROR, (_, data) => {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
networkError = `网络错误: ${data.details}`;
console.error(networkError);
setError(networkError);
onError?.(networkError);
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.error("Media error, attempting to recover");
setError("视频解码错误,尝试恢复...");
hls.recoverMediaError();
break;
default:
fatalError = `加载失败: ${data.details}`;
console.error(fatalError);
setError(fatalError);
onError?.(fatalError);
hls.destroy();
break;
}
}
});
};
// 事件处理
const handlePlay = () => setIsPlaying(true);
const handlePause = () => setIsPlaying(false);
const handleEnded = () => setIsPlaying(false);
const handleWaiting = () => setBufferingState(true);
const handlePlaying = () => setBufferingState(false);
const handleLoadedMetadata = () => {
if (videoRef.current) {
// 设置视频时长
setDuration(videoRef.current.duration);
}
};
if (videoRef.current) {
videoRef.current.addEventListener("play", handlePlay);
videoRef.current.addEventListener("pause", handlePause);
videoRef.current.addEventListener("ended", handleEnded);
videoRef.current.addEventListener("waiting", handleWaiting);
videoRef.current.addEventListener("playing", handlePlaying);
videoRef.current.addEventListener(
"loadedmetadata",
handleLoadedMetadata
);
}
initializeHls();
return () => {
if (videoRef.current) {
videoRef.current.removeEventListener("play", handlePlay);
videoRef.current.removeEventListener("pause", handlePause);
videoRef.current.removeEventListener("ended", handleEnded);
videoRef.current.removeEventListener("waiting", handleWaiting);
videoRef.current.removeEventListener("playing", handlePlaying);
videoRef.current.removeEventListener(
"loadedmetadata",
handleLoadedMetadata
);
}
if (hls) {
hls.destroy();
}
};
}, [src, onError, autoPlay]);
return (
<div className="relative w-full h-full">
<video
ref={videoRef}
className="w-full h-full"
poster={poster}
controls={false}
playsInline
muted={isMuted}
style={{ filter: `brightness(${brightness})` }}
onTimeUpdate={() => {
if (videoRef.current) {
setCurrentTime(videoRef.current.currentTime);
}
}}
/>
</div>
);
};