From 01029823edb53a9a6a8dc0572cd43f83b1f3edc7 Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 24 Jul 2024 08:19:16 +0200 Subject: [PATCH 1/4] MediaRecorder.isTypeSupported to check if mime types can be used --- src/media_recorder/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/media_recorder/mod.rs b/src/media_recorder/mod.rs index 662c7f1e..792dc59e 100644 --- a/src/media_recorder/mod.rs +++ b/src/media_recorder/mod.rs @@ -159,6 +159,13 @@ impl std::fmt::Debug for MediaRecorder { } impl MediaRecorder { + /// A static method which returns a true or false value indicating if the given MIME media type + /// is supported. + pub fn is_type_supported(mime_type: &str) -> bool { + // only WAV supported for now #508 + mime_type == "audio/wav" + } + /// Creates a new `MediaRecorder` object, given a [`MediaStream`] to record. /// /// Only supports WAV file format currently. From cd21ecbd9543efefafcc440bde95224677c9c2ec Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 24 Jul 2024 17:27:46 +0200 Subject: [PATCH 2/4] MediaRecorder constructor takes options argument (with mimeType) --- examples/recorder.rs | 5 +++-- src/media_recorder/mod.rs | 42 +++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/examples/recorder.rs b/examples/recorder.rs index 84bf5f50..c3336c0b 100644 --- a/examples/recorder.rs +++ b/examples/recorder.rs @@ -2,7 +2,7 @@ use std::io::Write; use web_audio_api::context::{ AudioContext, AudioContextLatencyCategory, AudioContextOptions, BaseAudioContext, }; -use web_audio_api::media_recorder::MediaRecorder; +use web_audio_api::media_recorder::{MediaRecorder, MediaRecorderOptions}; use web_audio_api::node::{AudioNode, AudioScheduledSourceNode}; // Example showing how to record audio live from a running AudioContext. @@ -42,7 +42,8 @@ fn main() { // Start playing osc.start(); - let recorder = MediaRecorder::new(dest.stream()); + let options = MediaRecorderOptions::default(); // default to audio/wav + let recorder = MediaRecorder::new(dest.stream(), options); recorder.set_ondataavailable(move |event| { eprintln!( "timecode {:.6}, data size {}", diff --git a/src/media_recorder/mod.rs b/src/media_recorder/mod.rs index 792dc59e..e15902bf 100644 --- a/src/media_recorder/mod.rs +++ b/src/media_recorder/mod.rs @@ -126,16 +126,26 @@ impl MediaRecorderInner { } } +#[non_exhaustive] +#[derive(Debug, Clone, Default)] +/// Dictionary with media recorder options +pub struct MediaRecorderOptions { + /// The container and codec format(s) for the recording, which may include any parameters that + /// are defined for the format. Defaults to `""` which means any suitable mimeType is picked. + pub mime_type: String, +} + /// Record and encode media /// /// ```no_run /// use web_audio_api::context::AudioContext; -/// use web_audio_api::media_recorder::MediaRecorder; +/// use web_audio_api::media_recorder::{MediaRecorder, MediaRecorderOptions}; /// /// let context = AudioContext::default(); /// let output = context.create_media_stream_destination(); /// -/// let recorder = MediaRecorder::new(output.stream()); +/// let options = MediaRecorderOptions::default(); // default to audio/wav +/// let recorder = MediaRecorder::new(output.stream(), options); /// recorder.set_ondataavailable(|event| { /// println!("Received {} bytes of data", event.blob.len()); /// }); @@ -163,13 +173,29 @@ impl MediaRecorder { /// is supported. pub fn is_type_supported(mime_type: &str) -> bool { // only WAV supported for now #508 - mime_type == "audio/wav" + match mime_type { + "" => true, // we are free to pick a supported mime type + "audio/wav" => true, + _ => false, + } } /// Creates a new `MediaRecorder` object, given a [`MediaStream`] to record. /// - /// Only supports WAV file format currently. - pub fn new(stream: &MediaStream) -> Self { + /// Only supports WAV file format currently, so `options.mime_type` should be set to + /// `audio/wav` or left empty. + /// + /// # Panics + /// + /// This function will panic with a `NotSupportedError` when the provided mime type is not + /// supported. Be sure to check [`Self::is_type_supported`] before calling this constructor. + pub fn new(stream: &MediaStream, options: MediaRecorderOptions) -> Self { + assert!( + Self::is_type_supported(&options.mime_type), + "NotSupportedError - the provided mime type is not supported" + ); + // TODO #508 actually use options.mime_type + let inner = MediaRecorderInner { stream: stream.clone(), active: AtomicBool::new(false), @@ -305,7 +331,7 @@ mod tests { ]; let track = MediaStreamTrack::from_iter(buffers); let stream = MediaStream::from_tracks(vec![track]); - let recorder = MediaRecorder::new(&stream); + let recorder = MediaRecorder::new(&stream, Default::default()); { let data_received = Arc::clone(&data_received); @@ -336,7 +362,7 @@ mod tests { ]; let track = MediaStreamTrack::from_iter(buffers); let stream = MediaStream::from_tracks(vec![track]); - let recorder = MediaRecorder::new(&stream); + let recorder = MediaRecorder::new(&stream, Default::default()); { let data_received = Arc::clone(&data_received); @@ -368,7 +394,7 @@ mod tests { ))]; let track = MediaStreamTrack::from_iter(buffers); let stream = MediaStream::from_tracks(vec![track]); - let recorder = MediaRecorder::new(&stream); + let recorder = MediaRecorder::new(&stream, Default::default()); let samples: Arc>> = Default::default(); { From 1b53a060d6601192540c7e69a68ceb6e174ede1e Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 24 Jul 2024 17:41:36 +0200 Subject: [PATCH 3/4] Make media_recorder::Blob more spec compliant (to denote mimetype) --- examples/recorder.rs | 4 ++-- src/media_recorder/mod.rs | 35 +++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/examples/recorder.rs b/examples/recorder.rs index c3336c0b..35f1e16a 100644 --- a/examples/recorder.rs +++ b/examples/recorder.rs @@ -48,9 +48,9 @@ fn main() { eprintln!( "timecode {:.6}, data size {}", event.timecode, - event.blob.len() + event.blob.size() ); - std::io::stdout().write_all(&event.blob).unwrap(); + std::io::stdout().write_all(&event.blob.data).unwrap(); }); recorder.start(); diff --git a/src/media_recorder/mod.rs b/src/media_recorder/mod.rs index e15902bf..016573e5 100644 --- a/src/media_recorder/mod.rs +++ b/src/media_recorder/mod.rs @@ -104,10 +104,14 @@ impl MediaRecorderInner { .duration_since(recorded_data.start_timecode) .as_secs_f64(); - let send = std::mem::replace(&mut recorded_data.blob, Vec::with_capacity(128 * 1024)); + let data = std::mem::replace(&mut recorded_data.blob, Vec::with_capacity(128 * 1024)); if let Some(f) = self.data_available_callback.lock().unwrap().as_mut() { + let blob = Blob { + data, + type_: "audio/wav", + }; let event = BlobEvent { - blob: send, + blob, timecode, event: Event { type_: "BlobEvent" }, }; @@ -147,7 +151,7 @@ pub struct MediaRecorderOptions { /// let options = MediaRecorderOptions::default(); // default to audio/wav /// let recorder = MediaRecorder::new(output.stream(), options); /// recorder.set_ondataavailable(|event| { -/// println!("Received {} bytes of data", event.blob.len()); +/// println!("Received {} bytes of data", event.blob.size()); /// }); /// recorder.start(); /// ``` @@ -302,8 +306,8 @@ impl MediaRecorder { #[non_exhaustive] #[derive(Debug)] pub struct BlobEvent { - /// The encoded data - pub blob: Vec, + /// The encoded Blob whose type attribute indicates the encoding of the blob data. + pub blob: Blob, /// The difference between the timestamp of the first chunk in data and the timestamp of the /// first chunk in the first BlobEvent produced by this recorder pub timecode: f64, @@ -311,6 +315,25 @@ pub struct BlobEvent { pub event: Event, } +#[derive(Debug)] +pub struct Blob { + /// Byte sequence of this blob + pub data: Vec, + type_: &'static str, +} + +impl Blob { + /// Returns the size of the byte sequence in number of bytes + pub fn size(&self) -> usize { + self.data.len() + } + + /// The ASCII-encoded string in lower case representing the media type + pub fn type_(&self) -> &str { + self.type_ + } +} + #[cfg(test)] mod tests { use crate::context::{BaseAudioContext, OfflineAudioContext}; @@ -400,7 +423,7 @@ mod tests { { let samples = Arc::clone(&samples); recorder.set_ondataavailable(move |e| { - samples.lock().unwrap().extend_from_slice(&e.blob); + samples.lock().unwrap().extend_from_slice(&e.blob.data); }); } From d6edd54b9ba4cec5b8b0c362835c5cae806d8902 Mon Sep 17 00:00:00 2001 From: Otto Date: Wed, 24 Jul 2024 18:09:59 +0200 Subject: [PATCH 4/4] Unrelated clippy fix --- src/worklet.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/worklet.rs b/src/worklet.rs index 902ccd82..9231f7f4 100644 --- a/src/worklet.rs +++ b/src/worklet.rs @@ -365,7 +365,7 @@ impl AudioProcessor for AudioWorkletRenderer

{ let c = input.number_of_channels(); let (left, right) = inputs_flat.split_at(c); // SAFETY - see comments above - let left_static = unsafe { std::mem::transmute(left) }; + let left_static = unsafe { std::mem::transmute::<&[&[f32]], &[&[f32]]>(left) }; self.inputs_grouped.push(left_static); inputs_flat = right; } @@ -409,7 +409,8 @@ impl AudioProcessor for AudioWorkletRenderer

{ for c in output_channel_count { let (left, right) = outputs_flat.split_at_mut(*c); // SAFETY - see comments above - let left_static = unsafe { std::mem::transmute(left) }; + let left_static = + unsafe { std::mem::transmute::<&mut [&mut [f32]], &mut [&mut [f32]]>(left) }; self.outputs_grouped.push(left_static); outputs_flat = right; }