Files
stream-demo-standalone/src/ui/VideoPlayer.tsx
T
2026-04-10 17:16:13 +05:00

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