mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
fix(core): the decoded-audio cache keys a clip the same way everywhere (#6306)
* fix(core): the preview keeps a voiceover's decoded audio across an in-place reload The decode cache and the release pass keyed the same asset two ways, so a clip the release pass was handed as live could lose its buffer and be re-fetched mid-playback. * refactor(core): resolve the decode key through the runtime's own assetUrl Smaller than the inline URL resolution it replaces, which tipped the producer package-size budget, and one owner for "resolve against the document". * refactor(core): drop the decode-key comment the comment ratchet rejects * fix(core): an unreadable clip src no longer stops the preview from playing Resolving the decode key threw on a src that is not a URL, out of the release pass and through play(), so one bad clip silenced the whole preview instead of just failing its own fetch.
This commit is contained in:
@@ -6030,6 +6030,14 @@ describe("initSandboxRuntimeModular", () => {
|
||||
expect(cached()).toEqual(audios.map((a) => a.getAttribute("src")).sort());
|
||||
expect(fetchMock).toHaveBeenCalledTimes(12);
|
||||
});
|
||||
|
||||
it("plays the film when a clip's src cannot be read as a URL", async () => {
|
||||
mountAudio("http://localhost:PORT/vo.mp3");
|
||||
|
||||
await startPlayback();
|
||||
|
||||
expect(window.__player?.isPlaying()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// `el.volume` stops at 1, so a video's authored boost can only be carried by a Web Audio gain.
|
||||
|
||||
@@ -1597,6 +1597,8 @@ describe("WebAudioTransport", () => {
|
||||
}) as unknown as HTMLMediaElement;
|
||||
const failedSrcs = (t: WebAudioTransport) =>
|
||||
(t as unknown as { _failedSrcs: Set<string> })._failedSrcs;
|
||||
// Keys are resolved against the document, so one asset has one key however it is written.
|
||||
const key = (src: string) => new URL(src, document.baseURI).href;
|
||||
|
||||
it("does NOT blacklist a transient fetch failure — a later play retries and succeeds", async () => {
|
||||
const transport = transportWithDecode(async () => ({}) as AudioBuffer);
|
||||
@@ -1608,7 +1610,7 @@ describe("WebAudioTransport", () => {
|
||||
|
||||
const first = await transport.decodeAudioElement(el("tts.wav"));
|
||||
expect(first).toBeNull();
|
||||
expect(failedSrcs(transport).has("tts.wav")).toBe(false); // not permanently silenced
|
||||
expect(failedSrcs(transport).has(key("tts.wav"))).toBe(false); // not permanently silenced
|
||||
|
||||
const second = await transport.decodeAudioElement(el("tts.wav"));
|
||||
expect(second).not.toBeNull(); // self-heals once the asset is available
|
||||
@@ -1627,12 +1629,44 @@ describe("WebAudioTransport", () => {
|
||||
|
||||
const first = await transport.decodeAudioElement(el("corrupt.wav"));
|
||||
expect(first).toBeNull();
|
||||
expect(failedSrcs(transport).has("corrupt.wav")).toBe(true); // bad data is permanent
|
||||
expect(failedSrcs(transport).has(key("corrupt.wav"))).toBe(true); // bad data is permanent
|
||||
|
||||
const second = await transport.decodeAudioElement(el("corrupt.wav"));
|
||||
expect(second).toBeNull();
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1); // short-circuited, no re-fetch
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("keeps a listed clip's decoded audio while its load is between resources", async () => {
|
||||
// An in-place sub-composition reload resets the root voiceover's resource selection, so
|
||||
// `currentSrc` reads empty and the relative attribute behind it keyed a second cache slot:
|
||||
// the release pass then dropped the buffer of a clip it was handed as live.
|
||||
const transport = transportWithDecode(async () => ({}) as AudioBuffer);
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ ok: true, arrayBuffer: async () => new ArrayBuffer(8) });
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
const vo = document.createElement("audio");
|
||||
vo.setAttribute("src", "vo.mp3");
|
||||
let currentSrc = new URL("vo.mp3", document.baseURI).href;
|
||||
Object.defineProperty(vo, "currentSrc", { configurable: true, get: () => currentSrc });
|
||||
|
||||
expect(await transport.decodeAudioElement(vo)).not.toBeNull();
|
||||
currentSrc = "";
|
||||
transport.releaseDecodedExcept([vo]);
|
||||
currentSrc = new URL("vo.mp3", document.baseURI).href;
|
||||
|
||||
expect(await transport.decodeAudioElement(vo)).not.toBeNull();
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1); // the buffer it was told to keep is still there
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("keys a clip whose src cannot be read as a URL without throwing", () => {
|
||||
const transport = transportWithDecode(async () => ({}) as AudioBuffer);
|
||||
const bad = document.createElement("audio");
|
||||
bad.setAttribute("src", "http://localhost:PORT/vo.mp3");
|
||||
|
||||
expect(() => transport.releaseDecodedExcept([bad])).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import { clampAudioGain } from "../audioGain.js";
|
||||
import { getDebugSurface } from "./globals.js";
|
||||
import { readElementPlaybackRate } from "./media.js";
|
||||
import { classifyWebAudioMediaRoute, reportWebAudioMediaRoute } from "./webAudioRoute.js";
|
||||
import { assetUrl } from "./assetUrl.js";
|
||||
|
||||
const MASTER_FX_ATTRS: FxAttributes = { automation: null };
|
||||
|
||||
@@ -145,7 +146,15 @@ function isBufferSource(
|
||||
return source.sourceKind === "buffer";
|
||||
}
|
||||
|
||||
const decodeKey = (el: HTMLMediaElement): string | null => el.currentSrc || el.getAttribute("src");
|
||||
const decodeKey = (el: HTMLMediaElement): string | null => {
|
||||
const src = el.currentSrc || el.getAttribute("src");
|
||||
if (!src) return null;
|
||||
try {
|
||||
return assetUrl(src);
|
||||
} catch {
|
||||
return src;
|
||||
}
|
||||
};
|
||||
|
||||
// A pause to scrub or a reschedule mid-play keeps the output awake; a real idle lets it sleep.
|
||||
const IDLE_SUSPEND_MS = 10000;
|
||||
|
||||
Reference in New Issue
Block a user