/** * OpusCapability - runtime detection of WebCodecs Ogg-Opus decode support. * * The Opus decode path is a WebCodecs `AudioDecoder` streaming pipeline (OpusStreamDecoder), NOT * `decodeAudioData`. So the capability gate must test the path actually used: whether the browser has * `AudioDecoder` AND supports the `codec: 'opus'` config. `AudioDecoder` is available on Chrome/Edge, * Firefox 130+, and Safari 16.4+; older Safari and older Firefox lack it, and those listeners fall back * to the universal lossless WAV path (ยง3.4 / OQ2 / AC7 โ€” no listener ever gets silence over a codec gap). * * This module is the detection *seam* only โ€” it answers "can this browser stream-decode Opus via * WebCodecs?". The player (StreamingAudioPlayerService.ResolveStreamFormatAsync) consumes the answer to * choose the delivery format; this module never touches the player or the stream request. The result is * cached after the first probe (capability does not change within a session). */ let cachedSupport: Promise | null = null; /** * Resolve whether this browser can stream-decode Ogg Opus via WebCodecs. Cached after the first call. * Never rejects โ€” any failure (no AudioDecoder, unsupported config, thrown probe) resolves to `false` * (treat as unsupported, fall back to lossless) so an interop error can never silence playback. */ export function canDecodeOggOpus(): Promise { if (cachedSupport === null) { cachedSupport = probe(); } return cachedSupport; } async function probe(): Promise { try { if (typeof AudioDecoder === 'undefined' || typeof AudioDecoder.isConfigSupported !== 'function') { return false; } // 48 kHz stereo is the canonical fullband Opus shape this site produces. isConfigSupported does // not need the OpusHead `description` to report codec support, so we probe without it. const result = await AudioDecoder.isConfigSupported({ codec: 'opus', sampleRate: 48000, numberOfChannels: 2 }); return result.supported === true; } catch { return false; } }