fix(filmstrip): seeked event never fires at t=0; add per-frame seek timeout

Two bugs:

1. Frame 0 sets currentTime=0 but probe starts at t=0 after onloadedmetadata,
   so 'seeked' never fires (no position change). Promise hangs until the 15s
   global timeout kills the whole build. Fix: when currentTime is already at
   target (within 0.05s), call done() immediately without waiting for seeked.

2. Seeks into unbuffered regions of large MP4s can stall indefinitely.
   Fix: 3s per-frame timeout captures the current decoded frame and moves on,
   so a slow/stalled seek doesn't block the remaining 27 frames.
This commit is contained in:
Zac Gaetano 2026-05-26 16:21:00 +00:00
parent e9eeb84c5f
commit 89645f160e

View file

@ -135,6 +135,7 @@ function AssetDetail({ asset, onClose }) {
const nextFrames = [];
for (let i = 0; i < frameCount; i++) {
const at = frameCount === 1 ? 0 : (probe.duration * i) / (frameCount - 1);
const target = Math.min(Math.max(at, 0), Math.max(0, probe.duration - 0.05));
await new Promise(function(resolve) {
const done = function() {
try {
@ -145,12 +146,28 @@ function AssetDetail({ asset, onClose }) {
}
resolve();
};
const onSeeked = function() {
probe.removeEventListener('seeked', onSeeked);
// If already at the target position (frame 0 at t=0), 'seeked' will
// never fire because the browser sees no position change call done()
// directly. Otherwise wait for the seeked event with a per-frame
// timeout so a stalled seek (unbuffered range) doesn't hang the strip.
if (Math.abs(probe.currentTime - target) < 0.05) {
done();
};
probe.addEventListener('seeked', onSeeked);
probe.currentTime = Math.min(Math.max(at, 0), Math.max(0, probe.duration - 0.05));
} else {
let frameTimer;
const onSeeked = function() {
clearTimeout(frameTimer);
probe.removeEventListener('seeked', onSeeked);
done();
};
probe.addEventListener('seeked', onSeeked);
probe.currentTime = target;
// 3s per-frame deadline if seek stalls (e.g. unbuffered remote range),
// capture whatever frame is currently decoded and move on.
frameTimer = setTimeout(function() {
probe.removeEventListener('seeked', onSeeked);
done();
}, 3000);
}
});
if (cancelled) return;
}