use std::{cell::RefCell, rc::Rc};
use derive_more::with_trait::Display;
use futures::stream::LocalBoxStream;
use medea_client_api_proto as proto;
use medea_reactive::ObservableCell;
use crate::{
media::{MediaKind, track::MediaStreamTrackState},
peer::{
LocalStreamUpdateCriteria, MediaState, media_exchange_state, mute_state,
},
platform,
};
#[derive(Clone, Copy, Debug, Display, Eq, PartialEq)]
#[repr(u8)]
pub enum FacingMode {
#[display("user")]
User = 0,
#[display("environment")]
Environment = 1,
#[display("left")]
Left = 2,
#[display("right")]
Right = 3,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum NoiseSuppressionLevel {
Low = 0,
Moderate = 1,
High = 2,
VeryHigh = 3,
}
#[derive(Clone, Debug, Default)]
pub struct LocalTracksConstraints(Rc<RefCell<MediaStreamSettings>>);
#[expect( // intended: all fields have the same postfix `enabled`
clippy::struct_field_names,
reason = "intended: all fields have the same postfix `enabled`"
)]
#[derive(Debug)]
pub struct RecvConstraints {
audio_device_enabled: ObservableCell<bool>,
audio_display_enabled: ObservableCell<bool>,
video_device_enabled: ObservableCell<bool>,
video_display_enabled: ObservableCell<bool>,
}
impl Clone for RecvConstraints {
fn clone(&self) -> Self {
Self {
audio_device_enabled: ObservableCell::new(
self.audio_device_enabled.get(),
),
audio_display_enabled: ObservableCell::new(
self.audio_display_enabled.get(),
),
video_device_enabled: ObservableCell::new(
self.video_device_enabled.get(),
),
video_display_enabled: ObservableCell::new(
self.video_display_enabled.get(),
),
}
}
}
impl Default for RecvConstraints {
fn default() -> Self {
Self {
audio_device_enabled: ObservableCell::new(true),
audio_display_enabled: ObservableCell::new(true),
video_device_enabled: ObservableCell::new(true),
video_display_enabled: ObservableCell::new(true),
}
}
}
impl RecvConstraints {
pub fn set_enabled(
&self,
enabled: bool,
kind: MediaKind,
source_kind: Option<proto::MediaSourceKind>,
) {
match kind {
MediaKind::Audio => source_kind.map_or_else(
|| {
self.audio_device_enabled.set(enabled);
self.audio_display_enabled.set(enabled);
},
|sk| match sk {
proto::MediaSourceKind::Device => {
self.audio_device_enabled.set(enabled);
}
proto::MediaSourceKind::Display => {
self.audio_display_enabled.set(enabled);
}
},
),
MediaKind::Video => source_kind.map_or_else(
|| {
self.video_device_enabled.set(enabled);
self.video_display_enabled.set(enabled);
},
|sk| match sk {
proto::MediaSourceKind::Device => {
self.video_device_enabled.set(enabled);
}
proto::MediaSourceKind::Display => {
self.video_display_enabled.set(enabled);
}
},
),
}
}
pub fn is_audio_device_enabled(&self) -> bool {
self.audio_device_enabled.get()
}
pub fn is_audio_display_enabled(&self) -> bool {
self.audio_display_enabled.get()
}
pub fn is_video_device_enabled(&self) -> bool {
self.video_device_enabled.get()
}
pub fn is_video_display_enabled(&self) -> bool {
self.video_display_enabled.get()
}
pub fn on_audio_device_enabled_change(
&self,
) -> LocalBoxStream<'static, bool> {
self.audio_device_enabled.subscribe()
}
pub fn on_audio_display_enabled_change(
&self,
) -> LocalBoxStream<'static, bool> {
self.audio_display_enabled.subscribe()
}
pub fn on_video_device_enabled_change(
&self,
) -> LocalBoxStream<'static, bool> {
self.video_device_enabled.subscribe()
}
pub fn on_video_display_enabled_change(
&self,
) -> LocalBoxStream<'static, bool> {
self.video_display_enabled.subscribe()
}
}
#[cfg(feature = "mockable")]
impl From<MediaStreamSettings> for LocalTracksConstraints {
fn from(from: MediaStreamSettings) -> Self {
Self(Rc::new(RefCell::new(from)))
}
}
impl LocalTracksConstraints {
#[must_use]
pub fn calculate_kinds_diff(
&self,
settings: &MediaStreamSettings,
) -> LocalStreamUpdateCriteria {
self.0.borrow().calculate_kinds_diff(settings)
}
pub fn constrain(&self, other: MediaStreamSettings) {
self.0.borrow_mut().constrain(other);
}
#[must_use]
pub fn inner(&self) -> MediaStreamSettings {
self.0.borrow().clone()
}
pub fn set_media_state(
&self,
state: MediaState,
kind: MediaKind,
source_kind: Option<proto::MediaSourceKind>,
) {
self.0.borrow_mut().set_track_media_state(state, kind, source_kind);
}
pub fn set_media_exchange_state_by_kinds(
&self,
state: media_exchange_state::Stable,
kinds: LocalStreamUpdateCriteria,
) {
self.0.borrow_mut().set_media_exchange_state_by_kinds(state, kinds);
}
#[must_use]
pub fn enabled(&self, kind: &proto::MediaType) -> bool {
self.0.borrow().enabled(kind)
}
#[must_use]
pub fn muted(&self, kind: &proto::MediaType) -> bool {
self.0.borrow().muted(kind)
}
#[must_use]
pub fn is_track_enabled_and_constrained(
&self,
kind: MediaKind,
source: Option<proto::MediaSourceKind>,
) -> bool {
self.0.borrow().is_track_enabled_and_constrained(kind, source)
}
#[must_use]
pub fn is_track_enabled(
&self,
kind: MediaKind,
source: Option<proto::MediaSourceKind>,
) -> bool {
self.0.borrow().is_track_enabled(kind, source)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MediaTrackConstraints<C> {
constraints: Option<C>,
enabled: bool,
muted: bool,
}
impl<C: Default> Default for MediaTrackConstraints<C> {
fn default() -> Self {
Self { constraints: Some(C::default()), enabled: true, muted: false }
}
}
impl<C> MediaTrackConstraints<C> {
const fn enabled(&self) -> bool {
self.enabled && self.is_constrained()
}
fn set(&mut self, cons: C) {
self.constraints = Some(cons);
}
fn unconstrain(&mut self) {
drop(self.constraints.take());
}
const fn is_constrained(&self) -> bool {
self.constraints.is_some()
}
fn constrain(&mut self, other: Self) {
self.enabled &= other.enabled;
self.constraints = other.constraints;
}
}
impl MediaTrackConstraints<DeviceAudioTrackConstraints> {
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
if let Some(constraints) = &self.constraints {
self.enabled() && constraints.satisfies(track).await
} else {
false
}
}
}
impl MediaTrackConstraints<DisplayAudioTrackConstraints> {
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
if let Some(constraints) = &self.constraints {
self.enabled() && constraints.satisfies(track).await
} else {
false
}
}
}
impl MediaTrackConstraints<DeviceVideoTrackConstraints> {
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
if let Some(constraints) = &self.constraints {
self.enabled() && constraints.satisfies(track).await
} else {
false
}
}
}
impl MediaTrackConstraints<DisplayVideoTrackConstraints> {
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
if let Some(constraints) = &self.constraints {
self.enabled() && constraints.satisfies(track).await
} else {
false
}
}
}
async fn satisfies_track(
track: &platform::MediaStreamTrack,
kind: MediaKind,
source_kind: proto::MediaSourceKind,
) -> bool {
track.kind() == kind
&& track.ready_state().await == MediaStreamTrackState::Live
&& track.source_kind() == Some(source_kind.into())
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MediaStreamSettings {
device_audio: MediaTrackConstraints<DeviceAudioTrackConstraints>,
display_audio: MediaTrackConstraints<DisplayAudioTrackConstraints>,
device_video: MediaTrackConstraints<DeviceVideoTrackConstraints>,
display_video: MediaTrackConstraints<DisplayVideoTrackConstraints>,
}
impl MediaStreamSettings {
#[must_use]
pub const fn new() -> Self {
Self {
device_audio: MediaTrackConstraints {
enabled: true,
constraints: None,
muted: false,
},
display_audio: MediaTrackConstraints {
enabled: true,
constraints: None,
muted: false,
},
display_video: MediaTrackConstraints {
enabled: true,
constraints: None,
muted: false,
},
device_video: MediaTrackConstraints {
enabled: true,
constraints: None,
muted: false,
},
}
}
pub fn device_audio(&mut self, constraints: DeviceAudioTrackConstraints) {
self.device_audio.set(constraints);
}
pub fn display_audio(&mut self, constraints: DisplayAudioTrackConstraints) {
self.display_audio.set(constraints);
}
pub fn device_video(&mut self, constraints: DeviceVideoTrackConstraints) {
self.device_video.set(constraints);
}
pub fn display_video(&mut self, constraints: DisplayVideoTrackConstraints) {
self.display_video.set(constraints);
}
pub async fn unconstrain_if_satisfies_video<T>(&mut self, track: T) -> bool
where
T: AsRef<platform::MediaStreamTrack>,
{
if self.device_video.satisfies(&track).await {
self.device_video.unconstrain();
true
} else if self.display_video.satisfies(&track).await {
self.display_video.unconstrain();
true
} else {
false
}
}
pub async fn unconstrain_if_satisfies_audio<T>(&mut self, track: T) -> bool
where
T: AsRef<platform::MediaStreamTrack>,
{
if self.device_audio.satisfies(&track).await {
self.device_audio.unconstrain();
true
} else if self.display_audio.satisfies(&track).await {
self.display_audio.unconstrain();
true
} else {
false
}
}
#[must_use]
pub fn calculate_kinds_diff(
&self,
another: &Self,
) -> LocalStreamUpdateCriteria {
let mut kinds = LocalStreamUpdateCriteria::empty();
if self.device_video != another.device_video {
kinds.add(MediaKind::Video, proto::MediaSourceKind::Device);
}
if self.display_video != another.display_video {
kinds.add(MediaKind::Video, proto::MediaSourceKind::Display);
}
if self.device_audio != another.device_audio {
kinds.add(MediaKind::Audio, proto::MediaSourceKind::Device);
}
if self.display_audio != another.display_audio {
kinds.add(MediaKind::Audio, proto::MediaSourceKind::Display);
}
kinds
}
#[must_use]
pub const fn get_device_audio(
&self,
) -> Option<&DeviceAudioTrackConstraints> {
self.device_audio.constraints.as_ref()
}
#[must_use]
pub const fn get_display_audio(
&self,
) -> Option<&DisplayAudioTrackConstraints> {
self.display_audio.constraints.as_ref()
}
#[must_use]
pub const fn get_display_video(
&self,
) -> Option<&DisplayVideoTrackConstraints> {
self.display_video.constraints.as_ref()
}
#[must_use]
pub const fn get_device_video(
&self,
) -> Option<&DeviceVideoTrackConstraints> {
self.device_video.constraints.as_ref()
}
pub fn set_track_media_state(
&mut self,
state: MediaState,
kind: MediaKind,
source_kind: Option<proto::MediaSourceKind>,
) {
match kind {
MediaKind::Audio => match state {
MediaState::Mute(muted) => {
self.set_audio_muted(
muted == mute_state::Stable::Muted,
source_kind,
);
}
MediaState::MediaExchange(media_exchange) => {
self.set_audio_publish(
media_exchange == media_exchange_state::Stable::Enabled,
source_kind,
);
}
},
MediaKind::Video => match state {
MediaState::Mute(muted) => {
self.set_video_muted(
muted == mute_state::Stable::Muted,
source_kind,
);
}
MediaState::MediaExchange(media_exchange) => {
self.set_video_publish(
media_exchange == media_exchange_state::Stable::Enabled,
source_kind,
);
}
},
}
}
pub fn set_media_exchange_state_by_kinds(
&mut self,
state: media_exchange_state::Stable,
kinds: LocalStreamUpdateCriteria,
) {
let enabled = state == media_exchange_state::Stable::Enabled;
if kinds.has(MediaKind::Audio, proto::MediaSourceKind::Device) {
self.set_audio_publish(
enabled,
Some(proto::MediaSourceKind::Device),
);
}
if kinds.has(MediaKind::Audio, proto::MediaSourceKind::Display) {
self.set_audio_publish(
enabled,
Some(proto::MediaSourceKind::Display),
);
}
if kinds.has(MediaKind::Video, proto::MediaSourceKind::Device) {
self.set_video_publish(
enabled,
Some(proto::MediaSourceKind::Device),
);
}
if kinds.has(MediaKind::Video, proto::MediaSourceKind::Display) {
self.set_video_publish(
enabled,
Some(proto::MediaSourceKind::Display),
);
}
}
const fn set_audio_muted(
&mut self,
muted: bool,
source_kind: Option<proto::MediaSourceKind>,
) {
match source_kind {
None => {
self.display_audio.muted = muted;
self.device_audio.muted = muted;
}
Some(proto::MediaSourceKind::Device) => {
self.device_audio.muted = muted;
}
Some(proto::MediaSourceKind::Display) => {
self.display_audio.muted = muted;
}
}
}
const fn set_video_muted(
&mut self,
muted: bool,
source_kind: Option<proto::MediaSourceKind>,
) {
match source_kind {
None => {
self.display_video.muted = muted;
self.device_video.muted = muted;
}
Some(proto::MediaSourceKind::Device) => {
self.device_video.muted = muted;
}
Some(proto::MediaSourceKind::Display) => {
self.display_video.muted = muted;
}
}
}
pub const fn set_audio_publish(
&mut self,
enabled: bool,
source_kind: Option<proto::MediaSourceKind>,
) {
match source_kind {
None => {
self.display_audio.enabled = enabled;
self.device_audio.enabled = enabled;
}
Some(proto::MediaSourceKind::Device) => {
self.device_audio.enabled = enabled;
}
Some(proto::MediaSourceKind::Display) => {
self.display_audio.enabled = enabled;
}
}
}
pub const fn set_video_publish(
&mut self,
enabled: bool,
source_kind: Option<proto::MediaSourceKind>,
) {
match source_kind {
None => {
self.display_video.enabled = enabled;
self.device_video.enabled = enabled;
}
Some(proto::MediaSourceKind::Device) => {
self.device_video.enabled = enabled;
}
Some(proto::MediaSourceKind::Display) => {
self.display_video.enabled = enabled;
}
}
}
#[must_use]
pub const fn is_device_audio_enabled(&self) -> bool {
self.device_audio.enabled()
}
#[must_use]
pub const fn is_display_audio_enabled(&self) -> bool {
self.display_audio.enabled()
}
#[must_use]
pub const fn is_device_video_enabled(&self) -> bool {
self.device_video.enabled()
}
#[must_use]
pub const fn is_display_video_enabled(&self) -> bool {
self.display_video.enabled()
}
#[must_use]
pub const fn enabled(&self, kind: &proto::MediaType) -> bool {
match kind {
proto::MediaType::Video(video) => self
.is_track_enabled_and_constrained(
MediaKind::Video,
Some(video.source_kind),
),
proto::MediaType::Audio(audio) => self
.is_track_enabled_and_constrained(
MediaKind::Audio,
Some(audio.source_kind),
),
}
}
#[must_use]
pub const fn muted(&self, kind: &proto::MediaType) -> bool {
match kind {
proto::MediaType::Video(video) => match video.source_kind {
proto::MediaSourceKind::Device => self.device_video.muted,
proto::MediaSourceKind::Display => self.display_video.muted,
},
proto::MediaType::Audio(audio) => match audio.source_kind {
proto::MediaSourceKind::Device => self.device_audio.muted,
proto::MediaSourceKind::Display => self.display_audio.muted,
},
}
}
#[must_use]
pub const fn is_track_enabled_and_constrained(
&self,
kind: MediaKind,
source: Option<proto::MediaSourceKind>,
) -> bool {
match (kind, source) {
(MediaKind::Video, Some(proto::MediaSourceKind::Device)) => {
self.device_video.enabled()
}
(MediaKind::Video, Some(proto::MediaSourceKind::Display)) => {
self.display_video.enabled()
}
(MediaKind::Video, None) => {
self.display_video.enabled() && self.device_video.enabled()
}
(MediaKind::Audio, Some(proto::MediaSourceKind::Device)) => {
self.device_audio.enabled()
}
(MediaKind::Audio, Some(proto::MediaSourceKind::Display)) => {
self.display_audio.enabled()
}
(MediaKind::Audio, None) => {
self.device_audio.enabled() && self.display_audio.enabled()
}
}
}
#[must_use]
pub const fn is_track_enabled(
&self,
kind: MediaKind,
source: Option<proto::MediaSourceKind>,
) -> bool {
match (kind, source) {
(MediaKind::Video, Some(proto::MediaSourceKind::Device)) => {
self.device_video.enabled
}
(MediaKind::Video, Some(proto::MediaSourceKind::Display)) => {
self.display_video.enabled
}
(MediaKind::Video, None) => {
self.display_video.enabled && self.device_video.enabled
}
(MediaKind::Audio, Some(proto::MediaSourceKind::Device)) => {
self.device_audio.enabled
}
(MediaKind::Audio, Some(proto::MediaSourceKind::Display)) => {
self.display_audio.enabled
}
(MediaKind::Audio, None) => {
self.device_audio.enabled && self.display_audio.enabled
}
}
}
fn constrain(&mut self, other: Self) {
self.device_audio.constrain(other.device_audio);
self.display_audio.constrain(other.display_audio);
self.display_video.constrain(other.display_video);
self.device_video.constrain(other.device_video);
}
}
#[derive(Debug)]
pub enum MultiSourceTracksConstraints {
Device(platform::MediaStreamConstraints),
Display(platform::DisplayMediaStreamConstraints),
DeviceAndDisplay(
platform::MediaStreamConstraints,
platform::DisplayMediaStreamConstraints,
),
}
impl From<MediaStreamSettings> for Option<MultiSourceTracksConstraints> {
fn from(constraints: MediaStreamSettings) -> Self {
let is_device_video_enabled = constraints.is_device_video_enabled();
let is_display_video_enabled = constraints.is_display_video_enabled();
let is_device_audio_enabled = constraints.is_device_audio_enabled();
let is_display_audio_enabled = constraints.is_display_audio_enabled();
let mut device_cons = None;
let mut display_cons = None;
if is_device_video_enabled {
if let Some(device_video_cons) =
constraints.device_video.constraints
{
device_cons
.get_or_insert_with(platform::MediaStreamConstraints::new)
.video(device_video_cons);
}
}
if is_display_video_enabled {
if let Some(display_video_cons) =
constraints.display_video.constraints
{
display_cons
.get_or_insert_with(
platform::DisplayMediaStreamConstraints::new,
)
.video(display_video_cons);
}
}
if is_device_audio_enabled {
if let Some(device_audio_cons) =
constraints.device_audio.constraints
{
device_cons
.get_or_insert_with(platform::MediaStreamConstraints::new)
.audio(device_audio_cons);
}
}
if is_display_audio_enabled {
if let Some(display_audio_cons) =
constraints.display_audio.constraints
{
display_cons
.get_or_insert_with(
platform::DisplayMediaStreamConstraints::new,
)
.audio(display_audio_cons);
}
}
match (device_cons, display_cons) {
(Some(device_cons), Some(display_cons)) => {
Some(MultiSourceTracksConstraints::DeviceAndDisplay(
device_cons,
display_cons,
))
}
(Some(device_cons), None) => {
Some(MultiSourceTracksConstraints::Device(device_cons))
}
(None, Some(display_cons)) => {
Some(MultiSourceTracksConstraints::Display(display_cons))
}
(None, None) => None,
}
}
}
#[derive(Clone, Debug)]
pub enum VideoSource {
Device(DeviceVideoTrackConstraints),
Display(DisplayVideoTrackConstraints),
}
impl VideoSource {
#[expect(clippy::use_self, reason = "because of `const` only")]
#[must_use]
pub const fn required(&self) -> bool {
match self {
VideoSource::Device(device) => device.required,
VideoSource::Display(display) => display.required,
}
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
match self {
Self::Display(display) => display.satisfies(&track).await,
Self::Device(device) => device.satisfies(track).await,
}
}
}
impl From<proto::VideoSettings> for VideoSource {
fn from(settings: proto::VideoSettings) -> Self {
match settings.source_kind {
proto::MediaSourceKind::Device => {
Self::Device(DeviceVideoTrackConstraints {
device_id: None,
facing_mode: None,
width: None,
height: None,
required: settings.required,
})
}
proto::MediaSourceKind::Display => {
Self::Display(DisplayVideoTrackConstraints {
height: None,
width: None,
frame_rate: None,
required: settings.required,
device_id: None,
})
}
}
}
}
#[derive(Clone, Debug)]
pub enum AudioSource {
Device(DeviceAudioTrackConstraints),
Display(DisplayAudioTrackConstraints),
}
impl AudioSource {
#[expect(clippy::use_self, reason = "because of `const` only")]
#[must_use]
pub const fn required(&self) -> bool {
match self {
AudioSource::Device(device) => device.required,
AudioSource::Display(display) => display.required,
}
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
match self {
Self::Display(display) => display.satisfies(&track).await,
Self::Device(device) => device.satisfies(track).await,
}
}
}
impl From<proto::AudioSettings> for AudioSource {
fn from(settings: proto::AudioSettings) -> Self {
match settings.source_kind {
proto::MediaSourceKind::Device => {
Self::Device(DeviceAudioTrackConstraints::from(settings))
}
proto::MediaSourceKind::Display => {
Self::Display(DisplayAudioTrackConstraints::from(settings))
}
}
}
}
#[derive(Clone, Debug)]
pub enum TrackConstraints {
Audio(AudioSource),
Video(VideoSource),
}
impl TrackConstraints {
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
match self {
Self::Audio(audio) => audio.satisfies(&track).await,
Self::Video(video) => video.satisfies(&track).await,
}
}
#[expect(clippy::use_self, reason = "because of `const` only")]
#[must_use]
pub const fn required(&self) -> bool {
match self {
TrackConstraints::Video(video) => video.required(),
TrackConstraints::Audio(audio) => audio.required(),
}
}
#[expect(clippy::use_self, reason = "because of `const` only")]
#[must_use]
pub const fn media_source_kind(&self) -> proto::MediaSourceKind {
match &self {
TrackConstraints::Audio(AudioSource::Device(..))
| TrackConstraints::Video(VideoSource::Device(..)) => {
proto::MediaSourceKind::Device
}
TrackConstraints::Audio(AudioSource::Display(..))
| TrackConstraints::Video(VideoSource::Display(..)) => {
proto::MediaSourceKind::Display
}
}
}
#[expect(clippy::use_self, reason = "because of `const` only")]
#[must_use]
pub const fn media_kind(&self) -> MediaKind {
match &self {
TrackConstraints::Audio(..) => MediaKind::Audio,
TrackConstraints::Video(..) => MediaKind::Video,
}
}
}
impl From<proto::MediaType> for TrackConstraints {
fn from(caps: proto::MediaType) -> Self {
match caps {
proto::MediaType::Audio(audio) => Self::Audio(audio.into()),
proto::MediaType::Video(video) => Self::Video(video.into()),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeviceAudioTrackConstraints {
pub device_id: Option<ConstrainString<String>>,
pub required: bool,
pub auto_gain_control: Option<ConstrainBoolean>,
pub noise_suppression: Option<ConstrainBoolean>,
pub noise_suppression_level: Option<NoiseSuppressionLevel>,
pub echo_cancellation: Option<ConstrainBoolean>,
pub high_pass_filter: Option<ConstrainBoolean>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DisplayAudioTrackConstraints {
pub required: bool,
}
impl DeviceAudioTrackConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn device_id(&mut self, device_id: String) {
self.device_id = Some(ConstrainString::Exact(device_id));
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
let track = track.as_ref();
if !satisfies_track(
track,
MediaKind::Audio,
proto::MediaSourceKind::Device,
)
.await
{
return false;
}
if !ConstrainString::satisfies(
self.device_id.as_ref(),
track.device_id().as_ref(),
) {
return false;
}
if !track.is_audio_processing_available() {
return true;
}
if let Some(ConstrainBoolean::Exact(ns_caps)) = &self.noise_suppression
{
if let Ok(ns_enabled) = track.is_noise_suppression_enabled().await {
if *ns_caps != ns_enabled {
return false;
}
}
}
if let Some(ConstrainBoolean::Exact(aec_caps)) = &self.echo_cancellation
{
if let Ok(aec_enabled) = track.is_echo_cancellation_enabled().await
{
if *aec_caps != aec_enabled {
return false;
}
}
}
if let Some(ConstrainBoolean::Exact(agc_caps)) = &self.auto_gain_control
{
if let Ok(agc_enabled) = track.is_auto_gain_control_enabled().await
{
if *agc_caps != agc_enabled {
return false;
}
}
}
if let Some(ConstrainBoolean::Exact(hpf_caps)) = &self.high_pass_filter
{
if let Ok(hpf_enabled) = track.is_high_pass_filter_enabled().await {
if *hpf_caps != hpf_enabled {
return false;
}
}
}
true
}
pub fn merge(&mut self, another: Self) {
if self.device_id.is_none() && another.device_id.is_some() {
self.device_id = another.device_id;
}
if !self.required && another.required {
self.required = another.required;
}
if self.auto_gain_control.is_none()
&& another.auto_gain_control.is_some()
{
self.auto_gain_control = another.auto_gain_control;
}
if self.noise_suppression.is_none()
&& another.noise_suppression.is_some()
{
self.noise_suppression = another.noise_suppression;
}
if self.noise_suppression_level.is_none()
&& another.noise_suppression_level.is_some()
{
self.noise_suppression_level = another.noise_suppression_level;
}
if self.echo_cancellation.is_none()
&& another.echo_cancellation.is_some()
{
self.echo_cancellation = another.echo_cancellation;
}
if self.high_pass_filter.is_none() && another.high_pass_filter.is_some()
{
self.high_pass_filter = another.high_pass_filter;
}
}
#[must_use]
pub const fn required(&self) -> bool {
self.required
}
}
impl DisplayAudioTrackConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn required(&self) -> bool {
self.required
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
let track = track.as_ref();
satisfies_track(
track,
MediaKind::Audio,
proto::MediaSourceKind::Display,
)
.await
}
pub const fn merge(&mut self, another: Self) {
if !self.required && another.required {
self.required = another.required;
}
}
}
impl From<proto::AudioSettings> for DeviceAudioTrackConstraints {
fn from(caps: proto::AudioSettings) -> Self {
Self {
required: caps.required,
device_id: None,
auto_gain_control: None,
noise_suppression: None,
noise_suppression_level: None,
echo_cancellation: None,
high_pass_filter: None,
}
}
}
impl From<proto::AudioSettings> for DisplayAudioTrackConstraints {
fn from(caps: proto::AudioSettings) -> Self {
Self { required: caps.required }
}
}
impl AsRef<str> for FacingMode {
fn as_ref(&self) -> &str {
match self {
Self::User => "user",
Self::Environment => "environment",
Self::Left => "left",
Self::Right => "right",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConstrainU32 {
Exact(u32),
Ideal(u32),
Range(u32, u32),
}
impl ConstrainU32 {
fn satisfies(this: Option<Self>, setting: Option<u32>) -> bool {
match this {
None | Some(Self::Ideal(_)) => true,
Some(Self::Exact(exact)) => setting.is_some_and(|val| val == exact),
Some(Self::Range(start, end)) => {
setting.is_some_and(|val| val >= start && val <= end)
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConstrainString<T> {
Exact(T),
Ideal(T),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConstrainBoolean {
Exact(bool),
Ideal(bool),
}
impl<T: AsRef<str>> ConstrainString<T> {
fn satisfies(this: Option<&Self>, setting: Option<&T>) -> bool {
match this {
None | Some(Self::Ideal(..)) => true,
Some(Self::Exact(constrain)) => {
setting.is_some_and(|val| val.as_ref() == constrain.as_ref())
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DeviceVideoTrackConstraints {
pub required: bool,
pub device_id: Option<ConstrainString<String>>,
pub facing_mode: Option<ConstrainString<FacingMode>>,
pub height: Option<ConstrainU32>,
pub width: Option<ConstrainU32>,
}
impl DeviceVideoTrackConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn device_id(&mut self, device_id: String) {
self.device_id = Some(ConstrainString::Exact(device_id));
}
pub const fn exact_facing_mode(&mut self, facing_mode: FacingMode) {
self.facing_mode = Some(ConstrainString::Exact(facing_mode));
}
pub const fn ideal_facing_mode(&mut self, facing_mode: FacingMode) {
self.facing_mode = Some(ConstrainString::Ideal(facing_mode));
}
pub const fn exact_height(&mut self, height: u32) {
self.height = Some(ConstrainU32::Exact(height));
}
pub const fn ideal_height(&mut self, height: u32) {
self.height = Some(ConstrainU32::Ideal(height));
}
pub const fn height_in_range(&mut self, min: u32, max: u32) {
self.height = Some(ConstrainU32::Range(min, max));
}
pub const fn exact_width(&mut self, width: u32) {
self.width = Some(ConstrainU32::Exact(width));
}
pub const fn ideal_width(&mut self, width: u32) {
self.width = Some(ConstrainU32::Ideal(width));
}
pub const fn width_in_range(&mut self, min: u32, max: u32) {
self.width = Some(ConstrainU32::Range(min, max));
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
let track = track.as_ref();
satisfies_track(track, MediaKind::Video, proto::MediaSourceKind::Device)
.await
&& ConstrainString::satisfies(
self.device_id.as_ref(),
track.device_id().as_ref(),
)
&& ConstrainString::satisfies(
self.facing_mode.as_ref(),
track.facing_mode().as_ref(),
)
&& ConstrainU32::satisfies(self.height, track.height())
&& ConstrainU32::satisfies(self.width, track.width())
}
pub fn merge(&mut self, another: Self) {
if self.device_id.is_none() && another.device_id.is_some() {
self.device_id = another.device_id;
}
if !self.required && another.required {
self.required = another.required;
}
if self.facing_mode.is_none() && another.facing_mode.is_some() {
self.facing_mode = another.facing_mode;
}
if self.height.is_none() && another.height.is_some() {
self.height = another.height;
}
if self.width.is_none() && another.width.is_some() {
self.width = another.width;
}
}
#[must_use]
pub const fn required(&self) -> bool {
self.required
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DisplayVideoTrackConstraints {
pub required: bool,
pub device_id: Option<ConstrainString<String>>,
pub height: Option<ConstrainU32>,
pub width: Option<ConstrainU32>,
pub frame_rate: Option<ConstrainU32>,
}
impl DisplayVideoTrackConstraints {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub async fn satisfies<T: AsRef<platform::MediaStreamTrack>>(
&self,
track: T,
) -> bool {
let track = track.as_ref();
satisfies_track(
track,
MediaKind::Video,
proto::MediaSourceKind::Display,
)
.await
&& ConstrainString::satisfies(
self.device_id.as_ref(),
track.device_id().as_ref(),
)
&& ConstrainU32::satisfies(self.height, track.height())
&& ConstrainU32::satisfies(self.width, track.width())
}
pub fn merge(&mut self, another: Self) {
if self.device_id.is_none() && another.device_id.is_some() {
self.device_id = another.device_id;
}
if !self.required && another.required {
self.required = another.required;
}
if self.height.is_none() && another.height.is_some() {
self.height = another.height;
}
if self.width.is_none() && another.width.is_some() {
self.width = another.width;
}
if self.frame_rate.is_none() && another.frame_rate.is_some() {
self.frame_rate = another.frame_rate;
}
}
pub const fn exact_height(&mut self, height: u32) {
self.height = Some(ConstrainU32::Exact(height));
}
pub const fn ideal_height(&mut self, height: u32) {
self.height = Some(ConstrainU32::Ideal(height));
}
pub const fn exact_width(&mut self, width: u32) {
self.width = Some(ConstrainU32::Exact(width));
}
pub const fn ideal_width(&mut self, width: u32) {
self.width = Some(ConstrainU32::Ideal(width));
}
pub fn device_id(&mut self, device_id: String) {
self.device_id = Some(ConstrainString::Exact(device_id));
}
pub const fn exact_frame_rate(&mut self, frame_rate: u32) {
self.frame_rate = Some(ConstrainU32::Exact(frame_rate));
}
pub const fn ideal_frame_rate(&mut self, frame_rate: u32) {
self.frame_rate = Some(ConstrainU32::Ideal(frame_rate));
}
#[must_use]
pub const fn required(&self) -> bool {
self.required
}
}