prod.rs

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