fix(core): a frame capture keeps waiting for a video between its range requests (#4490)

This commit is contained in:
Miguel Ángel
2026-09-25 14:12:43 +00:00
committed by GitHub
parent dd88b5f5b7
commit e266ad5e5c
2 changed files with 12 additions and 5 deletions
+8 -1
View File
@@ -819,10 +819,17 @@ describe("syncRuntimeMedia", () => {
}
});
it("releases a loading video that stops fetching before it has data", async () => {
it("keeps holding a loading video through a suspend between range requests", async () => {
const clip = seekLoadingVideo();
const barrier = waitForSeekCompletion();
clip.el.dispatchEvent(new Event("suspend"));
expect(await barrierSettled(barrier)).toBe(false);
Object.defineProperty(clip.el, "seeking", { value: true, configurable: true });
clip.el.dispatchEvent(new Event("loadedmetadata"));
clip.el.dispatchEvent(new Event("seeking"));
expect(await barrierSettled(barrier)).toBe(false);
Object.defineProperty(clip.el, "seeking", { value: false, configurable: true });
clip.el.dispatchEvent(new Event("seeked"));
expect(await barrierSettled(barrier)).toBe(true);
});
+4 -4
View File
@@ -19,13 +19,13 @@ export function readElementPlaybackStart(el: Element): number {
return readMediaStart(el);
}
const HOLD_END_EVENTS = ["seeked", "loadeddata", "suspend", "error", "emptied", "abort"] as const;
const HOLD_END_EVENTS = ["seeked", "loadeddata", "error", "emptied", "abort"] as const;
const HOLD_CAP_MS = 5000;
const releaseHeldVideo = new WeakMap<HTMLMediaElement, () => void>();
// A seeking video still paints its previous frame, and one still fetching its first data paints none (its seek
// waits for metadata without setting `seeking`); frame captures wait on the seek barrier until it lands. Each seek
// re-arms a 5 s cap, and listeners run in the capture phase so a failing <source> child releases the hold.
// waits for metadata without setting `seeking`); frame captures wait on the barrier until it lands. The 5 s cap is
// the only end for a stalled source (`suspend` also fires between range requests); capture phase catches <source>.
function holdSeekBarrierUntilVideoLands(el: HTMLMediaElement): void {
const loading =
el.readyState < el.HAVE_CURRENT_DATA &&
@@ -36,7 +36,7 @@ function holdSeekBarrierUntilVideoLands(el: HTMLMediaElement): void {
registerSeekCompletion(
new Promise<void>((resolve) => {
const done = (event?: Event) => {
if ((event?.type === "loadeddata" || event?.type === "suspend") && el.seeking) return;
if (event?.type === "loadeddata" && el.seeking) return;
clearTimeout(cap);
for (const type of HOLD_END_EVENTS) el.removeEventListener(type, done, true);
if (releaseHeldVideo.get(el) === done) releaseHeldVideo.delete(el);