fix(core): preserve playhead during volume probing (#2143)

This commit is contained in:
Miguel Ángel
2026-07-10 17:18:16 -04:00
committed by GitHub
parent 718c67b387
commit 16ab8b2935
2 changed files with 43 additions and 3 deletions
@@ -0,0 +1,31 @@
/** @vitest-environment jsdom */
import { describe, expect, it } from "vitest";
import { probeAndCacheElementVolume } from "./mediaVolumeEnvelope";
describe("probeAndCacheElementVolume", () => {
it("restores the timeline playhead after sampling volume automation", () => {
const audio = document.createElement("audio");
audio.dataset.volume = "1";
document.body.append(audio);
let playhead = 0.75;
const timeline = {
totalTime(next?: number) {
if (next !== undefined) {
playhead = next;
audio.volume = next >= 1 ? 0 : 1;
}
return playhead;
},
};
const cache = new WeakMap<HTMLMediaElement, { time: number; volume: number }[]>();
probeAndCacheElementVolume(audio, timeline, 1, cache);
expect(playhead).toBe(0.75);
expect(audio.volume).toBe(1);
expect(cache.get(audio)).toEqual(
expect.arrayContaining([expect.objectContaining({ volume: 0 })]),
);
});
});
@@ -126,8 +126,8 @@ export function probeElementVolumeKeyframes(
}
export interface RuntimeTimelineRef {
totalTime?: ((t: number, suppressEvents?: boolean) => unknown) | undefined;
seek?: ((t: number, suppressEvents?: boolean) => unknown) | undefined;
totalTime?: ((t?: number, suppressEvents?: boolean) => unknown) | undefined;
seek?: ((t?: number, suppressEvents?: boolean) => unknown) | undefined;
}
/**
@@ -155,8 +155,17 @@ export function probeAndCacheElementVolume(
// ignore seek failures during probe
}
};
// Sampling seeks the live timeline through the entire composition. Preserve
// its playhead so the probe cannot perturb the first rendered frame (or any
// user scrub in the preview).
const originalTime =
typeof timeline.totalTime === "function"
? Number(timeline.totalTime())
: typeof timeline.seek === "function"
? Number(timeline.seek())
: 0;
const keyframes = probeElementVolumeKeyframes(mediaEl, seekFn, compositionDuration, 60);
if (Number.isFinite(originalTime)) seekFn(originalTime);
if (keyframes) {
cache.set(mediaEl, keyframes);
}