diff --git a/components/player/VideoPlayer.tsx b/components/player/VideoPlayer.tsx index 4e22d0d..1c84b21 100644 --- a/components/player/VideoPlayer.tsx +++ b/components/player/VideoPlayer.tsx @@ -34,11 +34,14 @@ export function VideoPlayer({ playUrl, videoId, currentEpisode, onBack }: VideoP if (!videoId) return 0; // Directly check HistoryStore for progress - // This is the single source of truth for playback resumption + // We prioritize a strict match (including source), but fall back to any match for this video/episode + // This fixes issues where the source parameter might be missing or different const historyItem = viewingHistory.find(item => - // Loose match for videoId (string vs number) item.videoId.toString() === videoId?.toString() && - item.source === source && + item.episodeIndex === currentEpisode && + (source ? item.source === source : true) + ) || viewingHistory.find(item => + item.videoId.toString() === videoId?.toString() && item.episodeIndex === currentEpisode ); diff --git a/components/player/hooks/desktop/usePlaybackControls.ts b/components/player/hooks/desktop/usePlaybackControls.ts index 2f5e74d..a182517 100644 --- a/components/player/hooks/desktop/usePlaybackControls.ts +++ b/components/player/hooks/desktop/usePlaybackControls.ts @@ -1,4 +1,6 @@ import { useCallback, useEffect } from 'react'; +import { formatTime } from '@/lib/utils/format-utils'; +import { usePlaybackPolling } from '../usePlaybackPolling'; interface UsePlaybackControlsProps { videoRef: React.RefObject; @@ -112,13 +114,14 @@ export function usePlaybackControls({ } }, [videoRef, setPlaybackRate, setShowSpeedMenu, speedMenuTimeoutRef]); - const formatTime = useCallback((seconds: number) => { - if (isNaN(seconds)) return '0:00:00'; - const hours = Math.floor(seconds / 3600); - const mins = Math.floor((seconds % 3600) / 60); - const secs = Math.floor(seconds % 60); - return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }, []); + // Polling fallback for AirPlay and throttled events + usePlaybackPolling({ + isPlaying, + videoRef, + isDraggingProgressRef, + setCurrentTime, + setDuration + }); return { togglePlay, diff --git a/components/player/hooks/mobile/useMobilePlaybackControls.ts b/components/player/hooks/mobile/useMobilePlaybackControls.ts index 6e2c201..fa83734 100644 --- a/components/player/hooks/mobile/useMobilePlaybackControls.ts +++ b/components/player/hooks/mobile/useMobilePlaybackControls.ts @@ -1,4 +1,6 @@ import { useCallback, useEffect } from 'react'; +import { formatTime } from '@/lib/utils/format-utils'; +import { usePlaybackPolling } from '../usePlaybackPolling'; interface UseMobilePlaybackProps { videoRef: React.RefObject; @@ -124,13 +126,14 @@ export function useMobilePlaybackControls({ setShowSpeedMenu(false); }, [videoRef, setPlaybackRate, setShowSpeedMenu]); - const formatTime = useCallback((seconds: number) => { - if (isNaN(seconds)) return '0:00:00'; - const hours = Math.floor(seconds / 3600); - const mins = Math.floor((seconds % 3600) / 60); - const secs = Math.floor(seconds % 60); - return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; - }, []); + // Polling fallback for AirPlay and throttled events + usePlaybackPolling({ + isPlaying, + videoRef, + isDraggingProgressRef, + setCurrentTime, + setDuration + }); return { togglePlay, diff --git a/components/player/hooks/usePlaybackPolling.ts b/components/player/hooks/usePlaybackPolling.ts new file mode 100644 index 0000000..b40fffd --- /dev/null +++ b/components/player/hooks/usePlaybackPolling.ts @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; + +interface UsePlaybackPollingProps { + isPlaying: boolean; + videoRef: React.RefObject; + isDraggingProgressRef: React.MutableRefObject; + setCurrentTime: (time: number) => void; + setDuration: (duration: number) => void; +} + +/** + * Polling fallback for AirPlay and throttled events + * Updates playback progress manually when events are suppressed + */ +export function usePlaybackPolling({ + isPlaying, + videoRef, + isDraggingProgressRef, + setCurrentTime, + setDuration +}: UsePlaybackPollingProps) { + useEffect(() => { + if (!isPlaying || !videoRef.current) return; + + const interval = setInterval(() => { + if (videoRef.current && !isDraggingProgressRef.current) { + const current = videoRef.current.currentTime; + const total = videoRef.current.duration; + + // Only update if significantly different to avoid jitter + setCurrentTime(current); + if (!isNaN(total) && total > 0) { + setDuration(total); + } + } + }, 500); // Poll every 500ms + + return () => clearInterval(interval); + }, [isPlaying, videoRef, isDraggingProgressRef, setCurrentTime, setDuration]); +}