web_audio_api/media_recorder/
mod.rs1use crate::media_streams::MediaStream;
9use crate::{AudioBuffer, ErrorEvent, Event};
10use std::error::Error;
11
12use std::io::Write;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::sync::{Arc, Mutex};
15use std::time::Instant;
16
17type EventCallback = Box<dyn FnOnce(Event) + Send + 'static>;
18type BlobEventCallback = Box<dyn FnMut(BlobEvent) + Send + 'static>;
19type ErrorEventCallback = Box<dyn FnOnce(ErrorEvent) + Send + 'static>;
20
21struct RecordedData {
22 blob: Vec<u8>,
23 start_timecode: Instant,
24 current_timecode: Instant,
25}
26
27impl RecordedData {
28 fn new(blob: Vec<u8>) -> Self {
29 let now = Instant::now();
30
31 Self {
32 blob,
33 start_timecode: now,
34 current_timecode: now,
35 }
36 }
37
38 fn encode_first(&mut self, buf: AudioBuffer) {
40 let spec = hound::WavSpec {
41 channels: buf.number_of_channels() as u16,
42 sample_rate: buf.sample_rate() as u32,
43 bits_per_sample: 32,
44 sample_format: hound::SampleFormat::Float,
45 };
46 let v = spec.into_header_for_infinite_file();
47 self.blob.write_all(&v).unwrap();
48 self.encode_next(buf);
49 }
50
51 fn encode_next(&mut self, buf: AudioBuffer) {
53 for i in 0..buf.length() {
54 for c in 0..buf.number_of_channels() {
55 let v = buf.get_channel_data(c)[i];
56 hound::Sample::write(v, &mut self.blob, 32).unwrap();
57 }
58 }
59 }
60}
61
62struct MediaRecorderInner {
63 stream: MediaStream,
64 active: AtomicBool,
65 recorded_data: Mutex<RecordedData>,
66 data_available_callback: Mutex<Option<BlobEventCallback>>,
67 stop_callback: Mutex<Option<EventCallback>>,
68 error_callback: Mutex<Option<ErrorEventCallback>>,
69}
70
71impl MediaRecorderInner {
72 fn record(&self, buf: AudioBuffer) {
73 let mut recorded_data = self.recorded_data.lock().unwrap();
74
75 recorded_data.encode_next(buf);
76
77 if recorded_data.blob.len() > 128 * 1024 {
78 drop(recorded_data);
79 self.flush();
80 }
81 }
82
83 fn handle_error(&self, error: Box<dyn Error + Send + Sync>) {
84 self.flush();
85
86 if let Some(f) = self.error_callback.lock().unwrap().take() {
87 (f)(ErrorEvent {
88 message: error.to_string(),
89 error: Box::new(error),
90 event: Event {
91 type_: "ErrorEvent",
92 },
93 })
94 }
95
96 self.stop();
97 }
98
99 fn flush(&self) {
100 let mut recorded_data = self.recorded_data.lock().unwrap();
101
102 let timecode = recorded_data
103 .current_timecode
104 .duration_since(recorded_data.start_timecode)
105 .as_secs_f64();
106
107 let data = std::mem::replace(&mut recorded_data.blob, Vec::with_capacity(128 * 1024));
108 if let Some(f) = self.data_available_callback.lock().unwrap().as_mut() {
109 let blob = Blob {
110 data,
111 type_: "audio/wav",
112 };
113 let event = BlobEvent {
114 blob,
115 timecode,
116 event: Event { type_: "BlobEvent" },
117 };
118 (f)(event)
119 }
120
121 recorded_data.current_timecode = Instant::now();
122 }
123
124 fn stop(&self) {
125 self.active.store(false, Ordering::SeqCst);
126
127 if let Some(f) = self.stop_callback.lock().unwrap().take() {
128 (f)(Event { type_: "StopEvent" })
129 }
130 }
131}
132
133#[non_exhaustive]
134#[derive(Debug, Clone, Default)]
135pub struct MediaRecorderOptions {
137 pub mime_type: String,
140}
141
142pub struct MediaRecorder {
163 inner: Arc<MediaRecorderInner>,
164}
165
166impl std::fmt::Debug for MediaRecorder {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.debug_struct("MediaRecorder")
169 .field("stream", &self.inner.stream)
170 .field("active", &self.inner.active)
171 .finish_non_exhaustive()
172 }
173}
174
175impl MediaRecorder {
176 pub fn is_type_supported(mime_type: &str) -> bool {
179 match mime_type {
181 "" => true, "audio/wav" => true,
183 _ => false,
184 }
185 }
186
187 pub fn new(stream: &MediaStream, options: MediaRecorderOptions) -> Self {
197 assert!(
198 Self::is_type_supported(&options.mime_type),
199 "NotSupportedError - the provided mime type is not supported"
200 );
201 let inner = MediaRecorderInner {
204 stream: stream.clone(),
205 active: AtomicBool::new(false),
206 recorded_data: Mutex::new(RecordedData::new(vec![])),
207 data_available_callback: Mutex::new(None),
208 stop_callback: Mutex::new(None),
209 error_callback: Mutex::new(None),
210 };
211
212 Self {
213 inner: Arc::new(inner),
214 }
215 }
216
217 #[allow(clippy::missing_panics_doc)]
218 pub fn set_ondataavailable<F: FnMut(BlobEvent) + Send + 'static>(&self, callback: F) {
219 *self.inner.data_available_callback.lock().unwrap() = Some(Box::new(callback));
220 }
221
222 #[allow(clippy::missing_panics_doc)]
223 pub fn clear_ondataavailable(&self) {
224 *self.inner.data_available_callback.lock().unwrap() = None;
225 }
226
227 #[allow(clippy::missing_panics_doc)]
228 pub fn set_onstop<F: FnOnce(Event) + Send + 'static>(&self, callback: F) {
229 *self.inner.stop_callback.lock().unwrap() = Some(Box::new(callback));
230 }
231
232 #[allow(clippy::missing_panics_doc)]
233 pub fn clear_onstop(&self) {
234 *self.inner.stop_callback.lock().unwrap() = None;
235 }
236
237 #[allow(clippy::missing_panics_doc)]
238 pub fn set_onerror<F: FnOnce(ErrorEvent) + Send + 'static>(&self, callback: F) {
239 *self.inner.error_callback.lock().unwrap() = Some(Box::new(callback));
240 }
241
242 #[allow(clippy::missing_panics_doc)]
243 pub fn clear_onerror(&self) {
244 *self.inner.error_callback.lock().unwrap() = None;
245 }
246
247 pub fn start(&self) {
253 let prev_active = self.inner.active.swap(true, Ordering::Relaxed);
254 assert!(
255 !prev_active,
256 "InvalidStateError - recorder has already started"
257 );
258
259 let inner = Arc::clone(&self.inner);
260 let blob = Vec::with_capacity(128 * 1024);
261
262 std::thread::spawn(move || {
263 let mut stream_iter = inner.stream.get_tracks()[0].iter();
265 let buf = match stream_iter.next() {
266 None => return,
267 Some(Err(error)) => {
268 inner.handle_error(error);
269 return;
270 }
271 Some(Ok(first)) => first,
272 };
273
274 let mut recorded_data = RecordedData::new(blob);
275 recorded_data.encode_first(buf);
276 *inner.recorded_data.lock().unwrap() = recorded_data;
277
278 for item in stream_iter {
279 if !inner.active.load(Ordering::Relaxed) {
280 return; }
282
283 let buf = match item {
284 Ok(buf) => buf,
285 Err(error) => {
286 inner.handle_error(error);
287 return;
288 }
289 };
290
291 inner.record(buf);
292 }
293
294 inner.flush();
295 inner.stop();
296 });
297 }
298
299 pub fn stop(&self) {
300 self.inner.flush();
301 self.inner.stop();
302 }
303}
304
305#[non_exhaustive]
307#[derive(Debug)]
308pub struct BlobEvent {
309 pub blob: Blob,
311 pub timecode: f64,
314 pub event: Event,
316}
317
318#[derive(Debug)]
319pub struct Blob {
320 pub data: Vec<u8>,
322 type_: &'static str,
323}
324
325impl Blob {
326 pub fn size(&self) -> usize {
328 self.data.len()
329 }
330
331 pub fn type_(&self) -> &str {
333 self.type_
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use crate::context::{BaseAudioContext, OfflineAudioContext};
340 use crate::media_streams::MediaStreamTrack;
341 use float_eq::assert_float_eq;
342 use std::io::Cursor;
343
344 use super::*;
345
346 #[test]
347 fn test_record() {
348 let data_received = Arc::new(AtomicBool::new(false));
349
350 let buffers = vec![
351 Ok(AudioBuffer::from(vec![vec![1.; 1024]], 48000.)),
352 Ok(AudioBuffer::from(vec![vec![2.; 1024]], 48000.)),
353 Ok(AudioBuffer::from(vec![vec![3.; 1024]], 48000.)),
354 ];
355 let track = MediaStreamTrack::from_iter(buffers);
356 let stream = MediaStream::from_tracks(vec![track]);
357 let recorder = MediaRecorder::new(&stream, Default::default());
358
359 {
360 let data_received = Arc::clone(&data_received);
361 recorder.set_ondataavailable(move |_| data_received.store(true, Ordering::Relaxed));
362 }
363
364 let (send, recv) = crossbeam_channel::bounded(1);
366 recorder.set_onstop(move |_| {
367 let _ = send.send(());
368 });
369
370 recorder.start();
371
372 let _ = recv.recv();
373 assert!(data_received.load(Ordering::Relaxed));
374 }
375
376 #[test]
377 fn test_error() {
378 let data_received = Arc::new(AtomicBool::new(false));
379 let error_received = Arc::new(AtomicBool::new(false));
380
381 let buffers = vec![
382 Ok(AudioBuffer::from(vec![vec![1.; 1024]], 48000.)),
383 Err(String::from("error").into()),
384 Ok(AudioBuffer::from(vec![vec![3.; 1024]], 48000.)),
385 ];
386 let track = MediaStreamTrack::from_iter(buffers);
387 let stream = MediaStream::from_tracks(vec![track]);
388 let recorder = MediaRecorder::new(&stream, Default::default());
389
390 {
391 let data_received = Arc::clone(&data_received);
392 recorder.set_ondataavailable(move |_| data_received.store(true, Ordering::Relaxed));
393 }
394 {
395 let error_received = Arc::clone(&error_received);
396 recorder.set_onerror(move |_| error_received.store(true, Ordering::Relaxed));
397 }
398
399 let (send, recv) = crossbeam_channel::bounded(1);
401 recorder.set_onstop(move |_| {
402 let _ = send.send(());
403 });
404
405 recorder.start();
406
407 let _ = recv.recv();
408 assert!(data_received.load(Ordering::Relaxed));
409 assert!(error_received.load(Ordering::Relaxed));
410 }
411
412 #[test]
413 fn test_encode_decode() {
414 let buffers = vec![Ok(AudioBuffer::from(
415 vec![vec![1.; 1024], vec![-1.; 1024]],
416 48000.,
417 ))];
418 let track = MediaStreamTrack::from_iter(buffers);
419 let stream = MediaStream::from_tracks(vec![track]);
420 let recorder = MediaRecorder::new(&stream, Default::default());
421
422 let samples: Arc<Mutex<Vec<u8>>> = Default::default();
423 {
424 let samples = Arc::clone(&samples);
425 recorder.set_ondataavailable(move |e| {
426 samples.lock().unwrap().extend_from_slice(&e.blob.data);
427 });
428 }
429
430 let (send, recv) = crossbeam_channel::bounded(1);
432 recorder.set_onstop(move |_| {
433 let _ = send.send(());
434 });
435
436 recorder.start();
437 let _ = recv.recv();
438
439 let samples = samples.lock().unwrap().clone();
440
441 let ctx = OfflineAudioContext::new(1, 128, 48000.);
442 let buf = ctx.decode_audio_data_sync(Cursor::new(samples)).unwrap();
443 assert_eq!(buf.number_of_channels(), 2);
444 assert_eq!(buf.length(), 1024);
445 assert_float_eq!(buf.get_channel_data(0), &[1.; 1024][..], abs_all <= 0.);
446 assert_float_eq!(buf.get_channel_data(1), &[-1.; 1024][..], abs_all <= 0.);
447 }
448}