import React, { useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Play, Pause, Volume2, VolumeX, Maximize, Settings, PictureInPicture } from 'lucide-react'; import { Slider } from '../../ui/slider'; import { Button } from '../../ui/button'; import { FloatingMediaWrapper } from '../FloatingMediaWrapper'; import { cn } from '../../shared/utils'; export interface VideoPlayerProps { src: string; poster?: string; title?: string; autoPlay?: boolean; enableAutoFloat?: boolean; className?: string; } export function VideoPlayer({ src, poster, title, autoPlay = false, enableAutoFloat = true, className, }: VideoPlayerProps) { const { t } = useTranslation(); const videoRef = useRef(null); const containerRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [volume, setVolume] = useState(1); const [isMuted, setIsMuted] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [isFloating, setIsFloating] = useState(false); const [isManualFloating, setIsManualFloating] = useState(false); const [showControls, setShowControls] = useState(true); const [enableAutoFloatLocal, setEnableAutoFloatLocal] = useState(enableAutoFloat); let controlsTimeout: NodeJS.Timeout; // Wrapper for setIsFloating to handle scroll-on-restore and sync state const handleSetFloating = (floating: boolean) => { if (videoRef.current) { setCurrentTime(videoRef.current.currentTime); } setIsFloating(floating); if (!floating) { setIsManualFloating(false); } }; // Restore state when switching modes (remounting video element) useEffect(() => { const video = videoRef.current; if (video) { if (Math.abs(video.currentTime - currentTime) > 0.5) { video.currentTime = currentTime; } if (isPlaying) { const playPromise = video.play(); if (playPromise !== undefined) { playPromise.catch(error => { console.log('Playback interrupted during switch:', error); }); } } } }, [isFloating]); // Auto-float on scroll logic useEffect(() => { const container = containerRef.current; if (!container || !enableAutoFloatLocal) return; const observer = new IntersectionObserver( entries => { const entry = entries[0]; if (!entry) return; if (isPlaying && !entry.isIntersecting && !isFloating) { if (videoRef.current) setCurrentTime(videoRef.current.currentTime); setIsFloating(true); } else if (entry.isIntersecting && isFloating && !isManualFloating) { if (videoRef.current) setCurrentTime(videoRef.current.currentTime); handleSetFloating(false); } }, { threshold: 0.2 } ); observer.observe(container); return () => observer.disconnect(); }, [isPlaying, isFloating, isManualFloating, enableAutoFloatLocal]); // Video Event Listeners useEffect(() => { const video = videoRef.current; if (!video) return; const updateTime = () => { setCurrentTime(video.currentTime); }; const updateDuration = () => setDuration(video.duration); const onPlay = () => setIsPlaying(true); const onPause = () => setIsPlaying(false); video.addEventListener('timeupdate', updateTime); video.addEventListener('loadedmetadata', updateDuration); video.addEventListener('play', onPlay); video.addEventListener('pause', onPause); // Initial volume set video.volume = volume; video.muted = isMuted; return () => { video.removeEventListener('timeupdate', updateTime); video.removeEventListener('loadedmetadata', updateDuration); video.removeEventListener('play', onPlay); video.removeEventListener('pause', onPause); }; }, [isFloating]); // Re-attach when element changes const togglePlay = () => { if (videoRef.current) { if (isPlaying) { videoRef.current.pause(); } else { videoRef.current.play(); } } }; const handleSeek = (value: number[]) => { if (videoRef.current) { videoRef.current.currentTime = value[0]; setCurrentTime(value[0]); } }; const handleVolumeChange = (value: number[]) => { const newVolume = value[0]; if (videoRef.current) { videoRef.current.volume = newVolume; setVolume(newVolume); setIsMuted(newVolume === 0); } }; const toggleMute = () => { if (videoRef.current) { const newMutedState = !isMuted; videoRef.current.muted = newMutedState; setIsMuted(newMutedState); if (newMutedState) { setVolume(0); } else { setVolume(1); videoRef.current.volume = 1; } } }; const handleMouseMove = () => { setShowControls(true); clearTimeout(controlsTimeout); controlsTimeout = setTimeout(() => { if (isPlaying) setShowControls(false); }, 2500); }; const formatTime = (time: number) => { const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; }; return (
handleSetFloating(false)} onCloseMedia={() => { setIsFloating(false); setEnableAutoFloatLocal(false); }} title={title || t('media.untitledVideo')} aspectRatio={16 / 9} className="w-full h-full" playerId="video-player" >
isPlaying && setShowControls(false)} >
); }