106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
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<void>;
|
|
close?: () => Promise<void>;
|
|
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 } }
|
|
);
|
|
}
|