"use client"; import React, { useState, useRef, useEffect, useCallback } from "react"; import { motion, AnimatePresence } from "motion/react"; import { Play, Pause, RotateCcw, RotateCw, Volume2, VolumeX, Maximize, Minimize, Download, Share2, X, Check, ChevronDown, ChevronUp, Sparkles, Loader2 } from "lucide-react"; interface VideoModalPlayerProps { isOpen: boolean; onClose: () => void; video: { id?: string; title?: string; doctor?: string; videoUrl?: string; thumbnail?: string; description?: string; quote?: string; } | null; } const PLAYBACK_RATES = [0.5, 1, 1.25, 1.5, 1.75, 2, 2.5]; export default function VideoModalPlayer({ isOpen, onClose, video }: VideoModalPlayerProps) { const videoRef = useRef(null); const containerRef = useRef(null); const rateMenuRef = useRef(null); const controlsTimeoutRef = useRef | null>(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [bufferedEnd, setBufferedEnd] = useState(0); const [volume, setVolume] = useState(1); const [isMuted, setIsMuted] = useState(false); const [playbackRate, setPlaybackRate] = useState(1); const [showRateMenu, setShowRateMenu] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [isBuffering, setIsBuffering] = useState(true); const [showControls, setShowControls] = useState(true); const [isExpandedDesc, setIsExpandedDesc] = useState(false); const [isCopied, setIsCopied] = useState(false); // Close rate menu when clicking outside useEffect(() => { function handleClickOutside(e: MouseEvent) { if (rateMenuRef.current && !rateMenuRef.current.contains(e.target as Node)) { setShowRateMenu(false); } } if (showRateMenu) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [showRateMenu]); // Reset / Restore states on open/close useEffect(() => { if (isOpen && video?.id) { const savedTime = localStorage.getItem(`video_time_${video.id}`); const initialTime = savedTime ? parseFloat(savedTime) : 0; setIsPlaying(true); setShowControls(true); setIsExpandedDesc(false); setShowRateMenu(false); setBufferedEnd(0); if (videoRef.current && initialTime > 0) { videoRef.current.currentTime = initialTime; setCurrentTime(initialTime); } } else if (!isOpen && video?.id) { if (currentTime > 0) { localStorage.setItem(`video_time_${video.id}`, currentTime.toString()); } setIsPlaying(false); setBufferedEnd(0); } }, [isOpen, video?.id]); // Save playback time periodically useEffect(() => { if (video?.id && currentTime > 0) { localStorage.setItem(`video_time_${video.id}`, currentTime.toString()); } }, [currentTime, video?.id]); // Update buffered track const updateBuffer = useCallback(() => { if (videoRef.current && videoRef.current.buffered.length > 0) { const current = videoRef.current.currentTime; for (let i = 0; i < videoRef.current.buffered.length; i++) { if ( videoRef.current.buffered.start(i) <= current && videoRef.current.buffered.end(i) >= current ) { setBufferedEnd(videoRef.current.buffered.end(i)); break; } } } }, []); // Handle auto-hide timer for controls const resetControlsTimer = useCallback(() => { setShowControls(true); if (controlsTimeoutRef.current) { clearTimeout(controlsTimeoutRef.current); } // If video is playing, auto-hide after 3.5s; if paused, stay visible if (isPlaying) { controlsTimeoutRef.current = setTimeout(() => { if (!showRateMenu) { setShowControls(false); } }, 3500); } }, [isPlaying, showRateMenu]); useEffect(() => { if (!isPlaying) { setShowControls(true); if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current); } else { resetControlsTimer(); } return () => { if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current); }; }, [isPlaying, resetControlsTimer]); const togglePlay = () => { if (!videoRef.current) return; if (videoRef.current.paused) { videoRef.current.play().catch(console.error); setIsPlaying(true); } else { videoRef.current.pause(); setIsPlaying(false); setShowControls(true); } }; const handleSeek = (e: React.ChangeEvent) => { const time = Number(e.target.value); if (videoRef.current) { videoRef.current.currentTime = time; setCurrentTime(time); } }; const skipTime = (seconds: number) => { if (videoRef.current) { videoRef.current.currentTime = Math.max(0, Math.min(videoRef.current.duration || 0, videoRef.current.currentTime + seconds)); resetControlsTimer(); } }; const handleRateChange = (rate: number) => { if (videoRef.current) { videoRef.current.playbackRate = rate; setPlaybackRate(rate); setShowRateMenu(false); resetControlsTimer(); } }; const toggleMute = () => { if (videoRef.current) { videoRef.current.muted = !isMuted; setIsMuted(!isMuted); } }; const handleVolumeChange = (e: React.ChangeEvent) => { const val = Number(e.target.value); setVolume(val); if (videoRef.current) { videoRef.current.volume = val; if (val === 0) { setIsMuted(true); videoRef.current.muted = true; } else { setIsMuted(false); videoRef.current.muted = false; } } }; const toggleFullscreen = async () => { if (!containerRef.current) return; try { if (!document.fullscreenElement) { await containerRef.current.requestFullscreen(); setIsFullscreen(true); } else { await document.exitFullscreen(); setIsFullscreen(false); } } catch (err) { console.error('Fullscreen toggle failed:', err); } }; const handleShare = async () => { const shareUrl = window.location.href; if (navigator.share) { try { await navigator.share({ title: video?.title || 'ویدئوی راهنمای کنینا', text: video?.description || '', url: shareUrl }); } catch { // Fallback to clipboard } } else { try { await navigator.clipboard.writeText(shareUrl); setIsCopied(true); setTimeout(() => setIsCopied(false), 2500); } catch (err) { console.error('Copy failed:', err); } } }; const handleDownload = () => { if (!video?.videoUrl) return; const a = document.createElement('a'); a.href = video.videoUrl; a.download = `${video.title || 'canina-video'}.mp4`; a.target = '_blank'; a.rel = 'noopener noreferrer'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }; const formatTime = (secs: number) => { if (isNaN(secs) || secs < 0) return "00:00"; const mins = Math.floor(secs / 60); const remainingSecs = Math.floor(secs % 60); return `${mins.toString().padStart(2, "0")}:${remainingSecs.toString().padStart(2, "0")}`; }; if (!isOpen || !video) return null; const isIframe = video.videoUrl?.includes(" 0 ? (currentTime / duration) * 100 : 0; const bufferedPercent = duration > 0 ? (bufferedEnd / duration) * 100 : 0; const volumePercent = isMuted ? 0 : volume * 100; return (
{/* Backdrop (backdrop dismiss disabled to prevent accidental closure) */} {/* Modal Window Wrapper */}
{/* Iframe Support (Aparat / Youtube) */} {isIframe ? (
) : ( <> {/* Native Video Element */}
{/* Top Header Overlay */}

{video.title}

{video.doctor || "کادر علمی کنینا"}

{/* Share button */} {/* Download button */} {/* Close button */}
{/* Bottom Custom Controls Bar */}
e.stopPropagation()} > {/* Progress / Seek Bar with filled progress & buffer tracks */}
{formatTime(currentTime)}
{/* Background & Buffer & Progress Track */}
{/* Buffered bar */}
{/* Played progress bar */}
{/* Visual Scrubber Thumb */}
{formatTime(duration)}
{/* Action Buttons Row */}
{/* Left Controls (Playback & Jumps with - on Left and + on Right in RTL) */}
{/* Play/Pause */} {/* -10s button */} {/* +10s button */} {/* Volume Slider: Increases towards the speaker icon */}
{/* Right Controls (Speed Menu & Fullscreen) */}
{/* Playback Rate Popup Menu with click-outside */}
{showRateMenu && (
{PLAYBACK_RATES.map((rate) => ( ))}
)}
{/* Fullscreen Button */}
)} {/* Video Description Box (Rendered outside & below the video player for clean viewing) */} {video.description && (
توضیحات و نکات علمی ویدئو: {video.description.length > 120 && ( )}

{video.description}

)}
); }