prod.rs

  1use anyhow::{anyhow, Context, Result};
  2use core_foundation::{
  3    array::{CFArray, CFArrayRef},
  4    base::{CFRelease, CFRetain, TCFType},
  5    string::{CFString, CFStringRef},
  6};
  7use futures::{
  8    channel::{mpsc, oneshot},
  9    Future,
 10};
 11pub use media::core_video::CVImageBuffer;
 12use media::core_video::CVImageBufferRef;
 13use parking_lot::Mutex;
 14use postage::watch;
 15use std::{
 16    ffi::c_void,
 17    sync::{Arc, Weak},
 18};
 19
 20// SAFETY: Most live kit types are threadsafe:
 21// https://github.com/livekit/client-sdk-swift#thread-safety
 22macro_rules! pointer_type {
 23    ($pointer_name:ident) => {
 24        #[repr(transparent)]
 25        #[derive(Copy, Clone, Debug)]
 26        pub struct $pointer_name(pub *const std::ffi::c_void);
 27        unsafe impl Send for $pointer_name {}
 28    };
 29}
 30
 31mod swift {
 32    pointer_type!(Room);
 33    pointer_type!(LocalAudioTrack);
 34    pointer_type!(RemoteAudioTrack);
 35    pointer_type!(LocalVideoTrack);
 36    pointer_type!(RemoteVideoTrack);
 37    pointer_type!(LocalTrackPublication);
 38    pointer_type!(RemoteTrackPublication);
 39    pointer_type!(MacOSDisplay);
 40    pointer_type!(RoomDelegate);
 41}
 42
 43extern "C" {
 44    fn LKRoomDelegateCreate(
 45        callback_data: *mut c_void,
 46        on_did_disconnect: extern "C" fn(callback_data: *mut c_void),
 47        on_did_subscribe_to_remote_audio_track: extern "C" fn(
 48            callback_data: *mut c_void,
 49            publisher_id: CFStringRef,
 50            track_id: CFStringRef,
 51            remote_track: swift::RemoteAudioTrack,
 52            remote_publication: swift::RemoteTrackPublication,
 53        ),
 54        on_did_unsubscribe_from_remote_audio_track: extern "C" fn(
 55            callback_data: *mut c_void,
 56            publisher_id: CFStringRef,
 57            track_id: CFStringRef,
 58        ),
 59        on_mute_changed_from_remote_audio_track: extern "C" fn(
 60            callback_data: *mut c_void,
 61            track_id: CFStringRef,
 62            muted: bool,
 63        ),
 64        on_active_speakers_changed: extern "C" fn(
 65            callback_data: *mut c_void,
 66            participants: CFArrayRef,
 67        ),
 68        on_did_subscribe_to_remote_video_track: extern "C" fn(
 69            callback_data: *mut c_void,
 70            publisher_id: CFStringRef,
 71            track_id: CFStringRef,
 72            remote_track: swift::RemoteVideoTrack,
 73        ),
 74        on_did_unsubscribe_from_remote_video_track: extern "C" fn(
 75            callback_data: *mut c_void,
 76            publisher_id: CFStringRef,
 77            track_id: CFStringRef,
 78        ),
 79    ) -> swift::RoomDelegate;
 80
 81    fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room;
 82    fn LKRoomConnect(
 83        room: swift::Room,
 84        url: CFStringRef,
 85        token: CFStringRef,
 86        callback: extern "C" fn(*mut c_void, CFStringRef),
 87        callback_data: *mut c_void,
 88    );
 89    fn LKRoomDisconnect(room: swift::Room);
 90    fn LKRoomPublishVideoTrack(
 91        room: swift::Room,
 92        track: swift::LocalVideoTrack,
 93        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
 94        callback_data: *mut c_void,
 95    );
 96    fn LKRoomPublishAudioTrack(
 97        room: swift::Room,
 98        track: swift::LocalAudioTrack,
 99        callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef),
100        callback_data: *mut c_void,
101    );
102    fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication);
103
104    fn LKRoomAudioTracksForRemoteParticipant(
105        room: swift::Room,
106        participant_id: CFStringRef,
107    ) -> CFArrayRef;
108
109    fn LKRoomAudioTrackPublicationsForRemoteParticipant(
110        room: swift::Room,
111        participant_id: CFStringRef,
112    ) -> CFArrayRef;
113
114    fn LKRoomVideoTracksForRemoteParticipant(
115        room: swift::Room,
116        participant_id: CFStringRef,
117    ) -> CFArrayRef;
118
119    fn LKVideoRendererCreate(
120        callback_data: *mut c_void,
121        on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool,
122        on_drop: extern "C" fn(callback_data: *mut c_void),
123    ) -> *const c_void;
124
125    fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef;
126    fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void);
127    fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef;
128
129    fn LKDisplaySources(
130        callback_data: *mut c_void,
131        callback: extern "C" fn(
132            callback_data: *mut c_void,
133            sources: CFArrayRef,
134            error: CFStringRef,
135        ),
136    );
137    fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack;
138    fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack;
139
140    fn LKLocalTrackPublicationSetMute(
141        publication: swift::LocalTrackPublication,
142        muted: bool,
143        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
144        callback_data: *mut c_void,
145    );
146
147    fn LKRemoteTrackPublicationSetEnabled(
148        publication: swift::RemoteTrackPublication,
149        enabled: bool,
150        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
151        callback_data: *mut c_void,
152    );
153
154    fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool;
155    fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef;
156}
157
158pub type Sid = String;
159
160#[derive(Clone, Eq, PartialEq)]
161pub enum ConnectionState {
162    Disconnected,
163    Connected { url: String, token: String },
164}
165
166pub struct Room {
167    native_room: swift::Room,
168    connection: Mutex<(
169        watch::Sender<ConnectionState>,
170        watch::Receiver<ConnectionState>,
171    )>,
172    remote_audio_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteAudioTrackUpdate>>>,
173    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
174    _delegate: RoomDelegate,
175}
176
177impl Room {
178    pub fn new() -> Arc<Self> {
179        Arc::new_cyclic(|weak_room| {
180            let delegate = RoomDelegate::new(weak_room.clone());
181            Self {
182                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
183                connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)),
184                remote_audio_track_subscribers: Default::default(),
185                remote_video_track_subscribers: Default::default(),
186                _delegate: delegate,
187            }
188        })
189    }
190
191    pub fn status(&self) -> watch::Receiver<ConnectionState> {
192        self.connection.lock().1.clone()
193    }
194
195    pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
196        let url = CFString::new(url);
197        let token = CFString::new(token);
198        let (did_connect, tx, rx) = Self::build_done_callback();
199        unsafe {
200            LKRoomConnect(
201                self.native_room,
202                url.as_concrete_TypeRef(),
203                token.as_concrete_TypeRef(),
204                did_connect,
205                tx,
206            )
207        }
208
209        let this = self.clone();
210        let url = url.to_string();
211        let token = token.to_string();
212        async move {
213            rx.await.unwrap().context("error connecting to room")?;
214            *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token };
215            Ok(())
216        }
217    }
218
219    fn did_disconnect(&self) {
220        *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected;
221    }
222
223    pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
224        extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
225            unsafe {
226                let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
227
228                if sources.is_null() {
229                    let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
230                } else {
231                    let sources = CFArray::wrap_under_get_rule(sources)
232                        .into_iter()
233                        .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source)))
234                        .collect();
235
236                    let _ = tx.send(Ok(sources));
237                }
238            }
239        }
240
241        let (tx, rx) = oneshot::channel();
242
243        unsafe {
244            LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
245        }
246
247        async move { rx.await.unwrap() }
248    }
249
250    pub fn publish_video_track(
251        self: &Arc<Self>,
252        track: LocalVideoTrack,
253    ) -> impl Future<Output = Result<LocalTrackPublication>> {
254        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
255        extern "C" fn callback(
256            tx: *mut c_void,
257            publication: swift::LocalTrackPublication,
258            error: CFStringRef,
259        ) {
260            let tx =
261                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
262            if error.is_null() {
263                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
264            } else {
265                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
266                let _ = tx.send(Err(anyhow!(error)));
267            }
268        }
269        unsafe {
270            LKRoomPublishVideoTrack(
271                self.native_room,
272                track.0,
273                callback,
274                Box::into_raw(Box::new(tx)) as *mut c_void,
275            );
276        }
277        async { rx.await.unwrap().context("error publishing video track") }
278    }
279
280    pub fn publish_audio_track(
281        self: &Arc<Self>,
282        track: LocalAudioTrack,
283    ) -> impl Future<Output = Result<LocalTrackPublication>> {
284        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
285        extern "C" fn callback(
286            tx: *mut c_void,
287            publication: swift::LocalTrackPublication,
288            error: CFStringRef,
289        ) {
290            let tx =
291                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
292            if error.is_null() {
293                let _ = tx.send(Ok(LocalTrackPublication::new(publication)));
294            } else {
295                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
296                let _ = tx.send(Err(anyhow!(error)));
297            }
298        }
299        unsafe {
300            LKRoomPublishAudioTrack(
301                self.native_room,
302                track.0,
303                callback,
304                Box::into_raw(Box::new(tx)) as *mut c_void,
305            );
306        }
307        async { rx.await.unwrap().context("error publishing audio track") }
308    }
309
310    pub fn unpublish_track(&self, publication: LocalTrackPublication) {
311        unsafe {
312            LKRoomUnpublishTrack(self.native_room, publication.0);
313        }
314    }
315
316    pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
317        unsafe {
318            let tracks = LKRoomVideoTracksForRemoteParticipant(
319                self.native_room,
320                CFString::new(participant_id).as_concrete_TypeRef(),
321            );
322
323            if tracks.is_null() {
324                Vec::new()
325            } else {
326                let tracks = CFArray::wrap_under_get_rule(tracks);
327                tracks
328                    .into_iter()
329                    .map(|native_track| {
330                        let native_track = swift::RemoteVideoTrack(*native_track);
331                        let id =
332                            CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track))
333                                .to_string();
334                        Arc::new(RemoteVideoTrack::new(
335                            native_track,
336                            id,
337                            participant_id.into(),
338                        ))
339                    })
340                    .collect()
341            }
342        }
343    }
344
345    pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteAudioTrack>> {
346        unsafe {
347            let tracks = LKRoomAudioTracksForRemoteParticipant(
348                self.native_room,
349                CFString::new(participant_id).as_concrete_TypeRef(),
350            );
351
352            if tracks.is_null() {
353                Vec::new()
354            } else {
355                let tracks = CFArray::wrap_under_get_rule(tracks);
356                tracks
357                    .into_iter()
358                    .map(|native_track| {
359                        let native_track = swift::RemoteAudioTrack(*native_track);
360                        let id =
361                            CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track))
362                                .to_string();
363                        Arc::new(RemoteAudioTrack::new(
364                            native_track,
365                            id,
366                            participant_id.into(),
367                        ))
368                    })
369                    .collect()
370            }
371        }
372    }
373
374    pub fn remote_audio_track_publications(
375        &self,
376        participant_id: &str,
377    ) -> Vec<Arc<RemoteTrackPublication>> {
378        unsafe {
379            let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant(
380                self.native_room,
381                CFString::new(participant_id).as_concrete_TypeRef(),
382            );
383
384            if tracks.is_null() {
385                Vec::new()
386            } else {
387                let tracks = CFArray::wrap_under_get_rule(tracks);
388                tracks
389                    .into_iter()
390                    .map(|native_track_publication| {
391                        let native_track_publication =
392                            swift::RemoteTrackPublication(*native_track_publication);
393                        Arc::new(RemoteTrackPublication::new(native_track_publication))
394                    })
395                    .collect()
396            }
397        }
398    }
399
400    pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> {
401        let (tx, rx) = mpsc::unbounded();
402        self.remote_audio_track_subscribers.lock().push(tx);
403        rx
404    }
405
406    pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
407        let (tx, rx) = mpsc::unbounded();
408        self.remote_video_track_subscribers.lock().push(tx);
409        rx
410    }
411
412    fn did_subscribe_to_remote_audio_track(
413        &self,
414        track: RemoteAudioTrack,
415        publication: RemoteTrackPublication,
416    ) {
417        let track = Arc::new(track);
418        let publication = Arc::new(publication);
419        self.remote_audio_track_subscribers.lock().retain(|tx| {
420            tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(
421                track.clone(),
422                publication.clone(),
423            ))
424            .is_ok()
425        });
426    }
427
428    fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) {
429        self.remote_audio_track_subscribers.lock().retain(|tx| {
430            tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed {
431                publisher_id: publisher_id.clone(),
432                track_id: track_id.clone(),
433            })
434            .is_ok()
435        });
436    }
437
438    fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) {
439        self.remote_audio_track_subscribers.lock().retain(|tx| {
440            tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged {
441                track_id: track_id.clone(),
442                muted,
443            })
444            .is_ok()
445        });
446    }
447
448    // A vec of publisher IDs
449    fn active_speakers_changed(&self, speakers: Vec<String>) {
450        self.remote_audio_track_subscribers
451            .lock()
452            .retain(move |tx| {
453                tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged {
454                    speakers: speakers.clone(),
455                })
456                .is_ok()
457            });
458    }
459
460    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
461        let track = Arc::new(track);
462        self.remote_video_track_subscribers.lock().retain(|tx| {
463            tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
464                .is_ok()
465        });
466    }
467
468    fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
469        self.remote_video_track_subscribers.lock().retain(|tx| {
470            tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
471                publisher_id: publisher_id.clone(),
472                track_id: track_id.clone(),
473            })
474            .is_ok()
475        });
476    }
477
478    fn build_done_callback() -> (
479        extern "C" fn(*mut c_void, CFStringRef),
480        *mut c_void,
481        oneshot::Receiver<Result<()>>,
482    ) {
483        let (tx, rx) = oneshot::channel();
484        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
485            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
486            if error.is_null() {
487                let _ = tx.send(Ok(()));
488            } else {
489                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
490                let _ = tx.send(Err(anyhow!(error)));
491            }
492        }
493        (
494            done_callback,
495            Box::into_raw(Box::new(tx)) as *mut c_void,
496            rx,
497        )
498    }
499
500    pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
501        unreachable!("This is a test-only function")
502    }
503}
504
505impl Drop for Room {
506    fn drop(&mut self) {
507        unsafe {
508            LKRoomDisconnect(self.native_room);
509            CFRelease(self.native_room.0);
510        }
511    }
512}
513
514struct RoomDelegate {
515    native_delegate: swift::RoomDelegate,
516    weak_room: *mut c_void,
517}
518
519impl RoomDelegate {
520    fn new(weak_room: Weak<Room>) -> Self {
521        let weak_room = weak_room.into_raw() as *mut c_void;
522        let native_delegate = unsafe {
523            LKRoomDelegateCreate(
524                weak_room,
525                Self::on_did_disconnect,
526                Self::on_did_subscribe_to_remote_audio_track,
527                Self::on_did_unsubscribe_from_remote_audio_track,
528                Self::on_mute_change_from_remote_audio_track,
529                Self::on_active_speakers_changed,
530                Self::on_did_subscribe_to_remote_video_track,
531                Self::on_did_unsubscribe_from_remote_video_track,
532            )
533        };
534        Self {
535            native_delegate,
536            weak_room,
537        }
538    }
539
540    extern "C" fn on_did_disconnect(room: *mut c_void) {
541        let room = unsafe { Weak::from_raw(room as *mut Room) };
542        if let Some(room) = room.upgrade() {
543            room.did_disconnect();
544        }
545        let _ = Weak::into_raw(room);
546    }
547
548    extern "C" fn on_did_subscribe_to_remote_audio_track(
549        room: *mut c_void,
550        publisher_id: CFStringRef,
551        track_id: CFStringRef,
552        track: swift::RemoteAudioTrack,
553        publication: swift::RemoteTrackPublication,
554    ) {
555        let room = unsafe { Weak::from_raw(room as *mut Room) };
556        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
557        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
558        let track = RemoteAudioTrack::new(track, track_id, publisher_id);
559        let publication = RemoteTrackPublication::new(publication);
560        if let Some(room) = room.upgrade() {
561            room.did_subscribe_to_remote_audio_track(track, publication);
562        }
563        let _ = Weak::into_raw(room);
564    }
565
566    extern "C" fn on_did_unsubscribe_from_remote_audio_track(
567        room: *mut c_void,
568        publisher_id: CFStringRef,
569        track_id: CFStringRef,
570    ) {
571        let room = unsafe { Weak::from_raw(room as *mut Room) };
572        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
573        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
574        if let Some(room) = room.upgrade() {
575            room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id);
576        }
577        let _ = Weak::into_raw(room);
578    }
579
580    extern "C" fn on_mute_change_from_remote_audio_track(
581        room: *mut c_void,
582        track_id: CFStringRef,
583        muted: bool,
584    ) {
585        let room = unsafe { Weak::from_raw(room as *mut Room) };
586        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
587        if let Some(room) = room.upgrade() {
588            room.mute_changed_from_remote_audio_track(track_id, muted);
589        }
590        let _ = Weak::into_raw(room);
591    }
592
593    extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
594        if participants.is_null() {
595            return;
596        }
597
598        let room = unsafe { Weak::from_raw(room as *mut Room) };
599        let speakers = unsafe {
600            CFArray::wrap_under_get_rule(participants)
601                .into_iter()
602                .map(
603                    |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
604                        CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
605                    },
606                )
607                .collect()
608        };
609
610        if let Some(room) = room.upgrade() {
611            room.active_speakers_changed(speakers);
612        }
613        let _ = Weak::into_raw(room);
614    }
615
616    extern "C" fn on_did_subscribe_to_remote_video_track(
617        room: *mut c_void,
618        publisher_id: CFStringRef,
619        track_id: CFStringRef,
620        track: swift::RemoteVideoTrack,
621    ) {
622        let room = unsafe { Weak::from_raw(room as *mut Room) };
623        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
624        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
625        let track = RemoteVideoTrack::new(track, track_id, publisher_id);
626        if let Some(room) = room.upgrade() {
627            room.did_subscribe_to_remote_video_track(track);
628        }
629        let _ = Weak::into_raw(room);
630    }
631
632    extern "C" fn on_did_unsubscribe_from_remote_video_track(
633        room: *mut c_void,
634        publisher_id: CFStringRef,
635        track_id: CFStringRef,
636    ) {
637        let room = unsafe { Weak::from_raw(room as *mut Room) };
638        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
639        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
640        if let Some(room) = room.upgrade() {
641            room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
642        }
643        let _ = Weak::into_raw(room);
644    }
645}
646
647impl Drop for RoomDelegate {
648    fn drop(&mut self) {
649        unsafe {
650            CFRelease(self.native_delegate.0);
651            let _ = Weak::from_raw(self.weak_room);
652        }
653    }
654}
655
656pub struct LocalAudioTrack(swift::LocalAudioTrack);
657
658impl LocalAudioTrack {
659    pub fn create() -> Self {
660        Self(unsafe { LKLocalAudioTrackCreateTrack() })
661    }
662}
663
664impl Drop for LocalAudioTrack {
665    fn drop(&mut self) {
666        unsafe { CFRelease(self.0 .0) }
667    }
668}
669
670pub struct LocalVideoTrack(swift::LocalVideoTrack);
671
672impl LocalVideoTrack {
673    pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
674        Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
675    }
676}
677
678impl Drop for LocalVideoTrack {
679    fn drop(&mut self) {
680        unsafe { CFRelease(self.0 .0) }
681    }
682}
683
684pub struct LocalTrackPublication(swift::LocalTrackPublication);
685
686impl LocalTrackPublication {
687    pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
688        unsafe {
689            CFRetain(native_track_publication.0);
690        }
691        Self(native_track_publication)
692    }
693
694    pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
695        let (tx, rx) = futures::channel::oneshot::channel();
696
697        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
698            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
699            if error.is_null() {
700                tx.send(Ok(())).ok();
701            } else {
702                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
703                tx.send(Err(anyhow!(error))).ok();
704            }
705        }
706
707        unsafe {
708            LKLocalTrackPublicationSetMute(
709                self.0,
710                muted,
711                complete_callback,
712                Box::into_raw(Box::new(tx)) as *mut c_void,
713            )
714        }
715
716        async move { rx.await.unwrap() }
717    }
718}
719
720impl Drop for LocalTrackPublication {
721    fn drop(&mut self) {
722        unsafe { CFRelease(self.0 .0) }
723    }
724}
725
726pub struct RemoteTrackPublication(swift::RemoteTrackPublication);
727
728impl RemoteTrackPublication {
729    pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
730        unsafe {
731            CFRetain(native_track_publication.0);
732        }
733        Self(native_track_publication)
734    }
735
736    pub fn sid(&self) -> String {
737        unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() }
738    }
739
740    pub fn is_muted(&self) -> bool {
741        unsafe { LKRemoteTrackPublicationIsMuted(self.0) }
742    }
743
744    pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
745        let (tx, rx) = futures::channel::oneshot::channel();
746
747        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
748            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
749            if error.is_null() {
750                tx.send(Ok(())).ok();
751            } else {
752                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
753                tx.send(Err(anyhow!(error))).ok();
754            }
755        }
756
757        unsafe {
758            LKRemoteTrackPublicationSetEnabled(
759                self.0,
760                enabled,
761                complete_callback,
762                Box::into_raw(Box::new(tx)) as *mut c_void,
763            )
764        }
765
766        async move { rx.await.unwrap() }
767    }
768}
769
770impl Drop for RemoteTrackPublication {
771    fn drop(&mut self) {
772        unsafe { CFRelease(self.0 .0) }
773    }
774}
775
776#[derive(Debug)]
777pub struct RemoteAudioTrack {
778    native_track: swift::RemoteAudioTrack,
779    sid: Sid,
780    publisher_id: String,
781}
782
783impl RemoteAudioTrack {
784    fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
785        unsafe {
786            CFRetain(native_track.0);
787        }
788        Self {
789            native_track,
790            sid,
791            publisher_id,
792        }
793    }
794
795    pub fn sid(&self) -> &str {
796        &self.sid
797    }
798
799    pub fn publisher_id(&self) -> &str {
800        &self.publisher_id
801    }
802
803    pub fn enable(&self) -> impl Future<Output = Result<()>> {
804        async { Ok(()) }
805    }
806
807    pub fn disable(&self) -> impl Future<Output = Result<()>> {
808        async { Ok(()) }
809    }
810}
811
812impl Drop for RemoteAudioTrack {
813    fn drop(&mut self) {
814        unsafe { CFRelease(self.native_track.0) }
815    }
816}
817
818#[derive(Debug)]
819pub struct RemoteVideoTrack {
820    native_track: swift::RemoteVideoTrack,
821    sid: Sid,
822    publisher_id: String,
823}
824
825impl RemoteVideoTrack {
826    fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
827        unsafe {
828            CFRetain(native_track.0);
829        }
830        Self {
831            native_track,
832            sid,
833            publisher_id,
834        }
835    }
836
837    pub fn sid(&self) -> &str {
838        &self.sid
839    }
840
841    pub fn publisher_id(&self) -> &str {
842        &self.publisher_id
843    }
844
845    pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
846        extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool {
847            unsafe {
848                let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
849                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
850                let result = tx.try_broadcast(Frame(buffer));
851                let _ = Box::into_raw(tx);
852                match result {
853                    Ok(_) => true,
854                    Err(async_broadcast::TrySendError::Closed(_))
855                    | Err(async_broadcast::TrySendError::Inactive(_)) => {
856                        log::warn!("no active receiver for frame");
857                        false
858                    }
859                    Err(async_broadcast::TrySendError::Full(_)) => {
860                        log::warn!("skipping frame as receiver is not keeping up");
861                        true
862                    }
863                }
864            }
865        }
866
867        extern "C" fn on_drop(callback_data: *mut c_void) {
868            unsafe {
869                let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
870            }
871        }
872
873        let (tx, rx) = async_broadcast::broadcast(64);
874        unsafe {
875            let renderer = LKVideoRendererCreate(
876                Box::into_raw(Box::new(tx)) as *mut c_void,
877                on_frame,
878                on_drop,
879            );
880            LKVideoTrackAddRenderer(self.native_track, renderer);
881            rx
882        }
883    }
884}
885
886impl Drop for RemoteVideoTrack {
887    fn drop(&mut self) {
888        unsafe { CFRelease(self.native_track.0) }
889    }
890}
891
892pub enum RemoteVideoTrackUpdate {
893    Subscribed(Arc<RemoteVideoTrack>),
894    Unsubscribed { publisher_id: Sid, track_id: Sid },
895}
896
897pub enum RemoteAudioTrackUpdate {
898    ActiveSpeakersChanged { speakers: Vec<Sid> },
899    MuteChanged { track_id: Sid, muted: bool },
900    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
901    Unsubscribed { publisher_id: Sid, track_id: Sid },
902}
903
904pub struct MacOSDisplay(swift::MacOSDisplay);
905
906impl MacOSDisplay {
907    fn new(ptr: swift::MacOSDisplay) -> Self {
908        unsafe {
909            CFRetain(ptr.0);
910        }
911        Self(ptr)
912    }
913}
914
915impl Drop for MacOSDisplay {
916    fn drop(&mut self) {
917        unsafe { CFRelease(self.0 .0) }
918    }
919}
920
921#[derive(Clone)]
922pub struct Frame(CVImageBuffer);
923
924impl Frame {
925    pub fn width(&self) -> usize {
926        self.0.width()
927    }
928
929    pub fn height(&self) -> usize {
930        self.0.height()
931    }
932
933    pub fn image(&self) -> CVImageBuffer {
934        self.0.clone()
935    }
936}