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