/** * Audio engine — W3, plan v1.1 decision (b): audio is CORE-ONLY. * * `window.__squidAudio` was an inline IIFE in the shell template. Moving it * here makes it versioned and patchable: a provider changes their embed API * and one package bump reaches every consumer, instead of every customer * needing to re-copy a component. * * The accepted trade-off is that consumers cannot customise playback * behaviour without forking. The mitigation is the shape of this module: what * moves into core is the ENGINE — adapters, gesture wiring, the listen clock, * media session — and NOT the chrome. `getState()` and `on()` expose * everything a copied component needs to render its own player UI against an * engine it does not own. Moving the shell's markup in here as well would have * been "just moving the file", and would have made every consumer's player * look like signage. * * Two invariants worth stating because both are load-bearing: * * - AUTOPLAY IS NEVER USED. Tap to play, always. Autoplaying audio in * someone else's page is the single fastest way for a customer to remove * the SDK. * - ONE PLAYER AT A TIME. Mounting a second destroys the first, so a page * that cycles variants cannot end up with two tracks over each other. */ export type AudioProvider = "suno" | "spotify"; export interface AudioBlock { provider: AudioProvider; /** Direct media URL (suno) or provider URI (spotify). */ audioUrl?: string; trackRef?: string; title?: string; artist?: string; durationMs?: number; loop?: boolean; artwork?: { url: string; }; } export type AudioEvent = "play" | "pause" | "progress" | "complete" | "error" | "ready"; export interface AudioState { provider: AudioProvider; playing: boolean; positionMs: number; durationMs: number; /** 0..1. Zero when the duration is not yet known — never NaN. */ percent: number; /** Wall-clock time this track was actually audible. Drives listen analytics. */ listenedMs: number; ended: boolean; errored: boolean; } export interface AudioCapabilities { /** * May the consumer render its own transport UI? False for providers whose * embed is a fixed iframe we are not permitted to hide or reskin. */ customChrome: boolean; /** Does playback work without the viewer holding a provider account? */ anonymousPlayback: boolean; } export interface AudioHandle { provider: AudioProvider; caps: AudioCapabilities; getState(): AudioState; play(): void; pause(): void; toggle(): void; /** No-op on providers that do not expose seeking. */ seek(positionMs: number): void; on(event: AudioEvent, listener: (state: AudioState) => void): () => void; destroy(): void; } /** The currently mounted player, if any. */ export declare function activePlayer(): AudioHandle | null; /** * Mount a player for `block` inside `host`. * * Destroys any existing player first — the single-player invariant. A page * cycling variants would otherwise stack tracks on top of each other, and the * viewer has no way to stop one they can no longer see. */ export declare function mountAudio(block: AudioBlock, host: HTMLElement): AudioHandle | null; export declare function destroyActiveAudio(): void; //# sourceMappingURL=audio.d.ts.map