import { AUDIO_ENGINE_BROWSER_CODES, BrowserAudioError, } from "../error-codes/error-codes.js"; import type { BrowserAdapterOptions } from "./browser-adapter.types.js"; import { constructStandardizedContext } from "./standardized-audio-context-bridge.js"; export interface AdapterContextLike { readonly sampleRate: number; readonly audioWorklet?: unknown; readonly destination: unknown; resume?: () => Promise; close?: () => Promise; createGain(): unknown; createStereoPanner(): unknown; createDynamicsCompressor(): unknown; createAnalyser(): unknown; createBufferSource(): unknown; createScriptProcessor( bufferSize: number, numberOfInputChannels: number, numberOfOutputChannels: number ): unknown; } /** * Resolve a `BaseAudioContext`-shaped value for the adapter. Uses the * caller-supplied `context` option verbatim when present, otherwise * dynamically imports `standardized-audio-context` and constructs a * realtime `AudioContext`. Resumes the context before returning. * * Throws `BrowserAudioError(CONTEXT_FAILED)` for every failure mode. */ export async function resolveContext(args: { readonly injected?: unknown; readonly latencyHint?: string; readonly sampleRate?: number; readonly signal?: AbortSignal; }): Promise<{ readonly context: AdapterContextLike; readonly ownsContext: boolean; }> { if (args.signal?.aborted) { throw new BrowserAudioError( AUDIO_ENGINE_BROWSER_CODES.CONTEXT_FAILED, "audio-engine-browser.init-aborted", { cause: args.signal.reason } ); } const resolved = args.injected !== undefined ? { ctx: args.injected as AdapterContextLike, ownsContext: false } : await constructStandardizedContext(args); const ctx = resolved.ctx; const ownsContext = resolved.ownsContext; try { await ctx.resume?.(); } catch (cause) { throw new BrowserAudioError( AUDIO_ENGINE_BROWSER_CODES.CONTEXT_FAILED, "audio-engine-browser.context-resume-failed", { cause } ); } return { context: ctx, ownsContext }; } export function readyOptions(options: BrowserAdapterOptions): { readonly injected?: unknown; readonly latencyHint?: string; readonly sampleRate?: number; } { const out: { injected?: unknown; latencyHint?: string; sampleRate?: number; } = {}; if (options.context !== undefined) out.injected = options.context; if (typeof options.latencyHint === "string") out.latencyHint = options.latencyHint; if (options.sampleRate !== undefined) out.sampleRate = options.sampleRate; return out; } export function throwAlreadyDisposed(): never { throw new BrowserAudioError( AUDIO_ENGINE_BROWSER_CODES.ALREADY_DISPOSED, "audio-engine-browser.already-disposed" ); } export function throwNotInitialised(): never { throw new BrowserAudioError( AUDIO_ENGINE_BROWSER_CODES.CONTEXT_FAILED, "audio-engine-browser.not-initialised" ); } export function throwTrackNotFound(trackId: string): never { throw new BrowserAudioError( AUDIO_ENGINE_BROWSER_CODES.TRACK_NOT_FOUND, "audio-engine-browser.track-not-found", { context: { trackId } } ); }