import { useRef, useState, useEffect } from "react"; import { X, Download, Upload, Check, RotateCcw, Sparkles, Zap } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Slider } from "@/components/ui/slider"; import Hls from "hls.js"; interface ThumbnailGeneratorProps { videoUrl: string; videoTitle: string; isOpen: boolean; onClose: () => void; onThumbnailSelect: (thumbnailDataUrl: string) => void; } export default function ThumbnailGenerator({ videoUrl, videoTitle, isOpen, onClose, onThumbnailSelect }: ThumbnailGeneratorProps) { const videoRef = useRef(null); const canvasRef = useRef(null); const hlsRef = useRef(null); const fileInputRef = useRef(null); const [duration, setDuration] = useState(0); const [currentTime, setCurrentTime] = useState(0); const [isVideoLoaded, setIsVideoLoaded] = useState(false); const [selectedThumbnail, setSelectedThumbnail] = useState(null); const [customThumbnail, setCustomThumbnail] = useState(null); const [isGenerating, setIsGenerating] = useState(false); const [aiSuggestions, setAiSuggestions] = useState([]); const [isGeneratingAI, setIsGeneratingAI] = useState(false); // Initialize video when modal opens useEffect(() => { if (isOpen && videoRef.current && videoUrl) { initializeVideo(); } return () => { if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } }; }, [isOpen, videoUrl]); const initializeVideo = () => { const videoElement = videoRef.current; if (!videoElement) return; // Clean up previous HLS instance if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } if (videoUrl.includes('.m3u8')) { if (Hls.isSupported()) { const hls = new Hls({ debug: false, enableWorker: false, lowLatencyMode: false, // Optimized for thumbnail generation - prioritize speed startLevel: 0, // Start with lowest quality for faster loading maxBufferLength: 10, // Smaller buffer for thumbnail generation fragLoadingTimeOut: 10000, manifestLoadingTimeOut: 5000 }); hls.loadSource(videoUrl); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () => { console.log('Video loaded for thumbnail generation'); setIsVideoLoaded(true); }); hlsRef.current = hls; } else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) { videoElement.src = videoUrl; setIsVideoLoaded(true); } } else { videoElement.src = videoUrl; setIsVideoLoaded(true); } // Video event listeners videoElement.addEventListener('loadedmetadata', () => { setDuration(videoElement.duration); setCurrentTime(0); }); videoElement.addEventListener('timeupdate', () => { setCurrentTime(videoElement.currentTime); }); }; const generateThumbnail = async () => { const videoElement = videoRef.current; const canvas = canvasRef.current; if (!videoElement || !canvas) return; setIsGenerating(true); try { const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas dimensions to match video canvas.width = videoElement.videoWidth || 1280; canvas.height = videoElement.videoHeight || 720; // Draw current video frame to canvas ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); // Convert to data URL const thumbnailDataUrl = canvas.toDataURL('image/jpeg', 0.9); setSelectedThumbnail(thumbnailDataUrl); setCustomThumbnail(null); } catch (error) { console.error('Error generating thumbnail:', error); } finally { setIsGenerating(false); } }; const handleTimeSliderChange = (value: number[]) => { const newTime = value[0]; setCurrentTime(newTime); if (videoRef.current) { videoRef.current.currentTime = newTime; } }; const handleCustomImageUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { const result = e.target?.result as string; setCustomThumbnail(result); setSelectedThumbnail(null); }; reader.readAsDataURL(file); }; const handleSaveThumbnail = () => { const thumbnail = selectedThumbnail || customThumbnail; if (thumbnail) { onThumbnailSelect(thumbnail); onClose(); } }; const generateAIThumbnails = async () => { const videoElement = videoRef.current; const canvas = canvasRef.current; if (!videoElement || !canvas || duration === 0) return; setIsGeneratingAI(true); try { const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas dimensions canvas.width = videoElement.videoWidth || 1280; canvas.height = videoElement.videoHeight || 720; const suggestions: string[] = []; // Generate thumbnails at strategic points (10%, 35%, 65% of video) const timePoints = [ duration * 0.1, // 10% - early content duration * 0.35, // 35% - middle content duration * 0.65 // 65% - later content ]; for (const timePoint of timePoints) { // Seek to specific time videoElement.currentTime = timePoint; // Wait for video to seek to the correct time await new Promise((resolve) => { const onSeeked = () => { videoElement.removeEventListener('seeked', onSeeked); resolve(void 0); }; videoElement.addEventListener('seeked', onSeeked); }); // Small delay to ensure frame is rendered await new Promise(resolve => setTimeout(resolve, 100)); // Draw frame to canvas ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); // Convert to data URL const thumbnailDataUrl = canvas.toDataURL('image/jpeg', 0.9); suggestions.push(thumbnailDataUrl); } setAiSuggestions(suggestions); } catch (error) { console.error('Error generating AI thumbnails:', error); } finally { setIsGeneratingAI(false); } }; const selectAISuggestion = (suggestionUrl: string) => { setSelectedThumbnail(suggestionUrl); setCustomThumbnail(null); }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; if (!isOpen) return null; return (
{/* Header */}

Generate Thumbnail Image

{/* Video Player Section */}

Video Preview

{!isVideoLoaded && (
)}
{/* Time Slider */} {isVideoLoaded && duration > 0 && (
{formatTime(currentTime)} {formatTime(duration)}
)} {/* Generate Buttons */}
{/* Thumbnail Preview Section */}

Thumbnail Preview

{/* Thumbnail Preview */}
{selectedThumbnail || customThumbnail ? (
Thumbnail preview

{selectedThumbnail ? 'Generirano iz videoposnetka' : 'Naložena slika'}

) : (
📷

Generirajte thumbnail iz videoposnetka ali naložite svojo sliko

)}
{/* AI Suggestions */} {aiSuggestions.length > 0 && (

AI predlogi

{aiSuggestions.map((suggestion, index) => ( ))}
)} {/* Custom Upload */}

Ali naložite svojo sliko

{/* Action Buttons */}
{/* Hidden canvas for thumbnail generation */}
); }