use cxx::UniquePtr; use livekit_utils::enum_dispatch; use std::fmt::{Debug, Formatter}; use std::pin::Pin; use std::sync::{Arc, Mutex}; use webrtc_sys::media_stream as sys_ms; use webrtc_sys::MEDIA_TYPE_VIDEO; pub use sys_ms::ffi::ContentHint; pub use sys_ms::ffi::TrackState; use crate::video_frame::VideoFrame; use crate::video_frame_buffer::VideoFrameBuffer; pub trait MediaStreamTrackTrait { fn kind(&self) -> String; fn id(&self) -> String; fn enabled(&self) -> bool; fn set_enabled(&self, enabled: bool) -> bool; fn state(&self) -> TrackState; } #[derive(Clone)] pub enum MediaStreamTrackHandle { Audio(Arc), Video(Arc), } impl MediaStreamTrackHandle { pub(crate) fn new(cxx_handle: UniquePtr) -> Self { unsafe { if cxx_handle.kind() == MEDIA_TYPE_VIDEO { Self::Video(VideoTrack::new(UniquePtr::from_raw( sys_ms::ffi::media_to_video(cxx_handle.into_raw()) as *mut sys_ms::ffi::VideoTrack, ))) } else { Self::Audio(AudioTrack::new(UniquePtr::from_raw( sys_ms::ffi::media_to_audio(cxx_handle.into_raw()) as *mut sys_ms::ffi::AudioTrack, ))) } } } } impl Debug for MediaStreamTrackHandle { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { f.debug_struct("MediaStreamTrack") .field("id", &self.id()) .field("kind", &self.kind()) .field("enabled", &self.enabled()) .field("state", &self.state()) .finish() } } impl MediaStreamTrackTrait for MediaStreamTrackHandle { enum_dispatch!( [Audio, Video] fnc!(kind, &Self, [], String); fnc!(id, &Self, [], String); fnc!(enabled, &Self, [], bool); fnc!(state, &Self, [], TrackState); fnc!(set_enabled, &Self, [enabled: bool], bool); ); } pub struct AudioTrack { cxx_handle: Mutex>, } impl AudioTrack { pub(crate) fn new(cxx_handle: UniquePtr) -> Arc { Arc::new(Self { cxx_handle: Mutex::new(cxx_handle), }) } } pub struct VideoTrack { cxx_handle: Mutex>, observer: Box, // Keep alive for c++ native_observer: UniquePtr, } macro_rules! impl_media_stream_track_trait { ($x:ty, $cast:ident) => { impl MediaStreamTrackTrait for $x { fn kind(&self) -> String { unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).kind() } } fn id(&self) -> String { unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).id() } } fn enabled(&self) -> bool { unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).enabled() } } fn set_enabled(&self, enabled: bool) -> bool { unsafe { let media = sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap()) as *mut sys_ms::ffi::MediaStreamTrack; Pin::new_unchecked(&mut *media).set_enabled(enabled) } } fn state(&self) -> TrackState { unsafe { (*sys_ms::ffi::$cast(&**self.cxx_handle.lock().unwrap())).state() } } } }; } impl_media_stream_track_trait!(VideoTrack, video_to_media); impl_media_stream_track_trait!(AudioTrack, audio_to_media); pub type OnFrameHandler = Box; pub type OnDiscardedFrameHandler = Box; pub type OnConstraintsChanged = Box; #[derive(Default)] struct InternalVideoTrackSink { on_frame_handler: Mutex>, on_discarded_frame_handler: Mutex>, on_constraints_changed_handler: Mutex>, } pub struct VideoTrackSourceConstraints { pub min_fps: Option, pub max_fps: Option, } impl From for VideoTrackSourceConstraints { fn from(cst: sys_ms::ffi::VideoTrackSourceConstraints) -> Self { Self { min_fps: (cst.min_fps != 1.0).then_some(cst.min_fps), max_fps: (cst.max_fps != 1.0).then_some(cst.max_fps), } } } impl sys_ms::VideoFrameSink for InternalVideoTrackSink { fn on_frame(&self, frame: UniquePtr) { if let Some(cb) = self.on_frame_handler.lock().unwrap().as_mut() { let frame = VideoFrame::new(frame); let video_frame_buffer = unsafe { frame.video_frame_buffer() }; cb(frame, video_frame_buffer); } } fn on_discarded_frame(&self) { if let Some(cb) = self.on_discarded_frame_handler.lock().unwrap().as_mut() { cb(); } } fn on_constraints_changed(&self, constraints: sys_ms::ffi::VideoTrackSourceConstraints) { if let Some(cb) = self.on_constraints_changed_handler.lock().unwrap().as_mut() { cb(constraints.into()); } } } impl VideoTrack { fn new(cxx_handle: UniquePtr) -> Arc { let mut observer = Box::new(InternalVideoTrackSink::default()); let mut track = unsafe { Self { cxx_handle: Mutex::new(cxx_handle), native_observer: sys_ms::ffi::create_native_video_frame_sink(Box::new( sys_ms::VideoFrameSinkWrapper::new(&mut *observer), )), observer, } }; unsafe { track .cxx_handle .lock() .unwrap() .pin_mut() .add_sink(track.native_observer.pin_mut()); } Arc::new(track) } pub fn set_should_receive(&self, should_receive: bool) { self.cxx_handle .lock() .unwrap() .pin_mut() .set_should_receive(should_receive) } pub fn set_content_hint(&self, hint: ContentHint) { self.cxx_handle .lock() .unwrap() .pin_mut() .set_content_hint(hint) } pub fn should_receive(&self) -> bool { self.cxx_handle.lock().unwrap().should_receive() } pub fn content_hint(&self) -> ContentHint { self.cxx_handle.lock().unwrap().content_hint() } pub fn on_frame(&self, handler: OnFrameHandler) { *self.observer.on_frame_handler.lock().unwrap() = Some(handler); } pub fn on_discarded_frame(&self, handler: OnDiscardedFrameHandler) { *self.observer.on_discarded_frame_handler.lock().unwrap() = Some(handler); } pub fn on_constraints_changed(&self, handler: OnConstraintsChanged) { *self.observer.on_constraints_changed_handler.lock().unwrap() = Some(handler); } } impl Drop for VideoTrack { fn drop(&mut self) { unsafe { self.cxx_handle .lock() .unwrap() .pin_mut() .remove_sink(self.native_observer.pin_mut()); } } } pub struct MediaStream { cxx_handle: UniquePtr, } impl Debug for MediaStream { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { f.debug_struct("MediaStream") .field("id", &self.id()) .finish() } } impl MediaStream { pub(crate) fn new(cxx_handle: UniquePtr) -> Self { Self { cxx_handle } } pub fn id(&self) -> String { self.cxx_handle.id() } }