Skip to content

Commit

Permalink
Merge pull request #519 from orottier/feature/media-recorder-mime-types
Browse files Browse the repository at this point in the history
MediaRecorder spec compliance
  • Loading branch information
orottier authored Jul 31, 2024
2 parents e8b5913 + d6edd54 commit 45ca745
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 19 deletions.
9 changes: 5 additions & 4 deletions examples/recorder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();

Expand Down
82 changes: 69 additions & 13 deletions src/media_recorder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
};
Expand All @@ -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();
/// ```
Expand All @@ -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),
Expand Down Expand Up @@ -269,15 +306,34 @@ impl MediaRecorder {
#[non_exhaustive]
#[derive(Debug)]
pub struct BlobEvent {
/// The encoded data
pub blob: Vec<u8>,
/// 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,
/// Inherits from this base Event
pub event: Event,
}

#[derive(Debug)]
pub struct Blob {
/// Byte sequence of this blob
pub data: Vec<u8>,
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};
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Mutex<Vec<u8>>> = 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);
});
}

Expand Down
5 changes: 3 additions & 2 deletions src/worklet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ impl<P: AudioWorkletProcessor> AudioProcessor for AudioWorkletRenderer<P> {
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;
}
Expand Down Expand Up @@ -409,7 +409,8 @@ impl<P: AudioWorkletProcessor> AudioProcessor for AudioWorkletRenderer<P> {
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;
}
Expand Down

0 comments on commit 45ca745

Please sign in to comment.