Register to publishing of remote tracks

Antonio Scandurra created

Change summary

crates/capture/src/main.rs                                              | 37 
crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift | 47 
crates/live_kit/src/live_kit.rs                                         | 93 
3 files changed, 143 insertions(+), 34 deletions(-)

Detailed changes

crates/capture/src/main.rs 🔗

@@ -1,5 +1,7 @@
 mod live_kit_token;
 
+use std::time::Duration;
+
 use gpui::{
     actions,
     elements::{Canvas, *},
@@ -34,25 +36,38 @@ fn main() {
         let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap();
         let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap();
 
-        let token = live_kit_token::create_token(
-            &live_kit_key,
-            &live_kit_secret,
-            "test-room",
-            "test-participant",
-        )
-        .unwrap();
-
-        let room = Room::new();
+        let background = cx.background().clone();
         cx.foreground()
             .spawn(async move {
                 println!("connecting...");
-                room.connect(&live_kit_url, &token).await.unwrap();
+                let user1_token = live_kit_token::create_token(
+                    &live_kit_key,
+                    &live_kit_secret,
+                    "test-room",
+                    "test-participant-1",
+                )
+                .unwrap();
+                let room1 = Room::new("user-1 room");
+                room1.connect(&live_kit_url, &user1_token).await.unwrap();
+
+                let user2_token = live_kit_token::create_token(
+                    &live_kit_key,
+                    &live_kit_secret,
+                    "test-room",
+                    "test-participant-2",
+                )
+                .unwrap();
+                let room2 = Room::new("user-2 room");
+                room2.connect(&live_kit_url, &user2_token).await.unwrap();
+
                 let windows = live_kit::list_windows();
                 println!("connected! {:?}", windows);
 
                 let window_id = windows.iter().next().unwrap().id;
                 let track = LocalVideoTrack::screen_share_for_window(window_id);
-                room.publish_video_track(&track).await.unwrap();
+                room1.publish_video_track(&track).await.unwrap();
+
+                background.timer(Duration::from_secs(120)).await;
             })
             .detach();
 

crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift 🔗

@@ -1,40 +1,61 @@
 import Foundation
 import LiveKit
 
+class LKRoomDelegate: RoomDelegate {
+    var data: UnsafeRawPointer
+    var onDidSubscribeToRemoteTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void
+    
+    init(data: UnsafeRawPointer, onDidSubscribeToRemoteTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) {
+        self.data = data
+        self.onDidSubscribeToRemoteTrack = onDidSubscribeToRemoteTrack
+    }
+    
+    func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
+        self.onDidSubscribeToRemoteTrack(self.data, Unmanaged.passRetained(track).toOpaque())
+    }
+}
+
 @_cdecl("LKRelease")
 public func LKRelease(ptr: UnsafeRawPointer)  {
-    let _ = Unmanaged<AnyObject>.fromOpaque(ptr).takeRetainedValue();
+    let _ = Unmanaged<AnyObject>.fromOpaque(ptr).takeRetainedValue()
+}
+
+@_cdecl("LKRoomDelegateCreate")
+public func LKRoomDelegateCreate(data: UnsafeRawPointer, onDidSubscribeToRemoteTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
+    let delegate = LKRoomDelegate(data: data, onDidSubscribeToRemoteTrack: onDidSubscribeToRemoteTrack)
+    return Unmanaged.passRetained(delegate).toOpaque()
 }
 
 @_cdecl("LKRoomCreate")
-public func LKRoomCreate() -> UnsafeMutableRawPointer  {
-    Unmanaged.passRetained(Room()).toOpaque()
+public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer  {
+    let delegate = Unmanaged<LKRoomDelegate>.fromOpaque(delegate).takeUnretainedValue()
+    return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque()
 }
 
 @_cdecl("LKRoomConnect")
 public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue();
+    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
 
     room.connect(url as String, token as String).then { _ in
-        callback(callback_data, UnsafeRawPointer(nil) as! CFString?);
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
     }.catch { error in
-        callback(callback_data, error.localizedDescription as CFString);
-    };
+        callback(callback_data, error.localizedDescription as CFString)
+    }
 }
 
 @_cdecl("LKRoomPublishVideoTrack")
 public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
-    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue();
-    let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue();
+    let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
+    let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue()
     room.localParticipant?.publishVideoTrack(track: track).then { _ in
-        callback(callback_data, UnsafeRawPointer(nil) as! CFString?);
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?)
     }.catch { error in
-        callback(callback_data, error.localizedDescription as CFString);
-    };
+        callback(callback_data, error.localizedDescription as CFString)
+    }
 }
 
 @_cdecl("LKCreateScreenShareTrackForWindow")
 public func LKCreateScreenShareTrackForWindow(windowId: uint32) -> UnsafeMutableRawPointer {
-    let track = LocalVideoTrack.createMacOSScreenShareTrack(source: .window(id: windowId));
+    let track = LocalVideoTrack.createMacOSScreenShareTrack(source: .window(id: windowId))
     return Unmanaged.passRetained(track).toOpaque()
 }

