2025-08-04 17:45:44 +02:00
|
|
|
"use client";
|
|
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
import { AnimatePresence, motion } 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 { 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;
|
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,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: VideoPlayerProps) {
|
2025-08-28 18:27:07 +02:00
|
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const aspectRatioClasses = {
|
|
|
|
|
square: "aspect-square",
|
|
|
|
|
video: "aspect-video",
|
|
|
|
|
vertical: "aspect-[9/16]",
|
|
|
|
|
};
|
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,
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<AnimatePresence mode="wait">
|
|
|
|
|
{!isPlaying ? (
|
|
|
|
|
<motion.div
|
|
|
|
|
key="cover"
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
transition={{ duration: 0.3 }}
|
|
|
|
|
className="relative h-full w-full cursor-pointer"
|
|
|
|
|
onClick={() => setIsPlaying(true)}
|
|
|
|
|
>
|
|
|
|
|
<Image
|
|
|
|
|
src={coverImage || "/fallback-video.png"}
|
|
|
|
|
alt={title || "Video thumbnail"}
|
|
|
|
|
fill
|
|
|
|
|
className="object-cover"
|
|
|
|
|
priority
|
|
|
|
|
/>
|
|
|
|
|
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
|
|
|
|
<motion.div
|
|
|
|
|
whileHover={{ scale: 1.1 }}
|
|
|
|
|
whileTap={{ scale: 0.95 }}
|
|
|
|
|
className="bg-primary/90 text-primary-foreground flex size-16 items-center justify-center rounded-full shadow-lg"
|
|
|
|
|
>
|
|
|
|
|
<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">
|
|
|
|
|
<h3 className="text-lg font-medium text-white">{title}</h3>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</motion.div>
|
|
|
|
|
) : (
|
|
|
|
|
<motion.div
|
|
|
|
|
key="video"
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
transition={{ duration: 0.3 }}
|
|
|
|
|
className="h-full w-full"
|
|
|
|
|
>
|
|
|
|
|
<video
|
|
|
|
|
src={videoSrc}
|
|
|
|
|
className="h-full w-full rounded-md"
|
|
|
|
|
controls
|
|
|
|
|
autoPlay
|
|
|
|
|
controlsList="play nodownload"
|
|
|
|
|
disablePictureInPicture
|
|
|
|
|
disableRemotePlayback
|
|
|
|
|
muted
|
|
|
|
|
onEnded={() => setIsPlaying(false)}
|
|
|
|
|
>
|
|
|
|
|
Your browser does not support the video tag.
|
|
|
|
|
</video>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
}
|