diff --git a/src/lib/components/player/VideoPlayer.svelte b/src/lib/components/player/VideoPlayer.svelte index 585637b..4d1ac23 100644 --- a/src/lib/components/player/VideoPlayer.svelte +++ b/src/lib/components/player/VideoPlayer.svelte @@ -203,6 +203,7 @@ seekOffset = 0; isMediaReady = false; // Reset to loading state when stream URL changes hasPerformedInitialSeek = false; // Reset so new video can seek to initial position + lastAppliedInitialPosition = undefined; // New stream - forget the previously-applied resume point endedFired = false; // New stream loaded - allow onEnded to fire again } }); @@ -373,18 +374,36 @@ } }); - // Handle initial position changes (for resuming same video from different position) + // Handle initial position changes (for resuming the *same* loaded video from a + // different position, e.g. the resume point is re-chosen without a reload). + // + // This must only react to a genuine change of the `initialPosition` prop to a + // value we haven't already applied. The previous version re-fired on the very + // first seek (it also depended on, and wrote, `hasPerformedInitialSeek`), and + // since seeking a transcoded/HLS stream re-buffers and fires `canplay` again, + // the two seek paths ping-ponged forever — the player appeared to pause/resume + // in a loop. We now snapshot the last-applied position and only seek when the + // prop actually moves to a new value, untracking the writes so this effect + // can't re-trigger itself. + let lastAppliedInitialPosition = $state(undefined); $effect(() => { - // Track initialPosition changes - if it changes and video is ready, seek to new position const pos = initialPosition; - if (pos && pos > 0 && isMediaReady && videoElement && hasPerformedInitialSeek) { - // Position changed after initial seek was done - seek to new position - console.log("[VideoPlayer] Initial position changed, seeking to:", pos); - hasPerformedInitialSeek = false; // Reset flag to allow handleCanPlay to seek - // Trigger seek directly since canplay won't fire for same video - videoElement.currentTime = pos; - currentTime = pos; + if (!pos || pos <= 0 || !isMediaReady) return; + // Only act on a real change to a not-yet-applied position. + if (pos === untrack(() => lastAppliedInitialPosition)) return; + // Skip the very first application; handleCanPlay owns the initial seek. + if (!untrack(() => hasPerformedInitialSeek)) { + lastAppliedInitialPosition = pos; + return; } + untrack(() => { + console.log("[VideoPlayer] Initial position changed, seeking to:", pos); + lastAppliedInitialPosition = pos; + if (videoElement) { + videoElement.currentTime = pos; + currentTime = pos; + } + }); }); // Set up progress reporting interval @@ -645,6 +664,7 @@ if (initialPosition && initialPosition > 0 && !hasPerformedInitialSeek && videoElement) { console.log("[VideoPlayer] Seeking to initial position:", initialPosition); hasPerformedInitialSeek = true; + lastAppliedInitialPosition = initialPosition; // mark this value as applied so the change-effect ignores it // Pause video to prevent autoplay from starting at position 0 const wasPlaying = !videoElement.paused;