fix(core): preserve per-element preload ordering in render mode

The lazy media preloading refactor split bindMediaMetadataListeners into
two loops: bind all listeners first, then preload all elements. This
changed when metadata listeners fire relative to .load(), shifting
timeline duration hydration and causing 3 transition frames to render at
a slightly different state in the style-9-prod regression test.

Move eager preload back inside the per-element binding loop so listener
attachment and .load() happen in the same iteration, matching the
original ordering. Lazy-mode demotion stays in a separate block after
mediaPreloader.refresh() since it needs the full clip list.
This commit is contained in:
Miguel Ángel
2026-05-11 21:21:35 -07:00
parent f13c30e1f2
commit e704a33c69
+9 -10
View File
@@ -1233,6 +1233,7 @@ export function initSandboxRuntimeModular(): void {
const bindMediaMetadataListeners = () => {
if (state.tornDown) return;
const mediaEls = Array.from(document.querySelectorAll("video, audio")) as HTMLMediaElement[];
const isLazy = mediaPreloader.isLazy();
let newElementsBound = false;
for (const mediaEl of mediaEls) {
@@ -1241,22 +1242,20 @@ export function initSandboxRuntimeModular(): void {
newElementsBound = true;
mediaEl.addEventListener("loadedmetadata", scheduleMetadataDurationHydration);
mediaEl.addEventListener("durationchange", scheduleMetadataDurationHydration);
// In eager mode, preload inline (same ordering as before lazy preloading)
if (!isLazy || isRenderMode) {
if (mediaEl.preload !== "auto") mediaEl.preload = "auto";
if (mediaEl.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) mediaEl.load();
}
}
if (newElementsBound && !isRenderMode) {
mediaPreloader.refresh();
}
if (!mediaPreloader.isLazy() || isRenderMode) {
for (const mediaEl of mediaEls) {
if (mediaEl.preload !== "auto") {
mediaEl.preload = "auto";
}
if (mediaEl.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) {
mediaEl.load();
}
}
} else {
// Lazy-mode demotion runs separately after refresh updates the clip list
if (mediaPreloader.isLazy() && !isRenderMode) {
// Only demote timed media (elements with data-start) to metadata preload.
// Untimed media (background audio, ambient loops, decorative video) must
// keep their original preload state — the mediaPreloader only manages