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