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: *mut c_void,
517}
518
519impl RoomDelegate {
520 fn new(weak_room: Weak<Room>) -> Self {
521 let weak_room = weak_room.into_raw() as *mut c_void;
522 let native_delegate = unsafe {
523 LKRoomDelegateCreate(
524 weak_room,
525 Self::on_did_disconnect,
526 Self::on_did_subscribe_to_remote_audio_track,
527 Self::on_did_unsubscribe_from_remote_audio_track,
528 Self::on_mute_change_from_remote_audio_track,
529 Self::on_active_speakers_changed,
530 Self::on_did_subscribe_to_remote_video_track,
531 Self::on_did_unsubscribe_from_remote_video_track,
532 )
533 };
534 Self {
535 native_delegate,
536 weak_room,
537 }
538 }
539
540 extern "C" fn on_did_disconnect(room: *mut c_void) {
541 let room = unsafe { Weak::from_raw(room as *mut Room) };
542 if let Some(room) = room.upgrade() {
543 room.did_disconnect();
544 }
545 let _ = Weak::into_raw(room);
546 }
547
548 extern "C" fn on_did_subscribe_to_remote_audio_track(
549 room: *mut c_void,
550 publisher_id: CFStringRef,
551 track_id: CFStringRef,
552 track: swift::RemoteAudioTrack,
553 publication: swift::RemoteTrackPublication,
554 ) {
555 let room = unsafe { Weak::from_raw(room as *mut Room) };
556 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
557 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
558 let track = RemoteAudioTrack::new(track, track_id, publisher_id);
559 let publication = RemoteTrackPublication::new(publication);
560 if let Some(room) = room.upgrade() {
561 room.did_subscribe_to_remote_audio_track(track, publication);
562 }
563 let _ = Weak::into_raw(room);
564 }
565
566 extern "C" fn on_did_unsubscribe_from_remote_audio_track(
567 room: *mut c_void,
568 publisher_id: CFStringRef,
569 track_id: CFStringRef,
570 ) {
571 let room = unsafe { Weak::from_raw(room as *mut Room) };
572 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
573 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
574 if let Some(room) = room.upgrade() {
575 room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id);
576 }
577 let _ = Weak::into_raw(room);
578 }
579
580 extern "C" fn on_mute_change_from_remote_audio_track(
581 room: *mut c_void,
582 track_id: CFStringRef,
583 muted: bool,
584 ) {
585 let room = unsafe { Weak::from_raw(room as *mut Room) };
586 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
587 if let Some(room) = room.upgrade() {
588 room.mute_changed_from_remote_audio_track(track_id, muted);
589 }
590 let _ = Weak::into_raw(room);
591 }
592
593 extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
594 if participants.is_null() {
595 return;
596 }
597
598 let room = unsafe { Weak::from_raw(room as *mut Room) };
599 let speakers = unsafe {
600 CFArray::wrap_under_get_rule(participants)
601 .into_iter()
602 .map(
603 |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
604 CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
605 },
606 )
607 .collect()
608 };
609
610 if let Some(room) = room.upgrade() {
611 room.active_speakers_changed(speakers);
612 }
613 let _ = Weak::into_raw(room);
614 }
615
616 extern "C" fn on_did_subscribe_to_remote_video_track(
617 room: *mut c_void,
618 publisher_id: CFStringRef,
619 track_id: CFStringRef,
620 track: swift::RemoteVideoTrack,
621 ) {
622 let room = unsafe { Weak::from_raw(room as *mut Room) };
623 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
624 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
625 let track = RemoteVideoTrack::new(track, track_id, publisher_id);
626 if let Some(room) = room.upgrade() {
627 room.did_subscribe_to_remote_video_track(track);
628 }
629 let _ = Weak::into_raw(room);
630 }
631
632 extern "C" fn on_did_unsubscribe_from_remote_video_track(
633 room: *mut c_void,
634 publisher_id: CFStringRef,
635 track_id: CFStringRef,
636 ) {
637 let room = unsafe { Weak::from_raw(room as *mut Room) };
638 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
639 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
640 if let Some(room) = room.upgrade() {
641 room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
642 }
643 let _ = Weak::into_raw(room);
644 }
645}
646
647impl Drop for RoomDelegate {
648 fn drop(&mut self) {
649 unsafe {
650 CFRelease(self.native_delegate.0);
651 let _ = Weak::from_raw(self.weak_room);
652 }
653 }
654}
655
656pub struct LocalAudioTrack(swift::LocalAudioTrack);
657
658impl LocalAudioTrack {
659 pub fn create() -> Self {
660 Self(unsafe { LKLocalAudioTrackCreateTrack() })
661 }
662}
663
664impl Drop for LocalAudioTrack {
665 fn drop(&mut self) {
666 unsafe { CFRelease(self.0 .0) }
667 }
668}
669
670pub struct LocalVideoTrack(swift::LocalVideoTrack);
671
672impl LocalVideoTrack {
673 pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
674 Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
675 }
676}
677
678impl Drop for LocalVideoTrack {
679 fn drop(&mut self) {
680 unsafe { CFRelease(self.0 .0) }
681 }
682}
683
684pub struct LocalTrackPublication(swift::LocalTrackPublication);
685
686impl LocalTrackPublication {
687 pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
688 unsafe {
689 CFRetain(native_track_publication.0);
690 }
691 Self(native_track_publication)
692 }
693
694 pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
695 let (tx, rx) = futures::channel::oneshot::channel();
696
697 extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
698 let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
699 if error.is_null() {
700 tx.send(Ok(())).ok();
701 } else {
702 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
703 tx.send(Err(anyhow!(error))).ok();
704 }
705 }
706
707 unsafe {
708 LKLocalTrackPublicationSetMute(
709 self.0,
710 muted,
711 complete_callback,
712 Box::into_raw(Box::new(tx)) as *mut c_void,
713 )
714 }
715
716 async move { rx.await.unwrap() }
717 }
718}
719
720impl Drop for LocalTrackPublication {
721 fn drop(&mut self) {
722 unsafe { CFRelease(self.0 .0) }
723 }
724}
725
726pub struct RemoteTrackPublication(swift::RemoteTrackPublication);
727
728impl RemoteTrackPublication {
729 pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
730 unsafe {
731 CFRetain(native_track_publication.0);
732 }
733 Self(native_track_publication)
734 }
735
736 pub fn sid(&self) -> String {
737 unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() }
738 }
739
740 pub fn is_muted(&self) -> bool {
741 unsafe { LKRemoteTrackPublicationIsMuted(self.0) }
742 }
743
744 pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
745 let (tx, rx) = futures::channel::oneshot::channel();
746
747 extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
748 let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
749 if error.is_null() {
750 tx.send(Ok(())).ok();
751 } else {
752 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
753 tx.send(Err(anyhow!(error))).ok();
754 }
755 }
756
757 unsafe {
758 LKRemoteTrackPublicationSetEnabled(
759 self.0,
760 enabled,
761 complete_callback,
762 Box::into_raw(Box::new(tx)) as *mut c_void,
763 )
764 }
765
766 async move { rx.await.unwrap() }
767 }
768}
769
770impl Drop for RemoteTrackPublication {
771 fn drop(&mut self) {
772 unsafe { CFRelease(self.0 .0) }
773 }
774}
775
776#[derive(Debug)]
777pub struct RemoteAudioTrack {
778 native_track: swift::RemoteAudioTrack,
779 sid: Sid,
780 publisher_id: String,
781}
782
783impl RemoteAudioTrack {
784 fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
785 unsafe {
786 CFRetain(native_track.0);
787 }
788 Self {
789 native_track,
790 sid,
791 publisher_id,
792 }
793 }
794
795 pub fn sid(&self) -> &str {
796 &self.sid
797 }
798
799 pub fn publisher_id(&self) -> &str {
800 &self.publisher_id
801 }
802
803 pub fn enable(&self) -> impl Future<Output = Result<()>> {
804 async { Ok(()) }
805 }
806
807 pub fn disable(&self) -> impl Future<Output = Result<()>> {
808 async { Ok(()) }
809 }
810}
811
812impl Drop for RemoteAudioTrack {
813 fn drop(&mut self) {
814 unsafe { CFRelease(self.native_track.0) }
815 }
816}
817
818#[derive(Debug)]
819pub struct RemoteVideoTrack {
820 native_track: swift::RemoteVideoTrack,
821 sid: Sid,
822 publisher_id: String,
823}
824
825impl RemoteVideoTrack {
826 fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
827 unsafe {
828 CFRetain(native_track.0);
829 }
830 Self {
831 native_track,
832 sid,
833 publisher_id,
834 }
835 }
836
837 pub fn sid(&self) -> &str {
838 &self.sid
839 }
840
841 pub fn publisher_id(&self) -> &str {
842 &self.publisher_id
843 }
844
845 pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
846 extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool {
847 unsafe {
848 let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
849 let buffer = CVImageBuffer::wrap_under_get_rule(frame);
850 let result = tx.try_broadcast(Frame(buffer));
851 let _ = Box::into_raw(tx);
852 match result {
853 Ok(_) => true,
854 Err(async_broadcast::TrySendError::Closed(_))
855 | Err(async_broadcast::TrySendError::Inactive(_)) => {
856 log::warn!("no active receiver for frame");
857 false
858 }
859 Err(async_broadcast::TrySendError::Full(_)) => {
860 log::warn!("skipping frame as receiver is not keeping up");
861 true
862 }
863 }
864 }
865 }
866
867 extern "C" fn on_drop(callback_data: *mut c_void) {
868 unsafe {
869 let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
870 }
871 }
872
873 let (tx, rx) = async_broadcast::broadcast(64);
874 unsafe {
875 let renderer = LKVideoRendererCreate(
876 Box::into_raw(Box::new(tx)) as *mut c_void,
877 on_frame,
878 on_drop,
879 );
880 LKVideoTrackAddRenderer(self.native_track, renderer);
881 rx
882 }
883 }
884}
885
886impl Drop for RemoteVideoTrack {
887 fn drop(&mut self) {
888 unsafe { CFRelease(self.native_track.0) }
889 }
890}
891
892pub enum RemoteVideoTrackUpdate {
893 Subscribed(Arc<RemoteVideoTrack>),
894 Unsubscribed { publisher_id: Sid, track_id: Sid },
895}
896
897pub enum RemoteAudioTrackUpdate {
898 ActiveSpeakersChanged { speakers: Vec<Sid> },
899 MuteChanged { track_id: Sid, muted: bool },
900 Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
901 Unsubscribed { publisher_id: Sid, track_id: Sid },
902}
903
904pub struct MacOSDisplay(swift::MacOSDisplay);
905
906impl MacOSDisplay {
907 fn new(ptr: swift::MacOSDisplay) -> Self {
908 unsafe {
909 CFRetain(ptr.0);
910 }
911 Self(ptr)
912 }
913}
914
915impl Drop for MacOSDisplay {
916 fn drop(&mut self) {
917 unsafe { CFRelease(self.0 .0) }
918 }
919}
920
921#[derive(Clone)]
922pub struct Frame(CVImageBuffer);
923
924impl Frame {
925 pub fn width(&self) -> usize {
926 self.0.width()
927 }
928
929 pub fn height(&self) -> usize {
930 self.0.height()
931 }
932
933 pub fn image(&self) -> CVImageBuffer {
934 self.0.clone()
935 }
936}