use std::{future::Future, rc::Rc};
use derive_more::From;
use js_sys::Reflect;
use medea_client_api_proto::EncodingParameters;
use wasm_bindgen::JsValue;
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,
},
};
#[derive(Debug)]
pub struct TransceiverInit(RtcRtpTransceiverInit);
impl TransceiverInit {
#[must_use]
pub fn new(direction: TransceiverDirection) -> Self {
let init = RtcRtpTransceiverInit::new();
init.set_direction(direction.into());
Self(init)
}
#[must_use]
pub const fn handle(&self) -> &RtcRtpTransceiverInit {
&self.0
}
pub fn set_send_encodings(&self, encodings: Vec<SendEncodingParameters>) {
let send_encoding = ::js_sys::Array::new();
for enc in encodings {
_ = send_encoding.push(enc.handle());
}
self.0.set_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());
}
}
#[expect(clippy::unused_async, reason = "`cfg` code uniformity")]
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()
}
pub async fn update_send_encodings(
&self,
encodings: Vec<EncodingParameters>,
) -> Result<(), Error> {
let params = self.0.sender().get_parameters();
let Some(encs) = params.get_encodings() else {
return Ok(());
};
for enc in encs.iter().map(RtcRtpEncodingParameters::from) {
let rid = enc.get_rid();
let Some(encoding) =
encodings.iter().find(|e| Some(&e.rid) == rid.as_ref())
else {
continue;
};
enc.set_active(encoding.active);
if let Some(max_bitrate) = encoding.max_bitrate {
enc.set_max_bitrate(max_bitrate);
}
if let Some(scale_resolution_down_by) =
encoding.scale_resolution_down_by
{
enc.set_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 =
Reflect::get(&self.0, &JsValue::from_str("setCodecPreferences"))
.map_or(None, |val| (!val.is_undefined()).then_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);
}
}
}