crates/live_kit/src/live_kit.rs 🔗

@@ -11,34 +11,56 @@ use core_graphics::window::{
     kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID, CGWindowListCopyWindowInfo,
 };
 use futures::{channel::oneshot, Future};
-use std::ffi::c_void;
+use std::{
+    ffi::c_void,
+    sync::{Arc, Weak},
+};
 
 extern "C" {
     fn LKRelease(object: *const c_void);
 
-    fn LKRoomCreate() -> *const c_void;
+    fn LKRoomDelegateCreate(
+        callback_data: *const c_void,
+        on_did_subscribe_to_remote_track: extern "C" fn(
+            callback_data: *mut c_void,
+            remote_track: *const c_void,
+        ),
+    ) -> *const c_void;
+
+    fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
     fn LKRoomConnect(
         room: *const c_void,
         url: CFStringRef,
         token: CFStringRef,
-        callback: extern "C" fn(*mut c_void, CFStringRef) -> (),
+        callback: extern "C" fn(*mut c_void, CFStringRef),
         callback_data: *mut c_void,
     );
     fn LKRoomPublishVideoTrack(
         room: *const c_void,
         track: *const c_void,
-        callback: extern "C" fn(*mut c_void, CFStringRef) -> (),
+        callback: extern "C" fn(*mut c_void, CFStringRef),
         callback_data: *mut c_void,
     );
 
     fn LKCreateScreenShareTrackForWindow(windowId: u32) -> *const c_void;
 }
 
-pub struct Room(*const c_void);
+pub struct Room {
+    debug_name: &'static str,
+    native_room: *const c_void,
+    _delegate: RoomDelegate,
+}
 
 impl Room {
-    pub fn new() -> Self {
-        Self(unsafe { LKRoomCreate() })
+    pub fn new(debug_name: &'static str) -> Arc<Self> {
+        Arc::new_cyclic(|weak_room| {
+            let delegate = RoomDelegate::new(weak_room.clone());
+            Self {
+                debug_name,
+                native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
+                _delegate: delegate,
+            }
+        })
     }
 
     pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
@@ -47,7 +69,7 @@ impl Room {
         let (did_connect, tx, rx) = Self::build_done_callback();
         unsafe {
             LKRoomConnect(
-                self.0,
+                self.native_room,
                 url.as_concrete_TypeRef(),
                 token.as_concrete_TypeRef(),
                 did_connect,
@@ -61,11 +83,15 @@ impl Room {
     pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
         let (did_publish, tx, rx) = Self::build_done_callback();
         unsafe {
-            LKRoomPublishVideoTrack(self.0, track.0, did_publish, tx);
+            LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
         }
         async { rx.await.unwrap().context("error publishing video track") }
     }
 
+    fn did_subscribe_to_remote_track(&self, track: RemoteVideoTrack) {
+        println!("{}: !!!!!!!!!!!!!!!!!!", self.debug_name);
+    }
+
     fn build_done_callback() -> (
         extern "C" fn(*mut c_void, CFStringRef),
         *mut c_void,
@@ -91,7 +117,46 @@ impl Room {
 
 impl Drop for Room {
     fn drop(&mut self) {
-        unsafe { LKRelease(self.0) }
+        unsafe { LKRelease(self.native_room) }
+    }
+}
+
+struct RoomDelegate {
+    native_delegate: *const c_void,
+    weak_room: *const Room,
+}
+
+impl RoomDelegate {
+    fn new(weak_room: Weak<Room>) -> Self {
+        let weak_room = Weak::into_raw(weak_room);
+        let native_delegate = unsafe {
+            LKRoomDelegateCreate(
+                weak_room as *const c_void,
+                Self::on_did_subscribe_to_remote_track,
+            )
+        };
+        Self {
+            native_delegate,
+            weak_room,
+        }
+    }
+
+    extern "C" fn on_did_subscribe_to_remote_track(room: *mut c_void, track: *const c_void) {
+        let room = unsafe { Weak::from_raw(room as *mut Room) };
+        let track = unsafe { RemoteVideoTrack(track) };
+        if let Some(room) = room.upgrade() {
+            room.did_subscribe_to_remote_track(track);
+        }
+        let _ = Weak::into_raw(room);
+    }
+}
+
+impl Drop for RoomDelegate {
+    fn drop(&mut self) {
+        unsafe {
+            LKRelease(self.native_delegate);
+            let _ = Weak::from_raw(self.weak_room);
+        }
     }
 }
 
@@ -109,6 +174,14 @@ impl Drop for LocalVideoTrack {
     }
 }
 
+pub struct RemoteVideoTrack(*const c_void);
+
+impl Drop for RemoteVideoTrack {
+    fn drop(&mut self) {
+        unsafe { LKRelease(self.0) }
+    }
+}
+
 #[derive(Debug)]
 pub struct WindowInfo {
     pub id: u32,