Skip to main content

web_codecs/audio/
encoder.rs

1use std::{cell::RefCell, rc::Rc};
2
3use tokio::sync::{mpsc, watch};
4use wasm_bindgen::{prelude::*, JsCast};
5
6use crate::{EncodedFrame, Error};
7
8use super::{AudioData, AudioDecoderConfig};
9
10// TODO support the full specification: https://developer.mozilla.org/en-US/docs/Web/API/AudioEncoder/configure
11#[derive(Debug, Default, Clone)]
12pub struct AudioEncoderConfig {
13	pub codec: String,
14	pub channel_count: Option<u32>,
15	pub sample_rate: Option<u32>,
16	pub bitrate: Option<u32>, // bits per second
17}
18
19impl AudioEncoderConfig {
20	pub fn new<T: Into<String>>(codec: T) -> Self {
21		Self {
22			codec: codec.into(),
23			channel_count: None,
24			sample_rate: None,
25			bitrate: None,
26		}
27	}
28
29	pub async fn is_supported(&self) -> Result<bool, Error> {
30		let res =
31			wasm_bindgen_futures::JsFuture::from(web_sys::AudioEncoder::is_config_supported(&self.into())).await?;
32		let support: web_sys::AudioEncoderSupport = res.unchecked_into();
33		Ok(support.get_supported().unwrap_or(false))
34	}
35
36	pub fn init(self) -> Result<(AudioEncoder, AudioEncoded), Error> {
37		let (frames_tx, frames_rx) = mpsc::unbounded_channel();
38		let (closed_tx, closed_rx) = watch::channel(Ok(()));
39		let config = Rc::new(RefCell::new(None));
40
41		let decoder = AudioEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
42		let decoded = AudioEncoded::new(config, frames_rx, closed_rx);
43
44		Ok((decoder, decoded))
45	}
46}
47
48impl From<&AudioEncoderConfig> for web_sys::AudioEncoderConfig {
49	fn from(this: &AudioEncoderConfig) -> Self {
50		let config = web_sys::AudioEncoderConfig::new(
51			&this.codec,
52			this.channel_count.unwrap_or(1),
53			this.sample_rate.unwrap_or(48000),
54		);
55
56		if let Some(bit_rate) = this.bitrate {
57			config.set_bitrate(bit_rate);
58		}
59
60		config
61	}
62}
63
64pub struct AudioEncoder {
65	inner: web_sys::AudioEncoder,
66	config: AudioEncoderConfig,
67
68	// These are held to avoid dropping them.
69	#[allow(dead_code)]
70	on_error: Closure<dyn FnMut(JsValue)>,
71	#[allow(dead_code)]
72	on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
73}
74
75impl AudioEncoder {
76	fn new(
77		config: AudioEncoderConfig,
78		on_config: Rc<RefCell<Option<AudioDecoderConfig>>>,
79		on_frame: mpsc::UnboundedSender<EncodedFrame>,
80		on_error: watch::Sender<Result<(), Error>>,
81	) -> Result<Self, Error> {
82		let on_error2 = on_error.clone();
83		let on_error = Closure::wrap(Box::new(move |e: JsValue| {
84			on_error.send_replace(Err(Error::from(e))).ok();
85		}) as Box<dyn FnMut(_)>);
86
87		let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
88			// First parameter is the frame, second optional parameter is metadata.
89			let frame: web_sys::EncodedAudioChunk = frame.unchecked_into();
90			let frame = EncodedFrame::from(frame);
91
92			if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
93				// TODO handle metadata
94				if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
95					if !config.is_falsy() {
96						let config: web_sys::AudioDecoderConfig = config.unchecked_into();
97						let config = AudioDecoderConfig::from(config);
98						on_config.borrow_mut().replace(config);
99					}
100				}
101			}
102
103			if on_frame.send(frame).is_err() {
104				on_error2.send_replace(Err(Error::Dropped)).ok();
105			}
106		}) as Box<dyn FnMut(_, _)>);
107
108		let init = web_sys::AudioEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
109		let inner: web_sys::AudioEncoder = web_sys::AudioEncoder::new(&init).unwrap();
110		inner.configure(&(&config).into())?;
111
112		Ok(Self {
113			config,
114			inner,
115			on_error,
116			on_frame,
117		})
118	}
119
120	pub fn encode(&mut self, frame: &AudioData) -> Result<(), Error> {
121		self.inner.encode(frame)?;
122		Ok(())
123	}
124
125	pub fn queue_size(&self) -> u32 {
126		self.inner.encode_queue_size()
127	}
128
129	pub fn config(&self) -> &AudioEncoderConfig {
130		&self.config
131	}
132
133	pub async fn flush(&mut self) -> Result<(), Error> {
134		wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
135		Ok(())
136	}
137}
138
139impl Drop for AudioEncoder {
140	fn drop(&mut self) {
141		let _ = self.inner.close();
142	}
143}
144
145pub struct AudioEncoded {
146	config: Rc<RefCell<Option<AudioDecoderConfig>>>,
147	frames: mpsc::UnboundedReceiver<EncodedFrame>,
148	closed: watch::Receiver<Result<(), Error>>,
149}
150
151impl AudioEncoded {
152	fn new(
153		config: Rc<RefCell<Option<AudioDecoderConfig>>>,
154		frames: mpsc::UnboundedReceiver<EncodedFrame>,
155		closed: watch::Receiver<Result<(), Error>>,
156	) -> Self {
157		Self { config, frames, closed }
158	}
159
160	pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
161		tokio::select! {
162			biased;
163			frame = self.frames.recv() => Ok(frame),
164			Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
165		}
166	}
167
168	pub fn config(&self) -> Option<AudioDecoderConfig> {
169		self.config.borrow().clone()
170	}
171}