You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
5.1 KiB

import { FC, MutableRefObject, useEffect, useRef, useState } from "react";
import Box from "@mui/material/Box";
import { SxProps } from "@mui/material/styles";
import { AdaptiveFormat, Caption, FormatStream } from "@interfaces/video";
import { PausedBy, VideoStatus } from "@interfaces/videoPlayer";
import useSettings from "@utils/hooks/useSettings";
import useVideoState from "@utils/hooks/useVideoState";
import Actions from "@components/Player/Actions";
import ProgressBar from "@components/Player/ProgressBar";
const Player: FC<{
formats: AdaptiveFormat[];
streams: FormatStream[];
captions: Caption[];
length: number;
videoId: string;
sx: SxProps;
}> = ({ formats, length: duration, sx }) => {
const [settings] = useSettings();
const playing = useVideoState((state) => state.playing);
const togglePlaying = useVideoState((state) => state.togglePlaying);
const setPlaying = useVideoState((state) => state.setPlaying);
const speed = useVideoState((state) => state.speed);
const muted = useVideoState((state) => state.muted);
const error = useVideoState((state) => state.error);
const setError = useVideoState((state) => state.setError);
const waiting = useVideoState((state) => state.waiting);
const setWaiting = useVideoState((state) => state.setWaiting);
const setProgress = useVideoState((state) => state.setProgress);
const pausedBy = useVideoState((state) => state.pausedBy);
const videoStream = formats.find(
(format) => format.qualityLabel == "2160p" || format.qualityLabel == "1080p"
)?.url;
const audioStream = formats.find((format) =>
format.type.includes("audio/mp4")
)?.url as string;
const videoRef = useRef<HTMLVideoElement | null>(null);
const audioRef = useRef<HTMLAudioElement>(new Audio(audioStream));
useEffect(() => {
const audio = audioRef.current;
audio.volume = 0.25;
const video = videoRef.current;
if (!video) return;
video.playbackRate = speed;
video.currentTime = 0;
const handleError = (e: ErrorEvent) => {
setError(e.message || "An unknown error occurred");
setPlaying(VideoStatus.Paused, PausedBy.Player);
};
const handleWaiting = (e: Event) => {
setWaiting(true);
if (playing == VideoStatus.Playing)
setPlaying(VideoStatus.Paused, PausedBy.Player);
};
const handleFinishedWaiting = (e: Event) => {
setWaiting(false);
if (pausedBy == PausedBy.Player) setPlaying(VideoStatus.Playing);
};
const onTimeUpdate = () => {
setProgress(video.currentTime ?? 0);
};
const handlePause = () => {
setPlaying(VideoStatus.Paused);
};
if (!videoStream) setError("Could not find video stream");
video.addEventListener("waiting", handleWaiting);
video.addEventListener("canplaythrough", handleFinishedWaiting);
video.addEventListener("error", handleError);
video.addEventListener("pause", handlePause);
video.addEventListener("timeupdate", onTimeUpdate);
audio.addEventListener("waiting", handleWaiting);
audio.addEventListener("canplaythrough", handleFinishedWaiting);
audio.addEventListener("pause", handlePause);
return () => {
audio.srcObject = null;
video.removeEventListener("waiting", handleWaiting);
video.removeEventListener("canplaythrough", handleFinishedWaiting);
video.removeEventListener("error", handleError);
video.removeEventListener("pause", handlePause);
video.removeEventListener("timeupdate", onTimeUpdate);
audio.removeEventListener("waiting", handleWaiting);
audio.removeEventListener("canplaythrough", handleFinishedWaiting);
audio.removeEventListener("pause", handlePause);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setPlaying(settings.autoPlay ? VideoStatus.Playing : VideoStatus.Paused);
}, [setPlaying, settings.autoPlay]);
useEffect(() => {
if (!videoRef.current || !audioRef.current) return;
if (playing == VideoStatus.Paused) {
videoRef.current.pause();
audioRef.current.pause();
} else {
videoRef.current.play();
audioRef.current.play();
}
}, [error, playing, waiting]);
useEffect(() => {
if (!videoRef.current) return;
videoRef.current.playbackRate = speed;
}, [speed]);
useEffect(() => {
if (!audioRef.current) return;
audioRef.current.muted = muted;
}, [muted, audioRef]);
return (
<Box
sx={{
...sx,
maxWidth: "fit-content",
position: "relative"
}}
>
{error && (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
{error}
</Box>
)}
<video
src={videoStream}
ref={videoRef}
style={{
height: "100%"
}}
autoPlay={playing == VideoStatus.Playing}
>
Your browser does not support video playback.
</video>
<Box
onClick={() => togglePlaying(PausedBy.User)}
sx={{
boxShadow: "0px -15px 30px 0px rgba(0,0,0,0.75) inset",
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%"
}}
></Box>
<ProgressBar videoRef={videoRef} duration={duration} />
<Actions videoRef={videoRef} duration={duration} />
</Box>
);
};
export default Player;