Files
deepdrft/DeepDrftPublic/Interop/audio/OpusCapability.ts
T

47 lines
2.1 KiB
TypeScript

/**
* 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<boolean> | 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<boolean> {
if (cachedSupport === null) {
cachedSupport = probe();
}
return cachedSupport;
}
async function probe(): Promise<boolean> {
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;
}
}