diff --git a/packages/core/src/runtime/webAudioTransport.ts b/packages/core/src/runtime/webAudioTransport.ts index baa1113bc..f24a2e9b3 100644 --- a/packages/core/src/runtime/webAudioTransport.ts +++ b/packages/core/src/runtime/webAudioTransport.ts @@ -18,6 +18,7 @@ export type ScheduledSource = { export class WebAudioTransport { private _ctx: AudioContext | null = null; private _bufferCache = new Map(); + private _failedSrcs = new Set(); private _activeSources: ScheduledSource[] = []; private _masterGain: GainNode | null = null; // Composition-time reference frame: at AudioContext time `_rateAnchorCtx`, @@ -53,14 +54,21 @@ export class WebAudioTransport { const src = el.currentSrc || el.getAttribute("src"); if (!src) return null; if (this._bufferCache.has(src)) return this._bufferCache.get(src)!; + if (this._failedSrcs.has(src)) return null; if (!this._ctx) return null; try { const response = await fetch(src); + if (!response.ok) { + this._failedSrcs.add(src); + swallow("webAudioTransport.fetch", new Error(`${response.status} ${src}`)); + return null; + } const arrayBuffer = await response.arrayBuffer(); const audioBuffer = await this._ctx.decodeAudioData(arrayBuffer); this._bufferCache.set(src, audioBuffer); return audioBuffer; } catch (err) { + this._failedSrcs.add(src); swallow("webAudioTransport.decode", err); return null; } diff --git a/packages/studio/src/main.tsx b/packages/studio/src/main.tsx index 0b4324790..92c547c73 100644 --- a/packages/studio/src/main.tsx +++ b/packages/studio/src/main.tsx @@ -22,6 +22,16 @@ function errorProps(value: unknown): { return { error_message: String(value), error_name: null, stack_trace: null }; } +function isCompositionAssetError(msg: string): boolean { + return msg.includes("Error fetching") && (msg.includes("404") || msg.includes("Not Found")); +} + +const ERROR_CAP = 50; +let errorCount = 0; +let rejectionCount = 0; +let errorCapSent = false; +let rejectionCapSent = false; + window.addEventListener("error", (event) => { if (event.message?.includes("ResizeObserver loop")) { event.stopImmediatePropagation(); @@ -29,6 +39,15 @@ window.addEventListener("error", (event) => { return; } + errorCount++; + if (errorCount > ERROR_CAP) { + if (!errorCapSent) { + errorCapSent = true; + trackStudioEvent("error_cap_reached", { count: errorCount }); + } + return; + } + trackStudioEvent("unhandled_error", { ...errorProps(event.error), error_message: event.message, @@ -39,7 +58,19 @@ window.addEventListener("error", (event) => { }); window.addEventListener("unhandledrejection", (event) => { - trackStudioEvent("unhandled_promise_rejection", errorProps(event.reason)); + const props = errorProps(event.reason); + if (isCompositionAssetError(props.error_message)) return; + + rejectionCount++; + if (rejectionCount > ERROR_CAP) { + if (!rejectionCapSent) { + rejectionCapSent = true; + trackStudioEvent("rejection_cap_reached", { count: rejectionCount }); + } + return; + } + + trackStudioEvent("unhandled_promise_rejection", props); }); createRoot(document.getElementById("root")!).render(