From 331cbab3304022a963f9ac933b6a857294672eec Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Thu, 29 Oct 2026 23:58:50 -0400 Subject: [PATCH] fix(core): a clip that has fully downloaded stays loaded and plays on time --- .../core/src/runtime/preloadMedia.test.ts | 30 +++++++++++++++++++ packages/core/src/runtime/preloadMedia.ts | 16 +++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/core/src/runtime/preloadMedia.test.ts b/packages/core/src/runtime/preloadMedia.test.ts index 10bb62f308..32021d9e7e 100644 --- a/packages/core/src/runtime/preloadMedia.test.ts +++ b/packages/core/src/runtime/preloadMedia.test.ts @@ -116,6 +116,36 @@ describe("releasing a clip's download", () => { expect(video.getAttribute("src")).toBe("a.mp4"); }); + it("keeps a clip whose whole file is buffered", () => { + const video = document.createElement("video"); + video.setAttribute("src", "a.mp4"); + Object.defineProperty(video, "duration", { value: 6.67 }); + Object.defineProperty(video, "buffered", { + value: { length: 1, start: () => 0, end: () => 6.67 }, + }); + video.load = vi.fn(); + + releaseMedia(video); + expect(video.load).not.toHaveBeenCalled(); + expect(video.getAttribute("src")).toBe("a.mp4"); + }); + + it.each([ + [0, 15], + [430, 2642], + ])("releases a clip with only %s-%s s of a long file buffered", (from, to) => { + const video = document.createElement("video"); + video.setAttribute("src", "a.mp4"); + Object.defineProperty(video, "duration", { value: 2642 }); + Object.defineProperty(video, "buffered", { + value: { length: 1, start: () => from, end: () => to }, + }); + video.load = vi.fn(); + + releaseMedia(video); + expect(video.load).toHaveBeenCalledTimes(1); + }); + it("leaves a clip on children, their order and its markup untouched", () => { const video = document.createElement("video"); video.innerHTML = diff --git a/packages/core/src/runtime/preloadMedia.ts b/packages/core/src/runtime/preloadMedia.ts index 8129ff3276..f4ceb5d6ab 100644 --- a/packages/core/src/runtime/preloadMedia.ts +++ b/packages/core/src/runtime/preloadMedia.ts @@ -5,6 +5,8 @@ import { defersMedia } from "./timedClipHide"; import { swallow } from "./diagnostics"; import { mediaElementTime } from "./mediaClock"; +const MEDIA_EDGE_SECONDS = 0.1; + type PreloadableMedia = Pick< HTMLMediaElement, "id" | "tagName" | "getAttribute" | "preload" | "readyState" | "networkState" | "load" @@ -73,8 +75,20 @@ export function stopMediaDownload(media: HTMLMediaElement): void { media.load(); } +// The whole file in memory: its download is over, so releasing it would free no connection, only drop the data. +function wholeFileBuffered(media: HTMLMediaElement): boolean { + const { buffered, duration } = media; + return ( + Number.isFinite(duration) && + duration > 0 && + buffered.length === 1 && + buffered.start(0) <= MEDIA_EDGE_SECONDS && + buffered.end(0) >= duration - MEDIA_EDGE_SECONDS + ); +} + export function releaseMedia(media: HTMLMediaElement): void { - if (media.querySelector("source")) return; + if (media.querySelector("source") || wholeFileBuffered(media)) return; const src = media.getAttribute("src"); stopMediaDownload(media); if (src !== null) media.setAttribute("src", src);