use std::{cell::RefCell, rc::Rc};
use tokio::sync::{mpsc, watch};
use wasm_bindgen::{prelude::*, JsCast};
use crate::{EncodedFrame, Error};
use super::{AudioData, AudioDecoderConfig};
#[derive(Debug, Default, Clone)]
pub struct AudioEncoderConfig {
pub codec: String,
pub channel_count: Option<u32>,
pub sample_rate: Option<u32>,
pub bitrate: Option<u32>, }
impl AudioEncoderConfig {
pub fn new<T: Into<String>>(codec: T) -> Self {
Self {
codec: codec.into(),
channel_count: None,
sample_rate: None,
bitrate: None,
}
}
pub async fn is_supported(&self) -> Result<bool, Error> {
let res =
wasm_bindgen_futures::JsFuture::from(web_sys::AudioEncoder::is_config_supported(&self.into())).await?;
let support: web_sys::AudioEncoderSupport = res.unchecked_into();
Ok(support.get_supported().unwrap_or(false))
}
pub fn init(self) -> Result<(AudioEncoder, AudioEncoded), Error> {
let (frames_tx, frames_rx) = mpsc::unbounded_channel();
let (closed_tx, closed_rx) = watch::channel(Ok(()));
let config = Rc::new(RefCell::new(None));
let decoder = AudioEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
let decoded = AudioEncoded::new(config, frames_rx, closed_rx);
Ok((decoder, decoded))
}
}
impl From<&AudioEncoderConfig> for web_sys::AudioEncoderConfig {
fn from(this: &AudioEncoderConfig) -> Self {
let config = web_sys::AudioEncoderConfig::new(
&this.codec,
this.channel_count.unwrap_or(1),
this.sample_rate.unwrap_or(48000),
);
if let Some(bit_rate) = this.bitrate {
config.set_bitrate(bit_rate);
}
config
}
}
pub struct AudioEncoder {
inner: web_sys::AudioEncoder,
config: AudioEncoderConfig,
#[allow(dead_code)]
on_error: Closure<dyn FnMut(JsValue)>,
#[allow(dead_code)]
on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
}
impl AudioEncoder {
fn new(
config: AudioEncoderConfig,
on_config: Rc<RefCell<Option<AudioDecoderConfig>>>,
on_frame: mpsc::UnboundedSender<EncodedFrame>,
on_error: watch::Sender<Result<(), Error>>,
) -> Result<Self, Error> {
let on_error2 = on_error.clone();
let on_error = Closure::wrap(Box::new(move |e: JsValue| {
on_error.send_replace(Err(Error::from(e))).ok();
}) as Box<dyn FnMut(_)>);
let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
let frame: web_sys::EncodedAudioChunk = frame.unchecked_into();
let frame = EncodedFrame::from(frame);
if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
if !config.is_falsy() {
let config: web_sys::AudioDecoderConfig = config.unchecked_into();
let config = AudioDecoderConfig::from(config);
on_config.borrow_mut().replace(config);
}
}
}
if on_frame.send(frame).is_err() {
on_error2.send_replace(Err(Error::Dropped)).ok();
}
}) as Box<dyn FnMut(_, _)>);
let init = web_sys::AudioEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
let inner: web_sys::AudioEncoder = web_sys::AudioEncoder::new(&init).unwrap();
inner.configure(&(&config).into())?;
Ok(Self {
config,
inner,
on_error,
on_frame,
})
}
pub fn encode(&mut self, frame: &AudioData) -> Result<(), Error> {
self.inner.encode(frame)?;
Ok(())
}
pub fn queue_size(&self) -> u32 {
self.inner.encode_queue_size()
}
pub fn config(&self) -> &AudioEncoderConfig {
&self.config
}
pub async fn flush(&mut self) -> Result<(), Error> {
wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
Ok(())
}
}
impl Drop for AudioEncoder {
fn drop(&mut self) {
let _ = self.inner.close();
}
}
pub struct AudioEncoded {
config: Rc<RefCell<Option<AudioDecoderConfig>>>,
frames: mpsc::UnboundedReceiver<EncodedFrame>,
closed: watch::Receiver<Result<(), Error>>,
}
impl AudioEncoded {
fn new(
config: Rc<RefCell<Option<AudioDecoderConfig>>>,
frames: mpsc::UnboundedReceiver<EncodedFrame>,
closed: watch::Receiver<Result<(), Error>>,
) -> Self {
Self { config, frames, closed }
}
pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
tokio::select! {
biased;
frame = self.frames.recv() => Ok(frame),
Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
}
}
pub fn config(&self) -> Option<AudioDecoderConfig> {
self.config.borrow().clone()
}
}