live_kit.rs

  1use anyhow::{anyhow, Context, Result};
  2use core_foundation::{
  3    array::CFArray,
  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}
 60
 61pub struct Room {
 62    native_room: *const c_void,
 63    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
 64    _delegate: RoomDelegate,
 65}
 66
 67impl Room {
 68    pub fn new() -> Arc<Self> {
 69        Arc::new_cyclic(|weak_room| {
 70            let delegate = RoomDelegate::new(weak_room.clone());
 71            Self {
 72                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
 73                remote_video_track_subscribers: Default::default(),
 74                _delegate: delegate,
 75            }
 76        })
 77    }
 78
 79    pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
 80        let url = CFString::new(url);
 81        let token = CFString::new(token);
 82        let (did_connect, tx, rx) = Self::build_done_callback();
 83        unsafe {
 84            LKRoomConnect(
 85                self.native_room,
 86                url.as_concrete_TypeRef(),
 87                token.as_concrete_TypeRef(),
 88                did_connect,
 89                tx,
 90            )
 91        }
 92
 93        async { rx.await.unwrap().context("error connecting to room") }
 94    }
 95
 96    pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
 97        let (did_publish, tx, rx) = Self::build_done_callback();
 98        unsafe {
 99            LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
100        }
101        async { rx.await.unwrap().context("error publishing video track") }
102    }
103
104    pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
105        let (tx, rx) = mpsc::unbounded();
106        self.remote_video_track_subscribers.lock().push(tx);
107        rx
108    }
109
110    fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
111        let track = Arc::new(track);
112        self.remote_video_track_subscribers
113            .lock()
114            .retain(|tx| tx.unbounded_send(track.clone()).is_ok());
115    }
116
117    fn build_done_callback() -> (
118        extern "C" fn(*mut c_void, CFStringRef),
119        *mut c_void,
120        oneshot::Receiver<Result<()>>,
121    ) {
122        let (tx, rx) = oneshot::channel();
123        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
124            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
125            if error.is_null() {
126                let _ = tx.send(Ok(()));
127            } else {
128                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
129                let _ = tx.send(Err(anyhow!(error)));
130            }
131        }
132        (
133            done_callback,
134            Box::into_raw(Box::new(tx)) as *mut c_void,
135            rx,
136        )
137    }
138}
139
140impl Drop for Room {
141    fn drop(&mut self) {
142        unsafe { LKRelease(self.native_room) }
143    }
144}
145
146struct RoomDelegate {
147    native_delegate: *const c_void,
148    weak_room: *const Room,
149}
150
151impl RoomDelegate {
152    fn new(weak_room: Weak<Room>) -> Self {
153        let weak_room = Weak::into_raw(weak_room);
154        let native_delegate = unsafe {
155            LKRoomDelegateCreate(
156                weak_room as *mut c_void,
157                Self::on_did_subscribe_to_remote_video_track,
158            )
159        };
160        Self {
161            native_delegate,
162            weak_room,
163        }
164    }
165
166    extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
167        let room = unsafe { Weak::from_raw(room as *mut Room) };
168        let track = RemoteVideoTrack(track);
169        if let Some(room) = room.upgrade() {
170            room.did_subscribe_to_remote_video_track(track);
171        }
172        let _ = Weak::into_raw(room);
173    }
174}
175
176impl Drop for RoomDelegate {
177    fn drop(&mut self) {
178        unsafe {
179            LKRelease(self.native_delegate);
180            let _ = Weak::from_raw(self.weak_room);
181        }
182    }
183}
184
185pub struct LocalVideoTrack(*const c_void);
186
187impl LocalVideoTrack {
188    pub fn screen_share_for_window(window_id: u32) -> Self {
189        Self(unsafe { LKCreateScreenShareTrackForWindow(window_id) })
190    }
191}
192
193impl Drop for LocalVideoTrack {
194    fn drop(&mut self) {
195        unsafe { LKRelease(self.0) }
196    }
197}
198
199pub struct RemoteVideoTrack(*const c_void);
200
201impl RemoteVideoTrack {
202    pub fn add_renderer<F>(&self, callback: F)
203    where
204        F: 'static + FnMut(CVImageBuffer),
205    {
206        extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
207        where
208            F: FnMut(CVImageBuffer),
209        {
210            unsafe {
211                let buffer = CVImageBuffer::wrap_under_get_rule(frame);
212                let callback = &mut *(callback_data as *mut F);
213                callback(buffer);
214            }
215        }
216
217        extern "C" fn on_drop<F>(callback_data: *mut c_void) {
218            unsafe {
219                let _ = Box::from_raw(callback_data as *mut F);
220            }
221        }
222
223        let callback_data = Box::into_raw(Box::new(callback));
224        unsafe {
225            let renderer =
226                LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
227            LKVideoTrackAddRenderer(self.0, renderer);
228        }
229    }
230}
231
232impl Drop for RemoteVideoTrack {
233    fn drop(&mut self) {
234        unsafe { LKRelease(self.0) }
235    }
236}
237
238#[derive(Debug)]
239pub struct WindowInfo {
240    pub id: u32,
241    pub owner_pid: i32,
242    pub owner_name: Option<String>,
243}
244
245pub fn list_windows() -> Vec<WindowInfo> {
246    unsafe {
247        let dicts = CFArray::<CFDictionary>::wrap_under_get_rule(CGWindowListCopyWindowInfo(
248            kCGWindowListOptionOnScreenOnly | kCGWindowListOptionExcludeDesktopElements,
249            kCGNullWindowID,
250        ));
251
252        dicts
253            .iter()
254            .map(|dict| {
255                let id =
256                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowNumber.as_void_ptr()) as _)
257                        .to_i64()
258                        .unwrap() as u32;
259
260                let owner_pid =
261                    CFNumber::wrap_under_get_rule(*dict.get(kCGWindowOwnerPID.as_void_ptr()) as _)
262                        .to_i32()
263                        .unwrap();
264
265                let owner_name = dict
266                    .find(kCGWindowOwnerName.as_void_ptr())
267                    .map(|name| CFString::wrap_under_get_rule(*name as _).to_string());
268                WindowInfo {
269                    id,
270                    owner_pid,
271                    owner_name,
272                }
273            })
274            .collect()
275    }
276}