On mount, the loop-toggle effect preloaded clip 2 into the slot the first goTo was loading clip 1 into, cancelling it, so nothing ever went on screen. Dev's StrictMode double-mount re-ran the first load and hid it. - preloadNext never loads over a slot a goTo is loading into - the loop effect only reloads the standby on an actual toggle - pausing or jumping mid pre-roll no longer re-parks a clip that a jump has since put on screen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,8 @@ export function SequencePlayer({ clips, onClipChange }: SequencePlayerProps) {
|
||||
const shownId = useRef<string | null>(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<Slot | null>(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]);
|
||||
|
||||
Reference in New Issue
Block a user