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
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;
|