infoalloggi-monorepo/apps/infoalloggi/src/components/videoPlayer.tsx

112 lines
2.7 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
"use client";
import { AnimatePresence, motion, useInView } from "framer-motion";
2025-08-04 17:45:44 +02:00
import { Play } from "lucide-react";
2025-08-28 18:27:07 +02:00
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
2025-08-04 17:45:44 +02:00
import { cn } from "~/lib/utils";
interface VideoPlayerProps {
2025-08-28 18:27:07 +02:00
coverImage: string;
videoSrc: string;
title?: string;
aspectRatio?: "square" | "video" | "vertical";
className?: string;
cacheKey: string;
2025-08-04 17:45:44 +02:00
}
export function VideoPlayer({
2025-08-28 18:27:07 +02:00
coverImage,
videoSrc,
title,
aspectRatio = "video",
className,
cacheKey,
2025-08-04 17:45:44 +02:00
}: VideoPlayerProps) {
2025-08-28 18:27:07 +02:00
const [isPlaying, setIsPlaying] = useState(false);
const videoRef = useRef<HTMLDivElement>(null);
const inView = useInView(videoRef);
useEffect(() => {
if (!inView) {
setIsPlaying(false);
}
}, [inView]);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const aspectRatioClasses = {
square: "aspect-square",
vertical: "aspect-[9/16]",
2025-08-29 16:18:32 +02:00
video: "aspect-video",
2025-08-28 18:27:07 +02:00
};
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<div
className={cn(
"relative w-full overflow-hidden rounded-lg",
aspectRatioClasses[aspectRatio],
className,
)}
ref={videoRef}
2025-08-28 18:27:07 +02:00
>
<AnimatePresence mode="wait">
{!isPlaying ? (
<motion.div
animate={{ opacity: 1 }}
className="relative h-full w-full cursor-pointer"
2025-08-29 16:18:32 +02:00
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
key="cover"
2025-08-28 18:27:07 +02:00
onClick={() => setIsPlaying(true)}
2025-08-29 16:18:32 +02:00
transition={{ duration: 0.3 }}
2025-08-28 18:27:07 +02:00
>
<Image
alt={title || "Video thumbnail"}
className="object-cover"
2025-08-29 16:18:32 +02:00
fill
2025-08-28 18:27:07 +02:00
priority
src={`${coverImage}?${cacheKey}` || "/fallback-video.png"}
2025-08-28 18:27:07 +02:00
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
<motion.div
2025-10-10 16:18:43 +02:00
className="flex size-16 items-center justify-center rounded-full bg-primary/90 text-primary-foreground shadow-lg"
2025-08-28 18:27:07 +02:00
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
>
<Play className="size-8" />
</motion.div>
</div>
{title && (
<div className="absolute right-0 bottom-0 left-0 bg-gradient-to-t from-black/80 to-transparent p-4">
2025-10-10 16:18:43 +02:00
<h3 className="font-medium text-lg text-white">{title}</h3>
2025-08-28 18:27:07 +02:00
</div>
)}
</motion.div>
) : (
<motion.div
animate={{ opacity: 1 }}
2025-08-29 16:18:32 +02:00
className="h-full w-full"
2025-08-28 18:27:07 +02:00
exit={{ opacity: 0 }}
2025-08-29 16:18:32 +02:00
initial={{ opacity: 0 }}
key="video"
2025-08-28 18:27:07 +02:00
transition={{ duration: 0.3 }}
>
<video
2025-08-29 16:18:32 +02:00
autoPlay
2025-08-28 18:27:07 +02:00
className="h-full w-full rounded-md"
controls
controlsList="play nodownload"
disablePictureInPicture
disableRemotePlayback
muted
onEnded={() => setIsPlaying(false)}
src={`${videoSrc}?${cacheKey}`}
2025-08-28 18:27:07 +02:00
>
Your browser does not support the video tag.
</video>
</motion.div>
)}
</AnimatePresence>
</div>
);
2025-08-04 17:45:44 +02:00
}