import { useState, useRef, useEffect } from "react"; import { Play } from "lucide-react"; import { type Video } from "@shared/schema"; interface VideoPreviewThumbnailProps { video: Video; onClick: (video: Video) => void; className?: string; } export default function VideoPreviewThumbnail({ video, onClick, className = "" }: VideoPreviewThumbnailProps) { const [isHovering, setIsHovering] = useState(false); const [previewTime, setPreviewTime] = useState(0); const [previewThumbnail, setPreviewThumbnail] = useState(null); const [isVideoLoaded, setIsVideoLoaded] = useState(false); const videoRef = useRef(null); const containerRef = useRef(null); const hoverTimeoutRef = useRef(); const previewIntervalRef = useRef(); // Create hidden video element for thumbnail generation useEffect(() => { if (!video.videoUrlMp4 && !video.videoUrl.includes('.mp4')) return; const videoElement = videoRef.current; if (!videoElement) return; const videoSrc = video.videoUrlMp4 || video.videoUrl; videoElement.src = videoSrc; videoElement.muted = true; videoElement.playsInline = true; videoElement.preload = 'metadata'; const handleLoadedMetadata = () => { setIsVideoLoaded(true); }; videoElement.addEventListener('loadedmetadata', handleLoadedMetadata); return () => { videoElement.removeEventListener('loadedmetadata', handleLoadedMetadata); }; }, [video.videoUrlMp4, video.videoUrl]); const generatePreviewThumbnail = (time: number) => { const videoElement = videoRef.current; if (!videoElement || !isVideoLoaded) return; videoElement.currentTime = time; const handleSeeked = () => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = videoElement.videoWidth; canvas.height = videoElement.videoHeight; ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); const thumbnailUrl = canvas.toDataURL('image/jpeg', 0.8); setPreviewThumbnail(thumbnailUrl); videoElement.removeEventListener('seeked', handleSeeked); }; videoElement.addEventListener('seeked', handleSeeked); }; const handleMouseMove = (e: React.MouseEvent) => { if (!isHovering || !containerRef.current || !isVideoLoaded) return; const rect = containerRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = Math.max(0, Math.min(1, x / rect.width)); const time = percentage * video.duration; setPreviewTime(time); // Throttle thumbnail generation if (previewIntervalRef.current) { clearTimeout(previewIntervalRef.current); } previewIntervalRef.current = setTimeout(() => { generatePreviewThumbnail(time); }, 150); }; const handleMouseEnter = () => { if (!isVideoLoaded) return; // Delay before showing preview to avoid flickering hoverTimeoutRef.current = setTimeout(() => { setIsHovering(true); generatePreviewThumbnail(0); }, 500); }; const handleMouseLeave = () => { if (hoverTimeoutRef.current) { clearTimeout(hoverTimeoutRef.current); } if (previewIntervalRef.current) { clearTimeout(previewIntervalRef.current); } setIsHovering(false); setPreviewThumbnail(null); setPreviewTime(0); }; const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; const formatDuration = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; return (
{/* Hidden video element for thumbnail generation */}