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