107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { AnimatePresence, motion } from "framer-motion";
|
|
import {
|
|
ComponentProps,
|
|
forwardRef,
|
|
useEffect,
|
|
useImperativeHandle,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { VideoMutingBtn } from "./VideoMutingBtn";
|
|
import { VideoProgressBar } from "./VideoProgressBar";
|
|
|
|
export const VideoPlayer = forwardRef<
|
|
HTMLVideoElement,
|
|
{
|
|
src: string;
|
|
showMutingBtn: boolean;
|
|
children?: React.ReactNode;
|
|
} & ComponentProps<"video">
|
|
>(
|
|
(
|
|
{ src, showMutingBtn, children, loop = true, autoPlay = true, className },
|
|
ref
|
|
) => {
|
|
const progressbarRef = useRef<HTMLDivElement>(null);
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
|
|
useImperativeHandle(ref, () => videoRef.current!);
|
|
|
|
const [muted, setMuted] = useState(autoPlay);
|
|
const [playing, setPlaying] = useState(autoPlay);
|
|
const [progress, setProgress] = useState(0);
|
|
|
|
function handleProgressbarClick(e: React.MouseEvent) {
|
|
const video = videoRef.current;
|
|
const bar = progressbarRef.current;
|
|
if (!video || !bar) return;
|
|
video.currentTime =
|
|
(video.duration * (e.clientX - bar.getBoundingClientRect().x)) /
|
|
bar.clientWidth;
|
|
setProgress(
|
|
((video.currentTime ?? 0) / (video.duration ?? 1)) * 100
|
|
);
|
|
}
|
|
|
|
function handlePlaybackClick() {
|
|
if (!videoRef.current) return;
|
|
setPlaying(videoRef.current.paused);
|
|
videoRef.current[videoRef.current.paused ? "play" : "pause"]();
|
|
}
|
|
|
|
useEffect(() => {
|
|
const video = videoRef.current;
|
|
if (!video) return;
|
|
const timeUpdateHandler = () =>
|
|
setProgress(((video.currentTime ?? 0) / (video.duration ?? 1)) * 100);
|
|
|
|
video.addEventListener("timeupdate", timeUpdateHandler);
|
|
return () => video.removeEventListener("timeupdate", timeUpdateHandler);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative h-full">
|
|
<video
|
|
ref={videoRef}
|
|
src={src}
|
|
autoPlay={autoPlay}
|
|
muted={muted}
|
|
loop={loop}
|
|
playsInline
|
|
className={`lg:rounded-[1.111vw] rounded-2xl w-full h-full object-cover${
|
|
className ? " " + className : ""
|
|
}`}
|
|
/>
|
|
{showMutingBtn && (
|
|
<VideoMutingBtn
|
|
handleClick={() => setMuted(!videoRef.current!.muted)}
|
|
muted={muted}
|
|
/>
|
|
)}
|
|
<div className="absolute inset-0 rounded-2xl [background:linear-gradient(to_top,rgba(20,22,31,0.6),rgba(20,22,31,0))]" />
|
|
<AnimatePresence>
|
|
{muted && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
<VideoProgressBar
|
|
muted={muted}
|
|
progress={progress}
|
|
progressbarRef={progressbarRef}
|
|
playing={playing}
|
|
handlePlaybackClick={handlePlaybackClick}
|
|
handleProgressbarClick={handleProgressbarClick}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
VideoPlayer.displayName = "VideoPlayer";
|