use crate::Result;
pub trait AudioDecoder {
fn sample_rate(&self) -> u32;
fn channels(&self) -> u8;
fn decode(&mut self, encoded: &[u8]) -> Result<Vec<f32>>;
}
#[cfg(feature = "web")]
mod browser_detect;
#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
mod native_opus;
#[cfg(feature = "web")]
mod safari_decoder;
#[cfg(not(feature = "web"))]
mod wasm_stub;
#[cfg(feature = "web")]
mod webcodecs_decoder;
#[cfg(feature = "web")]
pub use safari_decoder::SafariOpusDecoder;
#[cfg(feature = "web")]
pub use webcodecs_decoder::WebCodecsAudioDecoder;
#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
pub use native_opus::NativeOpusDecoder;
#[cfg(not(feature = "web"))]
pub use wasm_stub::*;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "native")))]
pub struct StubOpusDecoder {
sample_rate: u32,
channels: u8,
}
#[cfg(all(not(target_arch = "wasm32"), not(feature = "native")))]
impl StubOpusDecoder {
pub fn new(sample_rate: u32, channels: u8) -> Result<Self> {
log::warn!("Using stub Opus decoder - no actual decoding will occur");
Ok(Self {
sample_rate,
channels,
})
}
}
#[cfg(all(not(target_arch = "wasm32"), not(feature = "native")))]
impl AudioDecoder for StubOpusDecoder {
fn sample_rate(&self) -> u32 {
self.sample_rate
}
fn channels(&self) -> u8 {
self.channels
}
fn decode(&mut self, _encoded: &[u8]) -> Result<Vec<f32>> {
let samples_per_channel = (self.sample_rate as f32 * 0.02) as usize;
let total_samples = samples_per_channel * self.channels as usize;
Ok(vec![0.0; total_samples])
}
}
#[cfg(feature = "web")]
enum DecoderBackend {
WebCodecs(webcodecs_decoder::WebCodecsAudioDecoder),
JsLibrary(safari_decoder::SafariOpusDecoder),
}
#[cfg(feature = "web")]
pub struct UnifiedOpusDecoder {
decoder: DecoderBackend,
sample_rate: u32,
channels: u8,
}
#[cfg(feature = "web")]
impl UnifiedOpusDecoder {
pub async fn new(sample_rate: u32, channels: u8) -> Result<Self> {
let backend = browser_detect::detect_audio_backend();
let decoder = match backend {
browser_detect::AudioBackend::WebCodecs => {
log::info!("Initializing WebCodecs AudioDecoder (hardware-accelerated)");
let dec =
webcodecs_decoder::WebCodecsAudioDecoder::new(sample_rate, channels).await?;
DecoderBackend::WebCodecs(dec)
}
browser_detect::AudioBackend::JsLibrary => {
log::info!("Initializing opus-decoder JS library (Safari/iOS)");
let mut dec = safari_decoder::SafariOpusDecoder::new(sample_rate, channels);
dec.init_decoder().await?;
DecoderBackend::JsLibrary(dec)
}
};
Ok(Self {
decoder,
sample_rate,
channels,
})
}
pub fn decoder_type(&self) -> &'static str {
match &self.decoder {
DecoderBackend::WebCodecs(d) => d.get_decoder_type(),
DecoderBackend::JsLibrary(d) => d.get_decoder_type(),
}
}
pub async fn enable_audio_playback(
&self,
_audio_context: &web_sys::AudioContext,
) -> Result<()> {
Ok(())
}
pub fn flush_audio(&self) -> Result<()> {
Ok(())
}
pub async fn decode_async(&mut self, encoded: &[u8]) -> Vec<f32> {
match &mut self.decoder {
DecoderBackend::WebCodecs(d) => d.decode(encoded).unwrap_or_default(),
DecoderBackend::JsLibrary(d) => d.decode_sync(encoded),
}
}
}
#[cfg(feature = "web")]
impl AudioDecoder for UnifiedOpusDecoder {
fn sample_rate(&self) -> u32 {
self.sample_rate
}
fn channels(&self) -> u8 {
self.channels
}
fn decode(&mut self, encoded: &[u8]) -> Result<Vec<f32>> {
match &mut self.decoder {
DecoderBackend::WebCodecs(d) => d.decode(encoded),
DecoderBackend::JsLibrary(d) => Ok(d.decode_sync(encoded)),
}
}
}
#[cfg(target_arch = "wasm32")]
pub type OpusDecoder = UnifiedOpusDecoder;
#[cfg(all(not(target_arch = "wasm32"), feature = "native"))]
pub type OpusDecoder = native_opus::NativeOpusDecoder;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "native")))]
pub type OpusDecoder = StubOpusDecoder;