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: Mutex<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: Mutex<RoomDelegate>,
175}
176
177trait AssertSendSync: Send {}
178impl AssertSendSync for Room {}
179
180impl Room {
181 pub fn new() -> Arc<Self> {
182 Arc::new_cyclic(|weak_room| {
183 let delegate = RoomDelegate::new(weak_room.clone());
184 Self {
185 native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }),
186 connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)),
187 remote_audio_track_subscribers: Default::default(),
188 remote_video_track_subscribers: Default::default(),
189 _delegate: Mutex::new(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.lock(),
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.lock(),
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.lock(),
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.lock(), 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.lock(),
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.lock(),
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.lock(),
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 remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> {
404 let (tx, rx) = mpsc::unbounded();
405 self.remote_audio_track_subscribers.lock().push(tx);
406 rx
407 }
408
409 pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
410 let (tx, rx) = mpsc::unbounded();
411 self.remote_video_track_subscribers.lock().push(tx);
412 rx
413 }
414
415 fn did_subscribe_to_remote_audio_track(
416 &self,
417 track: RemoteAudioTrack,
418 publication: RemoteTrackPublication,
419 ) {
420 let track = Arc::new(track);
421 let publication = Arc::new(publication);
422 self.remote_audio_track_subscribers.lock().retain(|tx| {
423 tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(
424 track.clone(),
425 publication.clone(),
426 ))
427 .is_ok()
428 });
429 }
430
431 fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) {
432 self.remote_audio_track_subscribers.lock().retain(|tx| {
433 tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed {
434 publisher_id: publisher_id.clone(),
435 track_id: track_id.clone(),
436 })
437 .is_ok()
438 });
439 }
440
441 fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) {
442 self.remote_audio_track_subscribers.lock().retain(|tx| {
443 tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged {
444 track_id: track_id.clone(),
445 muted,
446 })
447 .is_ok()
448 });
449 }
450
451 // A vec of publisher IDs
452 fn active_speakers_changed(&self, speakers: Vec<String>) {
453 self.remote_audio_track_subscribers
454 .lock()
455 .retain(move |tx| {
456 tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged {
457 speakers: speakers.clone(),
458 })
459 .is_ok()
460 });
461 }
462
463 fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
464 let track = Arc::new(track);
465 self.remote_video_track_subscribers.lock().retain(|tx| {
466 tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
467 .is_ok()
468 });
469 }
470
471 fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
472 self.remote_video_track_subscribers.lock().retain(|tx| {
473 tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
474 publisher_id: publisher_id.clone(),
475 track_id: track_id.clone(),
476 })
477 .is_ok()
478 });
479 }
480
481 fn build_done_callback() -> (
482 extern "C" fn(*mut c_void, CFStringRef),
483 *mut c_void,
484 oneshot::Receiver<Result<()>>,
485 ) {
486 let (tx, rx) = oneshot::channel();
487 extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
488 let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
489 if error.is_null() {
490 let _ = tx.send(Ok(()));
491 } else {
492 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
493 let _ = tx.send(Err(anyhow!(error)));
494 }
495 }
496 (
497 done_callback,
498 Box::into_raw(Box::new(tx)) as *mut c_void,
499 rx,
500 )
501 }
502
503 pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
504 unreachable!("This is a test-only function")
505 }
506}
507
508impl Drop for Room {
509 fn drop(&mut self) {
510 unsafe {
511 let native_room = &*self.native_room.lock();
512 LKRoomDisconnect(*native_room);
513 CFRelease(native_room.0);
514 }
515 }
516}
517
518struct RoomDelegate {
519 native_delegate: swift::RoomDelegate,
520 _weak_room: Weak<Room>,
521}
522
523impl RoomDelegate {
524 fn new(weak_room: Weak<Room>) -> Self {
525 let native_delegate = unsafe {
526 LKRoomDelegateCreate(
527 weak_room.as_ptr() as *mut c_void,
528 Self::on_did_disconnect,
529 Self::on_did_subscribe_to_remote_audio_track,
530 Self::on_did_unsubscribe_from_remote_audio_track,
531 Self::on_mute_change_from_remote_audio_track,
532 Self::on_active_speakers_changed,
533 Self::on_did_subscribe_to_remote_video_track,
534 Self::on_did_unsubscribe_from_remote_video_track,
535 )
536 };
537 Self {
538 native_delegate,
539 _weak_room: weak_room,
540 }
541 }
542
543 extern "C" fn on_did_disconnect(room: *mut c_void) {
544 let room = unsafe { Weak::from_raw(room as *mut Room) };
545 if let Some(room) = room.upgrade() {
546 room.did_disconnect();
547 }
548 let _ = Weak::into_raw(room);
549 }
550
551 extern "C" fn on_did_subscribe_to_remote_audio_track(
552 room: *mut c_void,
553 publisher_id: CFStringRef,
554 track_id: CFStringRef,
555 track: swift::RemoteAudioTrack,
556 publication: swift::RemoteTrackPublication,
557 ) {
558 let room = unsafe { Weak::from_raw(room as *mut Room) };
559 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
560 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
561 let track = RemoteAudioTrack::new(track, track_id, publisher_id);
562 let publication = RemoteTrackPublication::new(publication);
563 if let Some(room) = room.upgrade() {
564 room.did_subscribe_to_remote_audio_track(track, publication);
565 }
566 let _ = Weak::into_raw(room);
567 }
568
569 extern "C" fn on_did_unsubscribe_from_remote_audio_track(
570 room: *mut c_void,
571 publisher_id: CFStringRef,
572 track_id: CFStringRef,
573 ) {
574 let room = unsafe { Weak::from_raw(room as *mut Room) };
575 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
576 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
577 if let Some(room) = room.upgrade() {
578 room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id);
579 }
580 let _ = Weak::into_raw(room);
581 }
582
583 extern "C" fn on_mute_change_from_remote_audio_track(
584 room: *mut c_void,
585 track_id: CFStringRef,
586 muted: bool,
587 ) {
588 let room = unsafe { Weak::from_raw(room as *mut Room) };
589 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
590 if let Some(room) = room.upgrade() {
591 room.mute_changed_from_remote_audio_track(track_id, muted);
592 }
593 let _ = Weak::into_raw(room);
594 }
595
596 extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) {
597 if participants.is_null() {
598 return;
599 }
600
601 let room = unsafe { Weak::from_raw(room as *mut Room) };
602 let speakers = unsafe {
603 CFArray::wrap_under_get_rule(participants)
604 .into_iter()
605 .map(
606 |speaker: core_foundation::base::ItemRef<'_, *const c_void>| {
607 CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string()
608 },
609 )
610 .collect()
611 };
612
613 if let Some(room) = room.upgrade() {
614 room.active_speakers_changed(speakers);
615 }
616 let _ = Weak::into_raw(room);
617 }
618
619 extern "C" fn on_did_subscribe_to_remote_video_track(
620 room: *mut c_void,
621 publisher_id: CFStringRef,
622 track_id: CFStringRef,
623 track: swift::RemoteVideoTrack,
624 ) {
625 let room = unsafe { Weak::from_raw(room as *mut Room) };
626 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
627 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
628 let track = RemoteVideoTrack::new(track, track_id, publisher_id);
629 if let Some(room) = room.upgrade() {
630 room.did_subscribe_to_remote_video_track(track);
631 }
632 let _ = Weak::into_raw(room);
633 }
634
635 extern "C" fn on_did_unsubscribe_from_remote_video_track(
636 room: *mut c_void,
637 publisher_id: CFStringRef,
638 track_id: CFStringRef,
639 ) {
640 let room = unsafe { Weak::from_raw(room as *mut Room) };
641 let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
642 let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
643 if let Some(room) = room.upgrade() {
644 room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
645 }
646 let _ = Weak::into_raw(room);
647 }
648}
649
650impl Drop for RoomDelegate {
651 fn drop(&mut self) {
652 unsafe {
653 CFRelease(self.native_delegate.0);
654 }
655 }
656}
657
658pub struct LocalAudioTrack(swift::LocalAudioTrack);
659
660impl LocalAudioTrack {
661 pub fn create() -> Self {
662 Self(unsafe { LKLocalAudioTrackCreateTrack() })
663 }
664}
665
666impl Drop for LocalAudioTrack {
667 fn drop(&mut self) {
668 unsafe { CFRelease(self.0 .0) }
669 }
670}
671
672pub struct LocalVideoTrack(swift::LocalVideoTrack);
673
674impl LocalVideoTrack {
675 pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
676 Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
677 }
678}
679
680impl Drop for LocalVideoTrack {
681 fn drop(&mut self) {
682 unsafe { CFRelease(self.0 .0) }
683 }
684}
685
686pub struct LocalTrackPublication(swift::LocalTrackPublication);
687
688impl LocalTrackPublication {
689 pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self {
690 unsafe {
691 CFRetain(native_track_publication.0);
692 }
693 Self(native_track_publication)
694 }
695
696 pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> {
697 let (tx, rx) = futures::channel::oneshot::channel();
698
699 extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
700 let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
701 if error.is_null() {
702 tx.send(Ok(())).ok();
703 } else {
704 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
705 tx.send(Err(anyhow!(error))).ok();
706 }
707 }
708
709 unsafe {
710 LKLocalTrackPublicationSetMute(
711 self.0,
712 muted,
713 complete_callback,
714 Box::into_raw(Box::new(tx)) as *mut c_void,
715 )
716 }
717
718 async move { rx.await.unwrap() }
719 }
720}
721
722impl Drop for LocalTrackPublication {
723 fn drop(&mut self) {
724 unsafe { CFRelease(self.0 .0) }
725 }
726}
727
728pub struct RemoteTrackPublication {
729 native_publication: Mutex<swift::RemoteTrackPublication>,
730}
731
732impl RemoteTrackPublication {
733 pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self {
734 unsafe {
735 CFRetain(native_track_publication.0);
736 }
737 Self {
738 native_publication: Mutex::new(native_track_publication),
739 }
740 }
741
742 pub fn sid(&self) -> String {
743 unsafe {
744 CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(
745 *self.native_publication.lock(),
746 ))
747 .to_string()
748 }
749 }
750
751 pub fn is_muted(&self) -> bool {
752 unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) }
753 }
754
755 pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
756 let (tx, rx) = futures::channel::oneshot::channel();
757
758 extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
759 let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
760 if error.is_null() {
761 tx.send(Ok(())).ok();
762 } else {
763 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
764 tx.send(Err(anyhow!(error))).ok();
765 }
766 }
767
768 unsafe {
769 LKRemoteTrackPublicationSetEnabled(
770 *self.native_publication.lock(),
771 enabled,
772 complete_callback,
773 Box::into_raw(Box::new(tx)) as *mut c_void,
774 )
775 }
776
777 async move { rx.await.unwrap() }
778 }
779}
780
781impl Drop for RemoteTrackPublication {
782 fn drop(&mut self) {
783 unsafe { CFRelease((*self.native_publication.lock()).0) }
784 }
785}
786
787#[derive(Debug)]
788pub struct RemoteAudioTrack {
789 native_track: Mutex<swift::RemoteAudioTrack>,
790 sid: Sid,
791 publisher_id: String,
792}
793
794impl RemoteAudioTrack {
795 fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self {
796 unsafe {
797 CFRetain(native_track.0);
798 }
799 Self {
800 native_track: Mutex::new(native_track),
801 sid,
802 publisher_id,
803 }
804 }
805
806 pub fn sid(&self) -> &str {
807 &self.sid
808 }
809
810 pub fn publisher_id(&self) -> &str {
811 &self.publisher_id
812 }
813
814 pub fn enable(&self) -> impl Future<Output = Result<()>> {
815 async { Ok(()) }
816 }
817
818 pub fn disable(&self) -> impl Future<Output = Result<()>> {
819 async { Ok(()) }
820 }
821}
822
823impl Drop for RemoteAudioTrack {
824 fn drop(&mut self) {
825 unsafe { CFRelease(self.native_track.lock().0) }
826 }
827}
828
829#[derive(Debug)]
830pub struct RemoteVideoTrack {
831 native_track: Mutex<swift::RemoteVideoTrack>,
832 sid: Sid,
833 publisher_id: String,
834}
835
836impl RemoteVideoTrack {
837 fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self {
838 unsafe {
839 CFRetain(native_track.0);
840 }
841 Self {
842 native_track: Mutex::new(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 frames(&self) -> async_broadcast::Receiver<Frame> {
857 extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool {
858 unsafe {
859 let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
860 let buffer = CVImageBuffer::wrap_under_get_rule(frame);
861 let result = tx.try_broadcast(Frame(buffer));
862 let _ = Box::into_raw(tx);
863 match result {
864 Ok(_) => true,
865 Err(async_broadcast::TrySendError::Closed(_))
866 | Err(async_broadcast::TrySendError::Inactive(_)) => {
867 log::warn!("no active receiver for frame");
868 false
869 }
870 Err(async_broadcast::TrySendError::Full(_)) => {
871 log::warn!("skipping frame as receiver is not keeping up");
872 true
873 }
874 }
875 }
876 }
877
878 extern "C" fn on_drop(callback_data: *mut c_void) {
879 unsafe {
880 let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender<Frame>);
881 }
882 }
883
884 let (tx, rx) = async_broadcast::broadcast(64);
885 unsafe {
886 let renderer = LKVideoRendererCreate(
887 Box::into_raw(Box::new(tx)) as *mut c_void,
888 on_frame,
889 on_drop,
890 );
891 LKVideoTrackAddRenderer(*self.native_track.lock(), renderer);
892 rx
893 }
894 }
895}
896
897impl Drop for RemoteVideoTrack {
898 fn drop(&mut self) {
899 unsafe { CFRelease(self.native_track.lock().0) }
900 }
901}
902
903pub enum RemoteVideoTrackUpdate {
904 Subscribed(Arc<RemoteVideoTrack>),
905 Unsubscribed { publisher_id: Sid, track_id: Sid },
906}
907
908pub enum RemoteAudioTrackUpdate {
909 ActiveSpeakersChanged { speakers: Vec<Sid> },
910 MuteChanged { track_id: Sid, muted: bool },
911 Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
912 Unsubscribed { publisher_id: Sid, track_id: Sid },
913}
914
915pub struct MacOSDisplay(swift::MacOSDisplay);
916
917impl MacOSDisplay {
918 fn new(ptr: swift::MacOSDisplay) -> Self {
919 unsafe {
920 CFRetain(ptr.0);
921 }
922 Self(ptr)
923 }
924}
925
926impl Drop for MacOSDisplay {
927 fn drop(&mut self) {
928 unsafe { CFRelease(self.0 .0) }
929 }
930}
931
932#[derive(Clone)]
933pub struct Frame(CVImageBuffer);
934
935impl Frame {
936 pub fn width(&self) -> usize {
937 self.0.width()
938 }
939
940 pub fn height(&self) -> usize {
941 self.0.height()
942 }
943
944 pub fn image(&self) -> CVImageBuffer {
945 self.0.clone()
946 }
947}