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}