diff --git a/examples/recorder.rs b/examples/recorder.rs index 84bf5f50..35f1e16a 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,14 +42,15 @@ 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 {}", 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 662c7f1e..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" }, }; @@ -126,18 +130,28 @@ 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()); +/// println!("Received {} bytes of data", event.blob.size()); /// }); /// recorder.start(); /// ``` @@ -159,10 +173,33 @@ 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 + 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), @@ -269,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, @@ -278,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}; @@ -298,7 +354,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); @@ -329,7 +385,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); @@ -361,13 +417,13 @@ 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(); { 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); }); } 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; }