diff --git a/components/player/SequencePlayer.tsx b/components/player/SequencePlayer.tsx index a5c507e..b920780 100644 --- a/components/player/SequencePlayer.tsx +++ b/components/player/SequencePlayer.tsx @@ -116,6 +116,8 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { const shownId = useRef(null); /** Bumped on every goTo so a superseded load stops acting. */ const generation = useRef(0); + /** The slot a goTo is loading into, until its clip is on screen. */ + const jumpingInto = useRef(null); /** How much earlier than its pre-roll frames the next clip must start. */ const startLatency = useRef(INITIAL_START_LATENCY_MS); @@ -216,6 +218,9 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { const n = nextIndex(fromIndex); if (n == null) return; const standby = (1 - activeRef.current) as Slot; + // A jump is loading its clip there; it preloads the next one itself + // once it's on screen. Loading over it would leave the screen black. + if (jumpingInto.current === standby) return; void prepare(standby, n, undefined, PREROLL_FRAMES); }, [nextIndex, prepare] @@ -266,8 +271,10 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { videoRefs[0].current?.pause(); videoRefs[1].current?.pause(); setBuffering(true); + jumpingInto.current = slot; const ok = await prepare(slot, i, frameInMedia); if (gen !== generation.current) return; + jumpingInto.current = null; setBuffering(false); if (!ok) return; show(slot, i); @@ -348,6 +355,7 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { const standbyVideo = videoRefs[standbySlot].current; const hasRvfc = "requestVideoFrameCallback" in HTMLVideoElement.prototype; + const armedGeneration = generation.current; let disposed = false; let held = false; // active clip paused on its last cut frame let prerolling = false; @@ -467,8 +475,16 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { } else { cancelAnimationFrame(handle); } - // Stopped mid pre-roll (paused, skipped): park the next clip again. - if (prerolling && !didCut && standbyVideo && n != null) { + // Stopped mid pre-roll (paused): park the next clip again — unless a + // jump has taken over, and the standby may now be the clip on screen. + if ( + prerolling && + !didCut && + standbyVideo && + n != null && + generation.current === armedGeneration && + activeRef.current !== standbySlot + ) { standbyVideo.pause(); void prepare(standbySlot, n, undefined, PREROLL_FRAMES); } @@ -508,8 +524,12 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [hq]); - // Reloading the standby when loop is toggled at the end of the list. + // Loop toggled: what comes after the last clip changed, so reload the + // standby. Not on mount — the first goTo is loading then. + const lastLoop = useRef(loop); useEffect(() => { + if (lastLoop.current === loop) return; + lastLoop.current = loop; if (clips.length) preloadNext(indexRef.current); // eslint-disable-next-line react-hooks/exhaustive-deps }, [loop]);