diff --git a/src/components/LiveStreamPlayer.tsx b/src/components/LiveStreamPlayer.tsx index 7fd69bdd..a071c865 100644 --- a/src/components/LiveStreamPlayer.tsx +++ b/src/components/LiveStreamPlayer.tsx @@ -2,6 +2,7 @@ import { useRef, useEffect, useState, useCallback } from 'react'; import type Hls from 'hls.js'; import { Play, Pause, Volume1, Volume2, VolumeX, Expand, Minimize } from 'lucide-react'; import { cn } from '@/lib/utils'; +import { usePlayerControls } from '@/hooks/usePlayerControls'; interface LiveStreamPlayerProps { src: string; @@ -17,19 +18,19 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live const videoRef = useRef(null); const containerRef = useRef(null); const hlsRef = useRef(null); - const hideTimeoutRef = useRef>(); - - const prevVolumeRef = useRef(0.8); const [isPlaying, setIsPlaying] = useState(false); - const [isMuted, setIsMuted] = useState(true); - const [volume, setVolume] = useState(0.8); - const [showControls, setShowControls] = useState(true); const [isFullscreen, setIsFullscreen] = useState(false); const [isBuffering, setIsBuffering] = useState(true); const [hasError, setHasError] = useState(false); const [autoplayBlocked, setAutoplayBlocked] = useState(false); + const { showControls, revealControls, scheduleHide, isMuted, volume, toggleMute, handleVolumeChange } = usePlayerControls({ + mediaRef: videoRef, + containerRef, + isPlaying, + }); + // Set up HLS — dynamically imports hls.js (1.3MB) only when needed useEffect(() => { const video = videoRef.current; @@ -99,51 +100,6 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live }; }, [src]); - // Pause video when scrolled out of view - useEffect(() => { - const video = videoRef.current; - const container = containerRef.current; - if (!video || !container) return; - - const observer = new IntersectionObserver( - ([entry]) => { - if (!entry.isIntersecting && !video.paused) { - video.pause(); - } - }, - { threshold: 0.25 }, - ); - - observer.observe(container); - return () => observer.disconnect(); - }, []); - - // Track playback & buffering state - useEffect(() => { - const video = videoRef.current; - if (!video) return; - - const onWaiting = () => setIsBuffering(true); - const onPlaying = () => { setIsBuffering(false); setIsPlaying(true); }; - const onCanPlay = () => setIsBuffering(false); - const onPause = () => setIsPlaying(false); - const onPlay = () => setIsPlaying(true); - - video.addEventListener('waiting', onWaiting); - video.addEventListener('playing', onPlaying); - video.addEventListener('canplay', onCanPlay); - video.addEventListener('pause', onPause); - video.addEventListener('play', onPlay); - - return () => { - video.removeEventListener('waiting', onWaiting); - video.removeEventListener('playing', onPlaying); - video.removeEventListener('canplay', onCanPlay); - video.removeEventListener('pause', onPause); - video.removeEventListener('play', onPlay); - }; - }, []); - // Track fullscreen changes useEffect(() => { const onFullscreenChange = () => { @@ -187,30 +143,7 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused'; }, [isPlaying]); - const scheduleHide = useCallback(() => { - if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current); - if (!autoplayBlocked) { - hideTimeoutRef.current = setTimeout(() => setShowControls(false), 3000); - } - }, [autoplayBlocked]); - - const revealControls = useCallback(() => { - setShowControls(true); - scheduleHide(); - }, [scheduleHide]); - - useEffect(() => { - if (!autoplayBlocked) { - scheduleHide(); - } else { - setShowControls(true); - } - return () => { - if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current); - }; - }, [scheduleHide, autoplayBlocked]); - - const togglePlay = (e: React.MouseEvent) => { + const togglePlay = useCallback((e: React.MouseEvent) => { e.stopPropagation(); const video = videoRef.current; if (!video) return; @@ -219,38 +152,24 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live } else { video.pause(); } - }; + }, []); - const toggleMute = (e: React.MouseEvent) => { + const handleVideoClick = useCallback((e: React.MouseEvent) => { e.stopPropagation(); - const video = videoRef.current; - if (!video) return; - if (video.muted || video.volume === 0) { - const restored = prevVolumeRef.current > 0 ? prevVolumeRef.current : 0.5; - video.muted = false; - video.volume = restored; - setIsMuted(false); - setVolume(restored); - } else { - prevVolumeRef.current = video.volume; - video.muted = true; - setIsMuted(true); + if (autoplayBlocked) { + const video = videoRef.current; + if (!video) return; + video.play().then(() => { + setAutoplayBlocked(false); + setIsBuffering(false); + }).catch(() => {}); + return; } - }; + togglePlay(e); + revealControls(); + }, [autoplayBlocked, togglePlay, revealControls]); - const handleVolumeChange = (e: React.ChangeEvent) => { - e.stopPropagation(); - const video = videoRef.current; - if (!video) return; - const val = parseFloat(e.target.value); - video.volume = val; - video.muted = val === 0; - if (val > 0) prevVolumeRef.current = val; - setVolume(val); - setIsMuted(val === 0); - }; - - const toggleFullscreen = (e: React.MouseEvent) => { + const toggleFullscreen = useCallback((e: React.MouseEvent) => { e.stopPropagation(); const container = containerRef.current; if (!container) return; @@ -259,18 +178,7 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live } else { container.requestFullscreen(); } - }; - - const handleManualPlay = () => { - const video = videoRef.current; - if (!video) return; - video.play().then(() => { - setAutoplayBlocked(false); - setIsBuffering(false); - }).catch(() => { - // Still blocked - unlikely after user gesture - }); - }; + }, []); if (hasError) { return ( @@ -291,7 +199,8 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live className, )} onMouseMove={revealControls} - onMouseLeave={() => { if (!autoplayBlocked) scheduleHide(); }} + onMouseLeave={() => { if (isPlaying) scheduleHide(); }} + onClick={(e) => e.stopPropagation()} >