1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
//! Primitives of the MediaDevices API
//!
//! The MediaDevices interface provides access to connected media input devices like microphones.
//!
//! <https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices>
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use crate::context::{AudioContextLatencyCategory, AudioContextOptions};
use crate::media_streams::MediaStream;
/// List the available media output devices, such as speakers, headsets, loopbacks, etc
///
/// The media device_id can be used to specify the [`sink_id` of the `AudioContext`](crate::context::AudioContextOptions::sink_id)
///
/// ```no_run
/// use web_audio_api::media_devices::{enumerate_devices_sync, MediaDeviceInfoKind};
///
/// let devices = enumerate_devices_sync();
/// assert_eq!(devices[0].device_id(), "1");
/// assert_eq!(devices[0].group_id(), None);
/// assert_eq!(devices[0].kind(), MediaDeviceInfoKind::AudioOutput);
/// assert_eq!(devices[0].label(), "Macbook Pro Builtin Speakers");
/// ```
pub fn enumerate_devices_sync() -> Vec<MediaDeviceInfo> {
crate::io::enumerate_devices_sync()
}
// Internal struct to derive a stable id for a given input / output device
// cf. https://github.com/orottier/web-audio-api-rs/issues/356
#[derive(Hash)]
pub(crate) struct DeviceId {
kind: MediaDeviceInfoKind,
host: String,
device_name: String,
num_channels: u16,
index: u8,
}
impl DeviceId {
pub(crate) fn as_string(
kind: MediaDeviceInfoKind,
host: String,
device_name: String,
num_channels: u16,
index: u8,
) -> String {
let device_info = Self {
kind,
host,
device_name,
num_channels,
index,
};
let mut hasher = FxHasher::default();
device_info.hash(&mut hasher);
format!("{}", hasher.finish())
}
}
/// Describes input/output type of a media device
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum MediaDeviceInfoKind {
VideoInput,
AudioInput,
AudioOutput,
}
/// Describes a single media input or output device
///
/// Call [`enumerate_devices_sync`] to obtain a list of devices for your hardware.
#[derive(Debug)]
pub struct MediaDeviceInfo {
device_id: String,
group_id: Option<String>,
kind: MediaDeviceInfoKind,
label: String,
device: Box<dyn std::any::Any>,
}
impl MediaDeviceInfo {
pub(crate) fn new(
device_id: String,
group_id: Option<String>,
kind: MediaDeviceInfoKind,
label: String,
device: Box<dyn std::any::Any>,
) -> Self {
Self {
device_id,
group_id,
kind,
label,
device,
}
}
/// Identifier for the represented device
///
/// The current implementation is not stable across sessions so you should not persist this
/// value
pub fn device_id(&self) -> &str {
&self.device_id
}
/// Two devices have the same group identifier if they belong to the same physical device
pub fn group_id(&self) -> Option<&str> {
self.group_id.as_deref()
}
/// Enumerated value that is either "videoinput", "audioinput" or "audiooutput".
pub fn kind(&self) -> MediaDeviceInfoKind {
self.kind
}
/// Friendly label describing this device
pub fn label(&self) -> &str {
&self.label
}
pub(crate) fn device(self) -> Box<dyn std::any::Any> {
self.device
}
}
/// Dictionary used to instruct what sort of tracks to include in the [`MediaStream`] returned by
/// [`get_user_media_sync`]
pub enum MediaStreamConstraints {
Audio,
AudioWithConstraints(MediaTrackConstraints),
}
/// Desired media stream track settings for [`MediaTrackConstraints`]
#[derive(Default)]
#[non_exhaustive]
pub struct MediaTrackConstraints {
// ConstrainULong width;
// ConstrainULong height;
// ConstrainDouble aspectRatio;
// ConstrainDouble frameRate;
// ConstrainDOMString facingMode;
// ConstrainDOMString resizeMode;
pub sample_rate: Option<f32>,
// ConstrainULong sampleSize;
// ConstrainBoolean echoCancellation;
// ConstrainBoolean autoGainControl;
// ConstrainBoolean noiseSuppression;
pub latency: Option<f64>,
//ConstrainULong channelCount;
pub device_id: Option<String>,
// ConstrainDOMString groupId;
}
impl From<MediaTrackConstraints> for AudioContextOptions {
fn from(value: MediaTrackConstraints) -> Self {
let latency_hint = match value.latency {
Some(v) => AudioContextLatencyCategory::Custom(v),
None => AudioContextLatencyCategory::Interactive,
};
let sink_id = value.device_id.unwrap_or(String::from(""));
AudioContextOptions {
latency_hint,
sample_rate: value.sample_rate,
sink_id,
render_size_hint: Default::default(),
}
}
}
/// Check if the provided device_id is available for playback
///
/// It should be "" or a valid input `deviceId` returned from [`enumerate_devices_sync`]
fn is_valid_device_id(device_id: &str) -> bool {
if device_id.is_empty() {
true
} else {
enumerate_devices_sync()
.into_iter()
.filter(|d| d.kind == MediaDeviceInfoKind::AudioInput)
.any(|d| d.device_id() == device_id)
}
}
/// Prompt for permission to use a media input (audio only)
///
/// This produces a [`MediaStream`] with tracks containing the requested types of media, which can
/// be used inside a [`MediaStreamAudioSourceNode`](crate::node::MediaStreamAudioSourceNode).
///
/// It is okay for the `MediaStream` struct to go out of scope, any corresponding stream will still be
/// kept alive and emit audio buffers. Call the `close()` method if you want to stop the media
/// input and release all system resources.
///
/// This function operates synchronously, which may be undesirable on the control thread. An async
/// version is currently not implemented.
///
/// # Example
///
/// ```no_run
/// use web_audio_api::context::{BaseAudioContext, AudioContext};
/// use web_audio_api::context::{AudioContextLatencyCategory, AudioContextOptions};
/// use web_audio_api::media_devices;
/// use web_audio_api::media_devices::MediaStreamConstraints;
/// use web_audio_api::node::AudioNode;
///
/// let context = AudioContext::default();
/// let mic = media_devices::get_user_media_sync(MediaStreamConstraints::Audio);
///
/// // register as media element in the audio context
/// let background = context.create_media_stream_source(&mic);
///
/// // connect the node directly to the destination node (speakers)
/// background.connect(&context.destination());
///
/// // enjoy listening
/// std::thread::sleep(std::time::Duration::from_secs(4));
/// ```
pub fn get_user_media_sync(constraints: MediaStreamConstraints) -> MediaStream {
let mut options = match constraints {
MediaStreamConstraints::Audio => AudioContextOptions::default(),
MediaStreamConstraints::AudioWithConstraints(cs) => cs.into(),
};
if !is_valid_device_id(&options.sink_id) {
log::error!("NotFoundError: invalid deviceId {:?}", options.sink_id);
options.sink_id = String::from("");
}
crate::io::build_input(options)
}