web_codecs/audio/
encoder.rs

1use std::{cell::RefCell, rc::Rc};
2
3use tokio::sync::{mpsc, watch};
4use wasm_bindgen::prelude::*;
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
33		let supported = js_sys::Reflect::get(&res, &JsValue::from_str("supported"))
34			.unwrap()
35			.as_bool()
36			.unwrap();
37
38		Ok(supported)
39	}
40
41	pub fn init(self) -> Result<(AudioEncoder, AudioEncoded), Error> {
42		let (frames_tx, frames_rx) = mpsc::unbounded_channel();
43		let (closed_tx, closed_rx) = watch::channel(Ok(()));
44		let config = Rc::new(RefCell::new(None));
45
46		let decoder = AudioEncoder::new(self, config.clone(), frames_tx, closed_tx)?;
47		let decoded = AudioEncoded::new(config, frames_rx, closed_rx);
48
49		Ok((decoder, decoded))
50	}
51}
52
53impl From<&AudioEncoderConfig> for web_sys::AudioEncoderConfig {
54	fn from(this: &AudioEncoderConfig) -> Self {
55		let config = web_sys::AudioEncoderConfig::new(&this.codec);
56
57		if let Some(channels) = this.channel_count {
58			config.set_number_of_channels(channels);
59		}
60
61		if let Some(sample_rate) = this.sample_rate {
62			config.set_sample_rate(sample_rate);
63		}
64
65		if let Some(bit_rate) = this.bitrate {
66			config.set_bitrate(bit_rate as f64);
67		}
68
69		config
70	}
71}
72
73pub struct AudioEncoder {
74	inner: web_sys::AudioEncoder,
75	config: AudioEncoderConfig,
76
77	// These are held to avoid dropping them.
78	#[allow(dead_code)]
79	on_error: Closure<dyn FnMut(JsValue)>,
80	#[allow(dead_code)]
81	on_frame: Closure<dyn FnMut(JsValue, JsValue)>,
82}
83
84impl AudioEncoder {
85	fn new(
86		config: AudioEncoderConfig,
87		on_config: Rc<RefCell<Option<AudioDecoderConfig>>>,
88		on_frame: mpsc::UnboundedSender<EncodedFrame>,
89		on_error: watch::Sender<Result<(), Error>>,
90	) -> Result<Self, Error> {
91		let on_error2 = on_error.clone();
92		let on_error = Closure::wrap(Box::new(move |e: JsValue| {
93			on_error.send_replace(Err(Error::from(e))).ok();
94		}) as Box<dyn FnMut(_)>);
95
96		let on_frame = Closure::wrap(Box::new(move |frame: JsValue, meta: JsValue| {
97			// First parameter is the frame, second optional parameter is metadata.
98			let frame: web_sys::EncodedAudioChunk = frame.unchecked_into();
99			let frame = EncodedFrame::from(frame);
100
101			if let Ok(metadata) = meta.dyn_into::<js_sys::Object>() {
102				// TODO handle metadata
103				if let Ok(config) = js_sys::Reflect::get(&metadata, &"decoderConfig".into()) {
104					if !config.is_falsy() {
105						let config: web_sys::AudioDecoderConfig = config.unchecked_into();
106						let config = AudioDecoderConfig::from(config);
107						on_config.borrow_mut().replace(config);
108					}
109				}
110			}
111
112			if on_frame.send(frame).is_err() {
113				on_error2.send_replace(Err(Error::Dropped)).ok();
114			}
115		}) as Box<dyn FnMut(_, _)>);
116
117		let init = web_sys::AudioEncoderInit::new(on_error.as_ref().unchecked_ref(), on_frame.as_ref().unchecked_ref());
118		let inner: web_sys::AudioEncoder = web_sys::AudioEncoder::new(&init).unwrap();
119		inner.configure(&(&config).into())?;
120
121		Ok(Self {
122			config,
123			inner,
124			on_error,
125			on_frame,
126		})
127	}
128
129	pub fn encode(&mut self, frame: &AudioData) -> Result<(), Error> {
130		self.inner.encode(frame)?;
131		Ok(())
132	}
133
134	pub fn queue_size(&self) -> u32 {
135		self.inner.encode_queue_size()
136	}
137
138	pub fn config(&self) -> &AudioEncoderConfig {
139		&self.config
140	}
141
142	pub async fn flush(&mut self) -> Result<(), Error> {
143		wasm_bindgen_futures::JsFuture::from(self.inner.flush()).await?;
144		Ok(())
145	}
146}
147
148impl Drop for AudioEncoder {
149	fn drop(&mut self) {
150		let _ = self.inner.close();
151	}
152}
153
154pub struct AudioEncoded {
155	config: Rc<RefCell<Option<AudioDecoderConfig>>>,
156	frames: mpsc::UnboundedReceiver<EncodedFrame>,
157	closed: watch::Receiver<Result<(), Error>>,
158}
159
160impl AudioEncoded {
161	fn new(
162		config: Rc<RefCell<Option<AudioDecoderConfig>>>,
163		frames: mpsc::UnboundedReceiver<EncodedFrame>,
164		closed: watch::Receiver<Result<(), Error>>,
165	) -> Self {
166		Self { config, frames, closed }
167	}
168
169	pub async fn frame(&mut self) -> Result<Option<EncodedFrame>, Error> {
170		tokio::select! {
171			biased;
172			frame = self.frames.recv() => Ok(frame),
173			Ok(()) = self.closed.changed() => Err(self.closed.borrow().clone().err().unwrap()),
174		}
175	}
176
177	pub fn config(&self) -> Option<AudioDecoderConfig> {
178		self.config.borrow().clone()
179	}
180}