use std::{future::Future, rc::Rc};
use derive_more::From;
use js_sys::Array;
use medea_client_api_proto::EncodingParameters;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
RtcRtpEncodingParameters, RtcRtpTransceiver, RtcRtpTransceiverInit,
};
use crate::{
media::track::local,
platform::{
send_encoding_parameters::SendEncodingParameters,
wasm::codec_capability::CodecCapability, Error, TransceiverDirection,
},
};
use super::get_property_by_name;
#[derive(Debug)]
pub struct TransceiverInit(RtcRtpTransceiverInit);
impl TransceiverInit {
#[must_use]
pub fn new(direction: TransceiverDirection) -> Self {
let mut init = RtcRtpTransceiverInit::new();
_ = init.direction(direction.into());
Self(init)
}
#[must_use]
pub const fn handle(&self) -> &RtcRtpTransceiverInit {
&self.0
}
pub fn sending_encodings(
&mut self,
encodings: Vec<SendEncodingParameters>,
) {
let send_encoding = ::js_sys::Array::new();
for enc in encodings {
_ = send_encoding.push(enc.handle());
}
_ = self.0.send_encodings(&send_encoding);
}
}
#[derive(Clone, Debug, From)]
pub struct Transceiver(RtcRtpTransceiver);
impl Transceiver {
fn direction(&self) -> TransceiverDirection {
TransceiverDirection::from(self.0.direction())
}
pub fn set_recv(&self, active: bool) -> impl Future<Output = ()> + 'static {
let transceiver = self.0.clone();
async move {
let current_direction =
TransceiverDirection::from(transceiver.direction());
let new_direction = if active {
current_direction | TransceiverDirection::RECV
} else {
current_direction - TransceiverDirection::RECV
};
transceiver.set_direction(new_direction.into());
}
}
pub fn set_send(&self, active: bool) -> impl Future<Output = ()> + 'static {
let transceiver = self.0.clone();
async move {
let current_direction =
TransceiverDirection::from(transceiver.direction());
let new_direction = if active {
current_direction | TransceiverDirection::SEND
} else {
current_direction - TransceiverDirection::SEND
};
transceiver.set_direction(new_direction.into());
}
}
#[allow(clippy::unused_async)] pub async fn has_direction(&self, direction: TransceiverDirection) -> bool {
self.direction().contains(direction)
}
pub async fn set_send_track(
&self,
new_track: Option<&Rc<local::Track>>,
) -> Result<(), Error> {
drop(
JsFuture::from(self.0.sender().replace_track(
new_track.map(|track| (**track).as_ref().as_ref()),
))
.await?,
);
Ok(())
}
#[must_use]
pub fn mid(&self) -> Option<String> {
self.0.mid()
}
#[must_use]
pub fn is_stopped(&self) -> bool {
self.0.stopped()
}
#[allow(clippy::missing_panics_doc)]
pub async fn update_send_encodings(
&self,
encodings: Vec<EncodingParameters>,
) -> Result<(), Error> {
let params = self.0.sender().get_parameters();
#[allow(clippy::unwrap_used)] let encs = get_property_by_name(¶ms, "encodings", |v| {
v.is_array().then_some(Array::from(&v))
})
.unwrap();
for mut enc in encs.iter().map(RtcRtpEncodingParameters::from) {
#[allow(clippy::unwrap_used)] let rid =
get_property_by_name(&enc, "rid", |v| v.as_string()).unwrap();
let Some(encoding) = encodings.iter().find(|e| e.rid == rid) else {
continue;
};
_ = enc.active(encoding.active);
if let Some(max_bitrate) = encoding.max_bitrate {
_ = enc.max_bitrate(max_bitrate);
}
if let Some(scale_resolution_down_by) =
encoding.scale_resolution_down_by
{
_ = enc
.scale_resolution_down_by(scale_resolution_down_by.into());
}
}
drop(
JsFuture::from(
self.0.sender().set_parameters_with_parameters(¶ms),
)
.await?,
);
Ok(())
}
pub fn set_codec_preferences(&self, codecs: Vec<CodecCapability>) {
let is_api_available =
get_property_by_name(&self.0, "setCodecPreferences", |val| {
if val.is_undefined() {
None
} else {
Some(val)
}
})
.is_some();
if is_api_available {
let arr = ::js_sys::Array::new();
for codec in codecs {
_ = arr.push(codec.handle());
}
self.0.set_codec_preferences(&arr);
}
}
}
#[cfg(test)]
mod tests {
use web_sys::RtcRtpTransceiverDirection;
use super::TransceiverDirection;
#[test]
fn enable_works_correctly() {
use TransceiverDirection as D;
for (init, enable_dir, result) in [
(D::INACTIVE, D::SEND, D::SEND),
(D::INACTIVE, D::RECV, D::RECV),
(D::SEND, D::RECV, D::RECV | D::SEND),
(D::RECV, D::SEND, D::RECV | D::SEND),
] {
assert_eq!(init | enable_dir, result);
}
}
#[test]
fn disable_works_correctly() {
use TransceiverDirection as D;
for (init, disable_dir, result) in [
(D::SEND, D::SEND, D::INACTIVE),
(D::RECV, D::RECV, D::INACTIVE),
(D::RECV | D::SEND, D::SEND, D::RECV),
(D::RECV | D::SEND, D::RECV, D::SEND),
] {
assert_eq!(init - disable_dir, result);
}
}
#[test]
fn from_trnscvr_direction_to_sys() {
use RtcRtpTransceiverDirection as S;
use TransceiverDirection as D;
for (trnscvr_dir, sys_dir) in [
(D::SEND, S::Sendonly),
(D::RECV, S::Recvonly),
(D::RECV | D::SEND, S::Sendrecv),
(D::INACTIVE, S::Inactive),
] {
assert_eq!(S::from(trnscvr_dir), sys_dir);
}
}
#[test]
fn from_sys_direction_to_trnscvr() {
use RtcRtpTransceiverDirection as S;
use TransceiverDirection as D;
for (sys_dir, trnscvr_dir) in [
(S::Sendonly, D::SEND),
(S::Recvonly, D::RECV),
(S::Sendrecv, D::RECV | D::SEND),
(S::Inactive, D::INACTIVE),
] {
assert_eq!(D::from(sys_dir), trnscvr_dir);
}
}
}