mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-14 21:29:34 +00:00
fix(core): let a failed worklet registration be retried
`ensureAudioFxWorklets` cached its promise per context and never removed a rejected one, while `readyContexts` was only written on success. So callers correctly kept asking and every ask replayed the same rejection: one transient `addModule` failure left the limiter, compressor, gate and bitcrush silent for the life of that AudioContext, with no path back short of a page reload. The entry is now dropped on failure so the next attempt actually retries. Falsified by removing the `registered.delete(ctx)`: the retry replays the rejection instead of resolving. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
71ca86456c
commit
85257571c8
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { audioFxWorkletsReady, ensureAudioFxWorklets } from "./audioFxWorklets.js";
|
||||
|
||||
/** Just enough of a BaseAudioContext for the registration cache to key on. */
|
||||
const contextWith = (addModule: (url: string) => Promise<void>): BaseAudioContext =>
|
||||
({ audioWorklet: { addModule } }) as unknown as BaseAudioContext;
|
||||
|
||||
describe("ensureAudioFxWorklets", () => {
|
||||
it("registers once per context and reuses the result", async () => {
|
||||
const addModule = vi.fn(async () => undefined);
|
||||
const ctx = contextWith(addModule);
|
||||
|
||||
await ensureAudioFxWorklets(ctx);
|
||||
await ensureAudioFxWorklets(ctx);
|
||||
|
||||
expect(addModule).toHaveBeenCalledTimes(1);
|
||||
expect(audioFxWorkletsReady(ctx)).toBe(true);
|
||||
});
|
||||
|
||||
it("retries after a failure instead of replaying it forever", async () => {
|
||||
// The rejected promise used to stay in the cache, so every later attempt
|
||||
// got the same rejection back — the limiter, compressor, gate and bitcrush
|
||||
// were silent for the life of the context after one transient failure.
|
||||
const addModule = vi
|
||||
.fn<(url: string) => Promise<void>>()
|
||||
.mockRejectedValueOnce(new Error("module load failed"))
|
||||
.mockResolvedValue(undefined);
|
||||
const ctx = contextWith(addModule);
|
||||
|
||||
await expect(ensureAudioFxWorklets(ctx)).rejects.toThrow("module load failed");
|
||||
expect(audioFxWorkletsReady(ctx)).toBe(false);
|
||||
|
||||
await expect(ensureAudioFxWorklets(ctx)).resolves.toBeUndefined();
|
||||
expect(addModule).toHaveBeenCalledTimes(2);
|
||||
expect(audioFxWorkletsReady(ctx)).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses a context with no AudioWorklet rather than hanging", async () => {
|
||||
const ctx = {} as BaseAudioContext;
|
||||
await expect(ensureAudioFxWorklets(ctx)).rejects.toThrow(/secure context/);
|
||||
});
|
||||
});
|
||||
@@ -183,7 +183,9 @@ class HfBitcrush extends AudioWorkletProcessor {
|
||||
this.p = o.processorOptions || {};
|
||||
this.holds = [];
|
||||
this.held = [];
|
||||
this.port.onmessage = (e) => { this.p = { ...this.p, ...e.data }; };
|
||||
this.port.onmessage = (e) => {
|
||||
this.p = { ...this.p, ...e.data };
|
||||
};
|
||||
}
|
||||
process(inputs, outputs) {
|
||||
const i = inputs[0], o = outputs[0];
|
||||
@@ -243,7 +245,16 @@ export function ensureAudioFxWorklets(ctx: BaseAudioContext): Promise<void> {
|
||||
)}`;
|
||||
await ctx.audioWorklet.addModule(url);
|
||||
readyContexts.add(ctx);
|
||||
})();
|
||||
})().catch((err: unknown) => {
|
||||
// A failed registration must not be remembered. `readyContexts` is only
|
||||
// written on success, so callers correctly keep asking — and every ask
|
||||
// replayed this same rejected promise, leaving the limiter, compressor,
|
||||
// gate and bitcrush silent for the life of the context with no way back.
|
||||
// One transient failure (a slow module load, a context still warming up)
|
||||
// permanently disabled half the rack.
|
||||
registered.delete(ctx);
|
||||
throw err;
|
||||
});
|
||||
registered.set(ctx, modulePromise);
|
||||
}
|
||||
return modulePromise;
|
||||
|
||||
Reference in New Issue
Block a user