use crate::codec::AudioDecoder;
use crate::Result;
use js_sys::{Function, Reflect, Uint8Array};
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::console;
fn js_err(msg: &str) -> impl Fn(JsValue) -> crate::NetEqError + '_ {
move |e| crate::NetEqError::DecoderError(format!("{msg}: {e:?}"))
}
pub struct WebCodecsAudioDecoder {
decoder: Option<JsValue>,
sample_rate: u32,
channels: u8,
output_buffer: Rc<RefCell<Vec<f32>>>,
input_buffer: Uint8Array,
}
unsafe impl Send for WebCodecsAudioDecoder {}
unsafe impl Sync for WebCodecsAudioDecoder {}
impl WebCodecsAudioDecoder {
pub async fn new(sample_rate: u32, channels: u8) -> Result<Self> {
let output_buffer = Rc::new(RefCell::new(Vec::new()));
let output_buffer_clone = output_buffer.clone();
let config = js_sys::Object::new();
Reflect::set(&config, &"codec".into(), &"opus".into())
.map_err(js_err("Failed to set codec"))?;
Reflect::set(&config, &"sampleRate".into(), &(sample_rate as f64).into())
.map_err(js_err("Failed to set sampleRate"))?;
Reflect::set(
&config,
&"numberOfChannels".into(),
&(channels as f64).into(),
)
.map_err(js_err("Failed to set numberOfChannels"))?;
let output_cb = Closure::wrap(Box::new(move |audio_data: JsValue| {
if let Err(e) = Self::handle_output(&audio_data, &output_buffer_clone) {
console::error_1(&format!("WebCodecs output error: {e:?}").into());
}
}) as Box<dyn FnMut(JsValue)>);
let error_cb = Closure::wrap(Box::new(move |e: JsValue| {
console::error_1(&"WebCodecs decoder error:".into());
console::error_1(&e);
}) as Box<dyn FnMut(JsValue)>);
let init = js_sys::Object::new();
Reflect::set(&init, &"output".into(), output_cb.as_ref())
.map_err(js_err("Failed to set output callback"))?;
Reflect::set(&init, &"error".into(), error_cb.as_ref())
.map_err(js_err("Failed to set error callback"))?;
let window = web_sys::window()
.ok_or_else(|| crate::NetEqError::DecoderError("No window available".to_string()))?;
let audio_decoder_ctor = Reflect::get(&JsValue::from(window), &"AudioDecoder".into())
.map_err(js_err("AudioDecoder not found"))?;
let decoder = Reflect::construct(
&audio_decoder_ctor.unchecked_into::<Function>(),
&js_sys::Array::of1(&init),
)
.map_err(js_err("Failed to construct AudioDecoder"))?;
let configure_fn = Reflect::get(&decoder, &"configure".into())
.map_err(js_err("Failed to get configure method"))?
.dyn_into::<Function>()
.map_err(|_| crate::NetEqError::DecoderError("configure not a function".to_string()))?;
configure_fn
.call1(&decoder, &config)
.map_err(|e| crate::NetEqError::DecoderError(format!("Configure failed: {e:?}")))?;
let state_prop =
Reflect::get(&decoder, &"state".into()).map_err(js_err("Failed to get state"))?;
let state = state_prop.as_string().unwrap_or_default();
console::log_1(&format!("WebCodecs decoder state: {state}").into());
output_cb.forget();
error_cb.forget();
Ok(Self {
decoder: Some(decoder),
sample_rate,
channels,
output_buffer,
input_buffer: Uint8Array::new_with_length(1275),
})
}
fn handle_output(audio_data: &JsValue, output_buffer: &Rc<RefCell<Vec<f32>>>) -> Result<()> {
let num_frames = Reflect::get(audio_data, &"numberOfFrames".into())
.map_err(js_err("Failed to get numberOfFrames"))?
.as_f64()
.unwrap_or(0.0) as usize;
if num_frames == 0 {
return Ok(());
}
let num_channels = Reflect::get(audio_data, &"numberOfChannels".into())
.map_err(js_err("Failed to get numberOfChannels"))?
.as_f64()
.unwrap_or(1.0) as usize;
let total_samples = num_frames * num_channels;
let mut samples = vec![0.0f32; total_samples];
let copy_options = js_sys::Object::new();
Reflect::set(©_options, &"planeIndex".into(), &0.into())
.map_err(js_err("Failed to set planeIndex"))?;
Reflect::set(©_options, &"format".into(), &"f32-planar".into())
.map_err(js_err("Failed to set format"))?;
let samples_array = js_sys::Float32Array::from(samples.as_slice());
let copy_to_fn = Reflect::get(audio_data, &"copyTo".into())
.map_err(js_err("Failed to get copyTo"))?
.dyn_into::<Function>()
.map_err(|_| crate::NetEqError::DecoderError("copyTo not a function".to_string()))?;
copy_to_fn
.call2(audio_data, &samples_array, ©_options)
.map_err(|e| crate::NetEqError::DecoderError(format!("copyTo failed: {e:?}")))?;
samples_array.copy_to(&mut samples);
output_buffer.borrow_mut().extend_from_slice(&samples);
let close_fn = Reflect::get(audio_data, &"close".into())
.map_err(js_err("Failed to get close"))?
.dyn_into::<Function>()
.map_err(|_| crate::NetEqError::DecoderError("close not a function".to_string()))?;
let _ = close_fn.call0(audio_data);
Ok(())
}
pub fn get_decoder_type(&self) -> &'static str {
"WebCodecs"
}
}
impl AudioDecoder for WebCodecsAudioDecoder {
fn sample_rate(&self) -> u32 {
self.sample_rate
}
fn channels(&self) -> u8 {
self.channels
}
fn decode(&mut self, encoded: &[u8]) -> Result<Vec<f32>> {
let decoder = self.decoder.as_ref().ok_or_else(|| {
crate::NetEqError::DecoderError("Decoder not initialized".to_string())
})?;
if self.input_buffer.length() < encoded.len() as u32 {
self.input_buffer = Uint8Array::new_with_length(encoded.len() as u32);
}
self.input_buffer.set(&Uint8Array::from(encoded), 0);
let chunk_init = js_sys::Object::new();
Reflect::set(&chunk_init, &"type".into(), &"key".into())
.map_err(js_err("Failed to set chunk type"))?;
Reflect::set(&chunk_init, &"timestamp".into(), &0.into())
.map_err(js_err("Failed to set timestamp"))?;
Reflect::set(
&chunk_init,
&"data".into(),
&self.input_buffer.subarray(0, encoded.len() as u32).buffer(),
)
.map_err(js_err("Failed to set data"))?;
let window = web_sys::window()
.ok_or_else(|| crate::NetEqError::DecoderError("No window".to_string()))?;
let encoded_chunk_ctor = Reflect::get(&JsValue::from(window), &"EncodedAudioChunk".into())
.map_err(js_err("EncodedAudioChunk not found"))?;
let chunk = Reflect::construct(
&encoded_chunk_ctor.unchecked_into::<Function>(),
&js_sys::Array::of1(&chunk_init),
)
.map_err(js_err("Failed to construct EncodedAudioChunk"))?;
let decode_fn = Reflect::get(decoder, &"decode".into())
.map_err(js_err("Failed to get decode method"))?
.dyn_into::<Function>()
.map_err(|_| crate::NetEqError::DecoderError("decode not a function".to_string()))?;
decode_fn
.call1(decoder, &chunk)
.map_err(|e| crate::NetEqError::DecoderError(format!("Decode failed: {e:?}")))?;
let flush_fn = Reflect::get(decoder, &"flush".into())
.map_err(js_err("Failed to get flush method"))?
.dyn_into::<Function>()
.ok();
if let Some(flush) = flush_fn {
let _ = flush.call0(decoder);
}
let samples = self.output_buffer.borrow_mut().drain(..).collect();
Ok(samples)
}
}