use std::{cell::Cell, rc::Rc};
use futures::StreamExt as _;
use medea_client_api_proto as proto;
use medea_reactive::ObservableCell;
use crate::{
api,
media::{MediaKind, MediaSourceKind, track::MediaStreamTrackState},
platform,
};
#[derive(Debug)]
struct Inner {
track: platform::MediaStreamTrack,
media_source_kind: proto::MediaSourceKind,
on_muted: platform::Callback<()>,
on_unmuted: platform::Callback<()>,
on_stopped: platform::Callback<()>,
#[cfg_attr(
not(target_family = "wasm"),
expect(unused_qualifications, reason = "`cfg` code uniformity")
)]
on_media_direction_changed: platform::Callback<api::MediaDirection>,
media_direction: Cell<MediaDirection>,
muted: ObservableCell<bool>,
}
#[derive(Clone, Debug)]
pub struct Track(Rc<Inner>);
impl Track {
#[must_use]
pub fn new<T>(
track: T,
media_source_kind: proto::MediaSourceKind,
muted: bool,
media_direction: MediaDirection,
) -> Self
where
platform::MediaStreamTrack: From<T>,
{
let track = platform::MediaStreamTrack::from(track);
let track = Self(Rc::new(Inner {
track,
media_source_kind,
muted: ObservableCell::new(muted),
on_media_direction_changed: platform::Callback::default(),
media_direction: Cell::new(media_direction),
on_stopped: platform::Callback::default(),
on_muted: platform::Callback::default(),
on_unmuted: platform::Callback::default(),
}));
track.0.track.on_ended({
let weak_inner = Rc::downgrade(&track.0);
Some(move || {
if let Some(inner) = weak_inner.upgrade() {
inner.on_stopped.call0();
}
})
});
let mut muted_changes = track.0.muted.subscribe().skip(1).fuse();
platform::spawn({
let weak_inner = Rc::downgrade(&track.0);
async move {
while let Some(is_muted) = muted_changes.next().await {
if let Some(inner) = weak_inner.upgrade() {
if is_muted {
inner.on_muted.call0();
} else {
inner.on_unmuted.call0();
}
}
}
}
});
track
}
pub fn set_media_direction(&self, direction: MediaDirection) {
self.0.media_direction.set(direction);
self.0.on_media_direction_changed.call1(direction);
}
pub fn set_muted(&self, muted: bool) {
self.0.muted.set(muted);
}
#[must_use]
pub fn id(&self) -> String {
self.0.track.id()
}
#[must_use]
pub fn kind(&self) -> MediaKind {
self.0.track.kind()
}
#[must_use]
pub fn media_source_kind(&self) -> MediaSourceKind {
self.0.media_source_kind.into()
}
pub async fn stop(self) {
let state = self.0.track.ready_state().await;
self.0.track.stop().await;
if state == MediaStreamTrackState::Live {
self.0.on_stopped.call0();
}
}
#[must_use]
pub fn get_track(&self) -> &platform::MediaStreamTrack {
&self.0.track
}
#[must_use]
pub fn muted(&self) -> bool {
self.0.muted.get()
}
pub fn on_muted(&self, callback: platform::Function<()>) {
self.0.on_muted.set_func(callback);
}
pub fn on_unmuted(&self, callback: platform::Function<()>) {
self.0.on_unmuted.set_func(callback);
}
pub fn on_stopped(&self, callback: platform::Function<()>) {
self.0.on_stopped.set_func(callback);
}
#[cfg_attr(
not(target_family = "wasm"),
expect(unused_qualifications, reason = "`cfg` code uniformity")
)]
pub fn on_media_direction_changed(
&self,
callback: platform::Function<api::MediaDirection>,
) {
self.0.on_media_direction_changed.set_func(callback);
}
#[must_use]
pub fn media_direction(&self) -> MediaDirection {
self.0.media_direction.get()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum MediaDirection {
SendRecv,
SendOnly,
RecvOnly,
Inactive,
}
impl From<MediaDirection> for proto::MediaDirection {
fn from(val: MediaDirection) -> Self {
match val {
MediaDirection::SendRecv => Self::SendRecv,
MediaDirection::SendOnly => Self::SendOnly,
MediaDirection::RecvOnly => Self::RecvOnly,
MediaDirection::Inactive => Self::Inactive,
}
}
}
impl From<proto::MediaDirection> for MediaDirection {
fn from(val: proto::MediaDirection) -> Self {
use proto::MediaDirection as D;
match val {
D::SendRecv => Self::SendRecv,
D::SendOnly => Self::SendOnly,
D::RecvOnly => Self::RecvOnly,
D::Inactive => Self::Inactive,
}
}
}