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: Mutex<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: Mutex<RoomDelegate>,
175}
176
177trait AssertSendSync: Send {}
178impl AssertSendSync for Room {}
179
180impl Room {
181    pub fn new() -> Arc<Self> {
182        Arc::new_cyclic(|weak_room| {
183            let delegate = RoomDelegate::new(weak_room.clone());
184            Self {
185                native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }),
186                connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)),
187                remote_audio_track_subscribers: Default::default(),
188                remote_video_track_subscribers: Default::default(),
189                _delegate: Mutex::new(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.lock(),
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.lock(),
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.lock(),
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.lock(), 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.lock(),
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.lock(),
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.lock(),
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 remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> {
404        let (tx, rx) = mpsc::unbounded();
405        self.remote_audio_track_subscribers.lock().push(tx);
406        rx
407    }
408
409    pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
410        let (tx, rx) = mpsc::unbounded();
411        self.remote_video_track_subscribers.lock().push(tx);
412        rx
413    }
414
415    fn did_subscribe_to_remote_audio_track(
416        &self,
417        track: RemoteAudioTrack,
418        publication: RemoteTrackPublication,
419    ) {
420        let track = Arc::new(track);
421        let publication = Arc::new(publication);
422        self.remote_audio_track_subscribers.lock().retain(|tx| {
423            tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(
424                track.clone(),
425                publication.clone(),
426            ))
427            .is_ok()
428        });
429    }
430
431    fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) {
432        self.remote_audio_track_subscribers.lock().retain(|tx| {
433            tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed {
434                publisher_id: publisher_id.clone(),
435                track_id: track_id.clone(),
436            })
437            .is_ok()
438        });
439    }
440
441    fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) {
442        self.remote_audio_track_subscribers.lock().retain(|tx| {
443            tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged {
444                track_id: track_id.clone(),
445                muted,
446            })
447            .is_ok()
448        });
449    }
450
451    // A vec of publisher IDs
452    fn active_speakers_changed(&self, speakers: Vec<String>) {
453        self.remote_audio_track_subscribers
454            .lock()
455            .retain(move |tx| {
456                tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged {
457                    speakers: speakers.clone(),
458                })
459                .is_ok()
460            });
461    }
462
463    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
464        let track = Arc::new(track);
465        self.remote_video_track_subscribers.lock().retain(|tx| {
466            tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
467                .is_ok()
468        });
469    }
470
471    fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
472        self.remote_video_track_subscribers.lock().retain(|tx| {
473            tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
474                publisher_id: publisher_id.clone(),
475                track_id: track_id.clone(),
476            })
477            .is_ok()
478        });
479    }
480
481    fn build_done_callback() -> (
482        extern "C" fn(*mut c_void, CFStringRef),
483        *mut c_void,
484        oneshot::Receiver<Result<()>>,
485    ) {
486        let (tx, rx) = oneshot::channel();
487        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
488            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
489            if error.is_null() {
490                let _ = tx.send(Ok(()));
491            } else {
492                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
493                let _ = tx.send(Err(anyhow!(error)));
494            }
495        }
496        (
497            done_callback,
498            Box::into_raw(Box::new(tx)) as *mut c_void,
499            rx,
500        )
501    }
502
503    pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
504        unreachable!("This is a test-only function")
505    }
506}
507
508impl Drop for Room {
509    fn drop(&mut self) {
510        unsafe {
511            let native_room = &*self.native_room.lock();
512            LKRoomDisconnect(*native_room);
513            CFRelease(native_room.0);
514        }
515    }
516}
517
518struct RoomDelegate {
519    native_delegate: swift::RoomDelegate,
520    _weak_room: Weak<Room>,
521}
522
523impl RoomDelegate {
524    fn new(weak_room: Weak<Room>) -> Self {
525        let native_delegate = unsafe {
526            LKRoomDelegateCreate(
527                weak_room.as_ptr() as *mut c_void,
528                Self::on_did_disconnect,
529                Self::on_did_subscribe_to_remote_audio_track,
530                Self::on_did_unsubscribe_from_remote_audio_track,
531                Self::on_mute_change_from_remote_audio_track,
532                Self::on_active_speakers_changed,
533                Self::on_did_subscribe_to_remote_video_track,
534                Self::on_did_unsubscribe_from_remote_video_track,
535            )
536        };
537        Self {
538            native_delegate,
539            _weak_room: weak_room,
540        }
541    }
542
543    extern "C" fn on_did_disconnect(room: *mut c_void) {
544        let room = unsafe { Weak::from_raw(room as *mut Room) };
545        if let Some(room) = room.upgrade() {
546            room.did_disconnect();
547        }
548        let _ = Weak::into_raw(room);
549    }
550
551    extern "C" fn on_did_subscribe_to_remote_audio_track(
552        room: *mut c_void,
553        publisher_id: CFStringRef,
554        track_id: CFStringRef,
555        track: swift::RemoteAudioTrack,
556        publication: swift::RemoteTrackPublication,
557    ) {
558        let room = unsafe { Weak::from_raw(room as *mut Room) };
559        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
560        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
561        let track = RemoteAudioTrack::new(track, track_id, publisher_id);
562        let publication = RemoteTrackPublication::new(publication);
563        if let Some(room) = room.upgrade() {
564            room.did_subscribe_to_remote_audio_track(track, publication);
565        }
566        let _ = Weak::into_raw(room);
567    }
568
569    extern "C" fn on_did_unsubscribe_from_remote_audio_track(
570        room: *mut c_void,
571        publisher_id: CFStringRef,
572        track_id: CFStringRef,
573    ) {
574        let room = unsafe { Weak::from_raw(room as *mut Room) };
575        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
576        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
577        if let Some(room) = room.upgrade() {
578            room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id);
579        }
580        let _ = Weak::into_raw(room);
581    }
582
583    extern "C" fn on_mute_change_from_remote_audio_track(
584        room: *mut c_void,
585        track_id: CFStringRef,
586        muted: bool,
587    ) {
588        let room = unsafe { Weak::from_raw(room as *mut Room) };
589        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
590        if let Some(room) = room.upgrade() {
591            room.mute_changed_from_remote_audio_track(track_id, muted);
592        }
593        let _ = Weak::into_raw(room);
594    }
595
596    extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
597        if participants.is_null() {
598            return;
599        }
600
601        let room = unsafe { Weak::from_raw(room as *mut Room) };
602        let speakers = unsafe {
603            CFArray::wrap_under_get_rule(participants)
604                .into_iter()
605                .map(
606                    |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
607                        CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
608                    },
609                )
610                .collect()
611        };
612
613        if let Some(room) = room.upgrade() {
614            room.active_speakers_changed(speakers);
615        }
616        let _ = Weak::into_raw(room);
617    }
618
619    extern "C" fn on_did_subscribe_to_remote_video_track(
620        room: *mut c_void,
621        publisher_id: CFStringRef,
622        track_id: CFStringRef,
623        track: swift::RemoteVideoTrack,
624    ) {
625        let room = unsafe { Weak::from_raw(room as *mut Room) };
626        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
627        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
628        let track = RemoteVideoTrack::new(track, track_id, publisher_id);
629        if let Some(room) = room.upgrade() {
630            room.did_subscribe_to_remote_video_track(track);
631        }
632        let _ = Weak::into_raw(room);
633    }
634
635    extern "C" fn on_did_unsubscribe_from_remote_video_track(
636        room: *mut c_void,
637        publisher_id: CFStringRef,
638        track_id: CFStringRef,
639    ) {
640        let room = unsafe { Weak::from_raw(room as *mut Room) };
641        let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
642        let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
643        if let Some(room) = room.upgrade() {
644            room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
645        }
646        let _ = Weak::into_raw(room);
647    }
648}
649
650impl Drop for RoomDelegate {
651    fn drop(&mut self) {
652        unsafe {
653            CFRelease(self.native_delegate.0);
654        }
655    }
656}
657
658pub struct LocalAudioTrack(swift::LocalAudioTrack);
659
660impl LocalAudioTrack {
661    pub fn create() -> Self {
662        Self(unsafe { LKLocalAudioTrackCreateTrack() })
663    }
664}
665
666impl Drop for LocalAudioTrack {
667    fn drop(&mut self) {
668        unsafe { CFRelease(self.0 .0) }
669    }
670}
671
672pub struct LocalVideoTrack(swift::LocalVideoTrack);
673
674impl LocalVideoTrack {
675    pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
676        Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
677    }
678}
679
680impl Drop for LocalVideoTrack {
681    fn drop(&mut self) {
682        unsafe { CFRelease(self.0 .0) }
683    }
684}
685
686pub struct LocalTrackPublication(swift::LocalTrackPublication);
687
688impl LocalTrackPublication {
689    pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
690        unsafe {
691            CFRetain(native_track_publication.0);
692        }
693        Self(native_track_publication)
694    }
695
696    pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
697        let (tx, rx) = futures::channel::oneshot::channel();
698
699        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
700            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
701            if error.is_null() {
702                tx.send(Ok(())).ok();
703            } else {
704                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
705                tx.send(Err(anyhow!(error))).ok();
706            }
707        }
708
709        unsafe {
710            LKLocalTrackPublicationSetMute(
711                self.0,
712                muted,
713                complete_callback,
714                Box::into_raw(Box::new(tx)) as *mut c_void,
715            )
716        }
717
718        async move { rx.await.unwrap() }
719    }
720}
721
722impl Drop for LocalTrackPublication {
723    fn drop(&mut self) {
724        unsafe { CFRelease(self.0 .0) }
725    }
726}
727
728pub struct RemoteTrackPublication {
729    native_publication: Mutex<swift::RemoteTrackPublication>,
730}
731
732impl RemoteTrackPublication {
733    pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
734        unsafe {
735            CFRetain(native_track_publication.0);
736        }
737        Self {
738            native_publication: Mutex::new(native_track_publication),
739        }
740    }
741
742    pub fn sid(&self) -> String {
743        unsafe {
744            CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(
745                *self.native_publication.lock(),
746            ))
747            .to_string()
748        }
749    }
750
751    pub fn is_muted(&self) -> bool {
752        unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) }
753    }
754
755    pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
756        let (tx, rx) = futures::channel::oneshot::channel();
757
758        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
759            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
760            if error.is_null() {
761                tx.send(Ok(())).ok();
762            } else {
763                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
764                tx.send(Err(anyhow!(error))).ok();
765            }
766        }
767
768        unsafe {
769            LKRemoteTrackPublicationSetEnabled(
770                *self.native_publication.lock(),
771                enabled,
772                complete_callback,
773                Box::into_raw(Box::new(tx)) as *mut c_void,
774            )
775        }
776
777        async move { rx.await.unwrap() }
778    }
779}
780
781impl Drop for RemoteTrackPublication {
782    fn drop(&mut self) {
783        unsafe { CFRelease((*self.native_publication.lock()).0) }
784    }
785}
786
787#[derive(Debug)]
788pub struct RemoteAudioTrack {
789    native_track: Mutex<swift::RemoteAudioTrack>,
790    sid: Sid,
791    publisher_id: String,
792}
793
794impl RemoteAudioTrack {
795    fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
796        unsafe {
797            CFRetain(native_track.0);
798        }
799        Self {
800            native_track: Mutex::new(native_track),
801            sid,
802            publisher_id,
803        }
804    }
805
806    pub fn sid(&self) -> &str {
807        &self.sid
808    }
809
810    pub fn publisher_id(&self) -> &str {
811        &self.publisher_id
812    }
813
814    pub fn enable(&self) -> impl Future<Output = Result<()>> {
815        async { Ok(()) }
816    }
817
818    pub fn disable(&self) -> impl Future<Output = Result<()>> {
819        async { Ok(()) }
820    }
821}
822
823impl Drop for RemoteAudioTrack {
824    fn drop(&mut self) {
825        unsafe { CFRelease(self.native_track.lock().0) }
826    }
827}
828
829#[derive(Debug)]
830pub struct RemoteVideoTrack {
831    native_track: Mutex<swift::RemoteVideoTrack>,
832    sid: Sid,
833    publisher_id: String,
834}
835
836impl RemoteVideoTrack {
837    fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
838        unsafe {
839            CFRetain(native_track.0);
840        }
841        Self {
842            native_track: Mutex::new(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 frames(&self) -> async_broadcast::Receiver<Frame> {
857        extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool {
858            unsafe {
859                let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
860                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
861                let result = tx.try_broadcast(Frame(buffer));
862                let _ = Box::into_raw(tx);
863                match result {
864                    Ok(_) => true,
865                    Err(async_broadcast::TrySendError::Closed(_))
866                    | Err(async_broadcast::TrySendError::Inactive(_)) => {
867                        log::warn!("no active receiver for frame");
868                        false
869                    }
870                    Err(async_broadcast::TrySendError::Full(_)) => {
871                        log::warn!("skipping frame as receiver is not keeping up");
872                        true
873                    }
874                }
875            }
876        }
877
878        extern "C" fn on_drop(callback_data: *mut c_void) {
879            unsafe {
880                let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
881            }
882        }
883
884        let (tx, rx) = async_broadcast::broadcast(64);
885        unsafe {
886            let renderer = LKVideoRendererCreate(
887                Box::into_raw(Box::new(tx)) as *mut c_void,
888                on_frame,
889                on_drop,
890            );
891            LKVideoTrackAddRenderer(*self.native_track.lock(), renderer);
892            rx
893        }
894    }
895}
896
897impl Drop for RemoteVideoTrack {
898    fn drop(&mut self) {
899        unsafe { CFRelease(self.native_track.lock().0) }
900    }
901}
902
903pub enum RemoteVideoTrackUpdate {
904    Subscribed(Arc<RemoteVideoTrack>),
905    Unsubscribed { publisher_id: Sid, track_id: Sid },
906}
907
908pub enum RemoteAudioTrackUpdate {
909    ActiveSpeakersChanged { speakers: Vec<Sid> },
910    MuteChanged { track_id: Sid, muted: bool },
911    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
912    Unsubscribed { publisher_id: Sid, track_id: Sid },
913}
914
915pub struct MacOSDisplay(swift::MacOSDisplay);
916
917impl MacOSDisplay {
918    fn new(ptr: swift::MacOSDisplay) -> Self {
919        unsafe {
920            CFRetain(ptr.0);
921        }
922        Self(ptr)
923    }
924}
925
926impl Drop for MacOSDisplay {
927    fn drop(&mut self) {
928        unsafe { CFRelease(self.0 .0) }
929    }
930}
931
932#[derive(Clone)]
933pub struct Frame(CVImageBuffer);
934
935impl Frame {
936    pub fn width(&self) -> usize {
937        self.0.width()
938    }
939
940    pub fn height(&self) -> usize {
941        self.0.height()
942    }
943
944    pub fn image(&self) -> CVImageBuffer {
945        self.0.clone()
946    }
947}