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