diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx index 31748df..21c6252 100644 --- a/src/pages/PlayerPage.tsx +++ b/src/pages/PlayerPage.tsx @@ -32,6 +32,7 @@ import PlayerOverlays from '../components/player/PlayerOverlays' import { computeRecapTrigger } from '../lib/recap-trigger' import { usePersonalData } from '../stores/personal-data-store' import { usePlayerRuntimeStore } from '../stores/player-runtime-store' +import { useQueueStore } from '../stores/queue-store' import { usePlayerShortcuts } from '../hooks/use-player-shortcuts' import { detachAudioGraph } from '../lib/audio-graph' import type { ShortcutContext } from '../lib/player-shortcuts' @@ -363,15 +364,16 @@ export default function PlayerPage() { }) } - /* Resume prompt: show on first mount when there's a saved position - * past the threshold AND the user wants the prompt. The prompt auto- - * confirms after a few seconds, which queues the deferred seek via - * pendingSeekRef and seeks the underlying HTMLMediaElement directly - * (vidstack's MediaPlayerInstance.currentTime has a known bug on - * direct-play MP4 sources that restarts the video from 0). - * Intentionally scoped to item?.Id only - we only want to evaluate - * the resume condition once per item, not re-trigger when item data - * refreshes or prefs change mid-playback. */ + /* Resume behaviour: when the saved position is past the threshold, + * queue the seek so onCanPlay can apply it (the underlying + * HTMLMediaElement.currentTime is the only path that works for both + * direct-play MP4 and HLS without restarting from 0). Decide whether + * to also show the prompt: + * - ?resume=true : the caller already chose Resume, don't ask again + * - playlist queue: auto-advance through a playlist shouldn't ask + * before every episode + * - otherwise : show the prompt so the user can pick Resume / + * Start over (if the show-resume-prompt pref is on) */ const resumePromptShownRef = useRef(null) const resumeItemId = item?.Id const resumePositionTicks = item?.UserData?.PlaybackPositionTicks @@ -388,25 +390,35 @@ export default function PlayerPage() { usePlayerRuntimeStore.getState().resetForNewItem() }, [id, setPanel]) + /* Resume behaviour: when the saved position is past the threshold, + * queue the seek so onCanPlay can apply it (the underlying + * HTMLMediaElement.currentTime is the only path that works for both + * direct-play MP4 and HLS without restarting from 0). Decide whether + * to also show the prompt: + * - ?resume=true : the caller already chose Resume, don't ask again + * - playlist queue: auto-advance through a playlist shouldn't ask + * before every episode + * - otherwise : show the prompt so the user can pick Resume / + * Start over (if the show-resume-prompt pref is on) */ + const queueSource = useQueueStore(s => s.source) useEffect(() => { - if (typeof window !== 'undefined') { - console.log('[player] resume prompt effect', { - resumeItemId, - alreadyShown: resumePromptShownRef.current === resumeItemId, - pos: Number(resumePositionTicks ?? 0), - showResumePromptPref, - }) - } - if (!resumeItemId || resumePromptShownRef.current === resumeItemId) return + if (!resumeItemId) return const pos = Number(resumePositionTicks ?? 0) const thresholdSec = usePreferencesStore.getState().resumeThresholdSec ?? 5 const threshold = thresholdSec * 10_000_000 - if (showResumePromptPref && pos > threshold) { - if (typeof window !== 'undefined') console.log('[player] resume prompt opening') + if (pos <= threshold) return + const target = pos / 10_000_000 + if (pendingSeekRef.current == null) { + pendingSeekRef.current = target + } + if (resumePromptShownRef.current === resumeItemId) return + const inPlaylist = queueActive && queueSource?.type === 'playlist' + const skipPrompt = resume || inPlaylist + if (!skipPrompt && showResumePromptPref) { setResumePromptOpen(true) resumePromptShownRef.current = resumeItemId } - }, [resumeItemId, resumePositionTicks, showResumePromptPref]) + }, [resumeItemId, resumePositionTicks, showResumePromptPref, resume, queueActive, queueSource]) /* Auto-rewatch counter: when an already-played item starts playing * again, record the rewatch. We trip it at most once per item-mount