live_kit.rs

  1use anyhow::{anyhow, Context, Result};
  2use core_foundation::{
  3    array::{CFArray, CFArrayRef},
  4    base::{TCFType, TCFTypeRef},
  5    dictionary::CFDictionary,
  6    number::CFNumber,
  7    string::{CFString, CFStringRef},
  8};
  9use core_graphics::window::{
 10    kCGNullWindowID, kCGWindowListOptionExcludeDesktopElements, kCGWindowListOptionOnScreenOnly,
 11    kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID, CGWindowListCopyWindowInfo,
 12};
 13use futures::{
 14    channel::{mpsc, oneshot},
 15    Future,
 16};
 17use media::core_video::{CVImageBuffer, CVImageBufferRef};
 18use parking_lot::Mutex;
 19use std::{
 20    ffi::c_void,
 21    sync::{Arc, Weak},
 22};
 23
 24extern "C" {
 25    fn LKRelease(object: *const c_void);
 26
 27    fn LKRoomDelegateCreate(
 28        callback_data: *mut c_void,
 29        on_did_subscribe_to_remote_video_track: extern "C" fn(
 30            callback_data: *mut c_void,
 31            remote_track: *const c_void,
 32        ),
 33    ) -> *const c_void;
 34
 35    fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
 36    fn LKRoomConnect(
 37        room: *const c_void,
 38        url: CFStringRef,
 39        token: CFStringRef,
 40        callback: extern "C" fn(*mut c_void, CFStringRef),
 41        callback_data: *mut c_void,
 42    );
 43    fn LKRoomPublishVideoTrack(
 44        room: *const c_void,
 45        track: *const c_void,
 46        callback: extern "C" fn(*mut c_void, CFStringRef),
 47        callback_data: *mut c_void,
 48    );
 49
 50    fn LKVideoRendererCreate(
 51        callback_data: *mut c_void,
 52        on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef),
 53        on_drop: extern "C" fn(callback_data: *mut c_void),
 54    ) -> *const c_void;
 55
 56    fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
 57
 58    fn LKCreateScreenShareTrackForWindow(windowId: u32) -> *const c_void;
 59    fn LKDisplaySources(
 60        callback_data: *mut c_void,
 61        callback: extern "C" fn(
 62            callback_data: *mut c_void,
 63            sources: CFArrayRef,
 64            error: CFStringRef,
 65        ),
 66    );
 67}
 68
 69pub struct Room {
 70    native_room: *const c_void,
 71    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
 72    _delegate: RoomDelegate,
 73}
 74
 75impl Room {
 76    pub fn new() -> Arc<Self> {
 77        Arc::new_cyclic(|weak_room| {
 78            let delegate = RoomDelegate::new(weak_room.clone());
 79            Self {
 80                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
 81                remote_video_track_subscribers: Default::default(),
 82                _delegate: delegate,
 83            }
 84        })
 85    }
 86
 87    pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
 88        let url = CFString::new(url);
 89        let token = CFString::new(token);
 90        let (did_connect, tx, rx) = Self::build_done_callback();
 91        unsafe {
 92            LKRoomConnect(
 93                self.native_room,
 94                url.as_concrete_TypeRef(),
 95                token.as_concrete_TypeRef(),
 96                did_connect,
 97                tx,
 98            )
 99        }
100
101        async { rx.await.unwrap().context("error connecting to room") }
102    }
103
104    pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
105        let (did_publish, tx, rx) = Self::build_done_callback();
106        unsafe {
107            LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
108        }
109        async { rx.await.unwrap().context("error publishing video track") }
110    }
111
112    pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
113        let (tx, rx) = mpsc::unbounded();
114        self.remote_video_track_subscribers.lock().push(tx);
115        rx
116    }
117
118    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
119        let track = Arc::new(track);
120        self.remote_video_track_subscribers
121            .lock()
122            .retain(|tx| tx.unbounded_send(track.clone()).is_ok());
123    }
124
125    fn build_done_callback() -> (
126        extern "C" fn(*mut c_void, CFStringRef),
127        *mut c_void,
128        oneshot::Receiver<Result<()>>,
129    ) {
130        let (tx, rx) = oneshot::channel();
131        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
132            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
133            if error.is_null() {
134                let _ = tx.send(Ok(()));
135            } else {
136                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
137                let _ = tx.send(Err(anyhow!(error)));
138            }
139        }
140        (
141            done_callback,
142            Box::into_raw(Box::new(tx)) as *mut c_void,
143            rx,
144        )
145    }
146}
147
148impl Drop for Room {
149    fn drop(&mut self) {
150        unsafe { LKRelease(self.native_room) }
151    }
152}
153
154struct RoomDelegate {
155    native_delegate: *const c_void,
156    weak_room: *const Room,
157}
158
159impl RoomDelegate {
160    fn new(weak_room: Weak<Room>) -> Self {
161        let weak_room = Weak::into_raw(weak_room);
162        let native_delegate = unsafe {
163            LKRoomDelegateCreate(
164                weak_room as *mut c_void,
165                Self::on_did_subscribe_to_remote_video_track,
166            )
167        };
168        Self {
169            native_delegate,
170            weak_room,
171        }
172    }
173
174    extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
175        let room = unsafe { Weak::from_raw(room as *mut Room) };
176        let track = RemoteVideoTrack(track);
177        if let Some(room) = room.upgrade() {
178            room.did_subscribe_to_remote_video_track(track);
179        }
180        let _ = Weak::into_raw(room);
181    }
182}
183
184impl Drop for RoomDelegate {
185    fn drop(&mut self) {
186        unsafe {
187            LKRelease(self.native_delegate);
188            let _ = Weak::from_raw(self.weak_room);
189        }
190    }
191}
192
193pub struct LocalVideoTrack(*const c_void);
194
195impl LocalVideoTrack {
196    pub fn screen_share_for_window(window_id: u32) -> Self {
197        Self(unsafe { LKCreateScreenShareTrackForWindow(window_id) })
198    }
199}
200
201impl Drop for LocalVideoTrack {
202    fn drop(&mut self) {
203        unsafe { LKRelease(self.0) }
204    }
205}
206
207pub struct RemoteVideoTrack(*const c_void);
208
209impl RemoteVideoTrack {
210    pub fn add_renderer<F>(&self, callback: F)
211    where
212        F: 'static + FnMut(CVImageBuffer),
213    {
214        extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
215        where
216            F: FnMut(CVImageBuffer),
217        {
218            unsafe {
219                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
220                let callback = &mut *(callback_data as *mut F);
221                callback(buffer);
222            }
223        }
224
225        extern "C" fn on_drop<F>(callback_data: *mut c_void) {
226            unsafe {
227                let _ = Box::from_raw(callback_data as *mut F);
228            }
229        }
230
231        let callback_data = Box::into_raw(Box::new(callback));
232        unsafe {
233            let renderer =
234                LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
235            LKVideoTrackAddRenderer(self.0, renderer);
236        }
237    }
238}
239
240impl Drop for RemoteVideoTrack {
241    fn drop(&mut self) {
242        unsafe { LKRelease(self.0) }
243    }
244}
245
246#[derive(Debug)]
247pub struct WindowInfo {
248    pub id: u32,
249    pub owner_pid: i32,
250    pub owner_name: Option<String>,
251}
252
253pub fn list_windows() -> Vec<WindowInfo> {
254    unsafe {
255        let dicts = CFArray::<CFDictionary>::wrap_under_get_rule(CGWindowListCopyWindowInfo(
256            kCGWindowListOptionOnScreenOnly | kCGWindowListOptionExcludeDesktopElements,
257            kCGNullWindowID,
258        ));
259
260        dicts
261            .iter()
262            .map(|dict| {
263                let id =
264                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowNumber.as_void_ptr()) as _)
265                        .to_i64()
266                        .unwrap() as u32;
267
268                let owner_pid =
269                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowOwnerPID.as_void_ptr()) as _)
270                        .to_i32()
271                        .unwrap();
272
273                let owner_name = dict
274                    .find(kCGWindowOwnerName.as_void_ptr())
275                    .map(|name| CFString::wrap_under_get_rule(*name as _).to_string());
276                WindowInfo {
277                    id,
278                    owner_pid,
279                    owner_name,
280                }
281            })
282            .collect()
283    }
284}
285
286pub struct MacOSDisplay(*const c_void);
287
288pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
289    extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
290        unsafe {
291            let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
292
293            if sources.is_null() {
294                let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
295            } else {
296                let sources = CFArray::wrap_under_get_rule(sources);
297                let sources = sources
298                    .into_iter()
299                    .map(|source| MacOSDisplay(*source))
300                    .collect();
301                let _ = tx.send(Ok(sources));
302            }
303        }
304    }
305
306    let (tx, rx) = oneshot::channel();
307
308    unsafe {
309        LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
310    }
311
312    async move { rx.await.unwrap() }
313}