mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 05:11:44 +03:00
WIP: Start on a fake implementation of live-kit
This commit is contained in:
parent
3160d07b9c
commit
fb5c6493cf
@ -12,6 +12,7 @@ test-support = [
|
|||||||
"client/test-support",
|
"client/test-support",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
|
"live_kit_client/test-support",
|
||||||
"project/test-support",
|
"project/test-support",
|
||||||
"util/test-support"
|
"util/test-support"
|
||||||
]
|
]
|
||||||
@ -33,5 +34,6 @@ postage = { version = "0.4.1", features = ["futures-traits"] }
|
|||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||||
project = { path = "../project", features = ["test-support"] }
|
project = { path = "../project", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
@ -11,6 +11,9 @@ doctest = false
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "test_app"
|
name = "test_app"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
media = { path = "../media" }
|
media = { path = "../media" }
|
||||||
|
|
||||||
|
@ -35,14 +35,16 @@ pub struct SwiftTarget {
|
|||||||
const MACOS_TARGET_VERSION: &str = "10.15";
|
const MACOS_TARGET_VERSION: &str = "10.15";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let swift_target = get_swift_target();
|
if cfg!(not(any(test, feature = "test-support"))) {
|
||||||
|
let swift_target = get_swift_target();
|
||||||
|
|
||||||
build_bridge(&swift_target);
|
build_bridge(&swift_target);
|
||||||
link_swift_stdlib(&swift_target);
|
link_swift_stdlib(&swift_target);
|
||||||
link_webrtc_framework(&swift_target);
|
link_webrtc_framework(&swift_target);
|
||||||
|
|
||||||
// Register exported Objective-C selectors, protocols, etc when building example binaries.
|
// Register exported Objective-C selectors, protocols, etc when building example binaries.
|
||||||
println!("cargo:rustc-link-arg=-Wl,-ObjC");
|
println!("cargo:rustc-link-arg=-Wl,-ObjC");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_bridge(swift_target: &SwiftTarget) {
|
fn build_bridge(swift_target: &SwiftTarget) {
|
||||||
|
@ -1,428 +1,8 @@
|
|||||||
use anyhow::{anyhow, Context, Result};
|
pub mod prod;
|
||||||
use core_foundation::{
|
pub mod test;
|
||||||
array::{CFArray, CFArrayRef},
|
|
||||||
base::{CFRelease, CFRetain, TCFType},
|
|
||||||
string::{CFString, CFStringRef},
|
|
||||||
};
|
|
||||||
use futures::{
|
|
||||||
channel::{mpsc, oneshot},
|
|
||||||
Future,
|
|
||||||
};
|
|
||||||
use media::core_video::{CVImageBuffer, CVImageBufferRef};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::{
|
|
||||||
ffi::c_void,
|
|
||||||
sync::{Arc, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Sid = String;
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
|
pub use prod::*;
|
||||||
|
|
||||||
extern "C" {
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fn LKRoomDelegateCreate(
|
pub use test::*;
|
||||||
callback_data: *mut c_void,
|
|
||||||
on_did_subscribe_to_remote_video_track: extern "C" fn(
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
publisher_id: CFStringRef,
|
|
||||||
track_id: CFStringRef,
|
|
||||||
remote_track: *const c_void,
|
|
||||||
),
|
|
||||||
on_did_unsubscribe_from_remote_video_track: extern "C" fn(
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
publisher_id: CFStringRef,
|
|
||||||
track_id: CFStringRef,
|
|
||||||
),
|
|
||||||
) -> *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_data: *mut c_void,
|
|
||||||
);
|
|
||||||
fn LKRoomDisconnect(room: *const c_void);
|
|
||||||
fn LKRoomPublishVideoTrack(
|
|
||||||
room: *const c_void,
|
|
||||||
track: *const c_void,
|
|
||||||
callback: extern "C" fn(*mut c_void, *mut c_void, CFStringRef),
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
);
|
|
||||||
fn LKRoomUnpublishTrack(room: *const c_void, publication: *const c_void);
|
|
||||||
fn LKRoomVideoTracksForRemoteParticipant(
|
|
||||||
room: *const c_void,
|
|
||||||
participant_id: CFStringRef,
|
|
||||||
) -> CFArrayRef;
|
|
||||||
|
|
||||||
fn LKVideoRendererCreate(
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef),
|
|
||||||
on_drop: extern "C" fn(callback_data: *mut c_void),
|
|
||||||
) -> *const c_void;
|
|
||||||
|
|
||||||
fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
|
|
||||||
fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
|
|
||||||
|
|
||||||
fn LKDisplaySources(
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
callback: extern "C" fn(
|
|
||||||
callback_data: *mut c_void,
|
|
||||||
sources: CFArrayRef,
|
|
||||||
error: CFStringRef,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Room {
|
|
||||||
native_room: *const c_void,
|
|
||||||
remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
|
|
||||||
_delegate: RoomDelegate,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Room {
|
|
||||||
pub fn new() -> Arc<Self> {
|
|
||||||
Arc::new_cyclic(|weak_room| {
|
|
||||||
let delegate = RoomDelegate::new(weak_room.clone());
|
|
||||||
Self {
|
|
||||||
native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
|
|
||||||
remote_video_track_subscribers: Default::default(),
|
|
||||||
_delegate: delegate,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
|
|
||||||
let url = CFString::new(url);
|
|
||||||
let token = CFString::new(token);
|
|
||||||
let (did_connect, tx, rx) = Self::build_done_callback();
|
|
||||||
unsafe {
|
|
||||||
LKRoomConnect(
|
|
||||||
self.native_room,
|
|
||||||
url.as_concrete_TypeRef(),
|
|
||||||
token.as_concrete_TypeRef(),
|
|
||||||
did_connect,
|
|
||||||
tx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async { rx.await.unwrap().context("error connecting to room") }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn publish_video_track(
|
|
||||||
&self,
|
|
||||||
track: &LocalVideoTrack,
|
|
||||||
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
|
||||||
let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
|
|
||||||
extern "C" fn callback(tx: *mut c_void, publication: *mut c_void, error: CFStringRef) {
|
|
||||||
let tx =
|
|
||||||
unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
|
|
||||||
if error.is_null() {
|
|
||||||
let _ = tx.send(Ok(LocalTrackPublication(publication)));
|
|
||||||
} else {
|
|
||||||
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
|
||||||
let _ = tx.send(Err(anyhow!(error)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
LKRoomPublishVideoTrack(
|
|
||||||
self.native_room,
|
|
||||||
track.0,
|
|
||||||
callback,
|
|
||||||
Box::into_raw(Box::new(tx)) as *mut c_void,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async { rx.await.unwrap().context("error publishing video track") }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unpublish_track(&self, publication: LocalTrackPublication) {
|
|
||||||
unsafe {
|
|
||||||
LKRoomUnpublishTrack(self.native_room, publication.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
|
|
||||||
unsafe {
|
|
||||||
let tracks = LKRoomVideoTracksForRemoteParticipant(
|
|
||||||
self.native_room,
|
|
||||||
CFString::new(participant_id).as_concrete_TypeRef(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if tracks.is_null() {
|
|
||||||
Vec::new()
|
|
||||||
} else {
|
|
||||||
let tracks = CFArray::wrap_under_get_rule(tracks);
|
|
||||||
tracks
|
|
||||||
.into_iter()
|
|
||||||
.map(|native_track| {
|
|
||||||
let native_track = *native_track;
|
|
||||||
let id =
|
|
||||||
CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track))
|
|
||||||
.to_string();
|
|
||||||
Arc::new(RemoteVideoTrack::new(
|
|
||||||
native_track,
|
|
||||||
id,
|
|
||||||
participant_id.into(),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
|
|
||||||
let (tx, rx) = mpsc::unbounded();
|
|
||||||
self.remote_video_track_subscribers.lock().push(tx);
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
|
|
||||||
let track = Arc::new(track);
|
|
||||||
self.remote_video_track_subscribers.lock().retain(|tx| {
|
|
||||||
tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
|
|
||||||
.is_ok()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
|
|
||||||
self.remote_video_track_subscribers.lock().retain(|tx| {
|
|
||||||
tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
|
|
||||||
publisher_id: publisher_id.clone(),
|
|
||||||
track_id: track_id.clone(),
|
|
||||||
})
|
|
||||||
.is_ok()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_done_callback() -> (
|
|
||||||
extern "C" fn(*mut c_void, CFStringRef),
|
|
||||||
*mut c_void,
|
|
||||||
oneshot::Receiver<Result<()>>,
|
|
||||||
) {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
|
|
||||||
let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
|
|
||||||
if error.is_null() {
|
|
||||||
let _ = tx.send(Ok(()));
|
|
||||||
} else {
|
|
||||||
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
|
||||||
let _ = tx.send(Err(anyhow!(error)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
done_callback,
|
|
||||||
Box::into_raw(Box::new(tx)) as *mut c_void,
|
|
||||||
rx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Room {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
LKRoomDisconnect(self.native_room);
|
|
||||||
CFRelease(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 *mut c_void,
|
|
||||||
Self::on_did_subscribe_to_remote_video_track,
|
|
||||||
Self::on_did_unsubscribe_from_remote_video_track,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
Self {
|
|
||||||
native_delegate,
|
|
||||||
weak_room,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn on_did_subscribe_to_remote_video_track(
|
|
||||||
room: *mut c_void,
|
|
||||||
publisher_id: CFStringRef,
|
|
||||||
track_id: CFStringRef,
|
|
||||||
track: *const c_void,
|
|
||||||
) {
|
|
||||||
let room = unsafe { Weak::from_raw(room as *mut Room) };
|
|
||||||
let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
|
|
||||||
let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
|
|
||||||
let track = RemoteVideoTrack::new(track, track_id, publisher_id);
|
|
||||||
if let Some(room) = room.upgrade() {
|
|
||||||
room.did_subscribe_to_remote_video_track(track);
|
|
||||||
}
|
|
||||||
let _ = Weak::into_raw(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn on_did_unsubscribe_from_remote_video_track(
|
|
||||||
room: *mut c_void,
|
|
||||||
publisher_id: CFStringRef,
|
|
||||||
track_id: CFStringRef,
|
|
||||||
) {
|
|
||||||
let room = unsafe { Weak::from_raw(room as *mut Room) };
|
|
||||||
let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
|
|
||||||
let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
|
|
||||||
if let Some(room) = room.upgrade() {
|
|
||||||
room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
|
|
||||||
}
|
|
||||||
let _ = Weak::into_raw(room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RoomDelegate {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRelease(self.native_delegate);
|
|
||||||
let _ = Weak::from_raw(self.weak_room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LocalVideoTrack(*const c_void);
|
|
||||||
|
|
||||||
impl LocalVideoTrack {
|
|
||||||
pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
|
|
||||||
Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for LocalVideoTrack {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { CFRelease(self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LocalTrackPublication(*const c_void);
|
|
||||||
|
|
||||||
impl Drop for LocalTrackPublication {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { CFRelease(self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RemoteVideoTrack {
|
|
||||||
native_track: *const c_void,
|
|
||||||
sid: Sid,
|
|
||||||
publisher_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemoteVideoTrack {
|
|
||||||
fn new(native_track: *const c_void, sid: Sid, publisher_id: String) -> Self {
|
|
||||||
unsafe {
|
|
||||||
CFRetain(native_track);
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
native_track,
|
|
||||||
sid,
|
|
||||||
publisher_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sid(&self) -> &str {
|
|
||||||
&self.sid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn publisher_id(&self) -> &str {
|
|
||||||
&self.publisher_id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_renderer<F>(&self, callback: F)
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(CVImageBuffer),
|
|
||||||
{
|
|
||||||
extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
|
|
||||||
where
|
|
||||||
F: FnMut(CVImageBuffer),
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
let buffer = CVImageBuffer::wrap_under_get_rule(frame);
|
|
||||||
let callback = &mut *(callback_data as *mut F);
|
|
||||||
callback(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn on_drop<F>(callback_data: *mut c_void) {
|
|
||||||
unsafe {
|
|
||||||
let _ = Box::from_raw(callback_data as *mut F);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let callback_data = Box::into_raw(Box::new(callback));
|
|
||||||
unsafe {
|
|
||||||
let renderer =
|
|
||||||
LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
|
|
||||||
LKVideoTrackAddRenderer(self.native_track, renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RemoteVideoTrack {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { CFRelease(self.native_track) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum RemoteVideoTrackUpdate {
|
|
||||||
Subscribed(Arc<RemoteVideoTrack>),
|
|
||||||
Unsubscribed { publisher_id: Sid, track_id: Sid },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MacOSDisplay(*const c_void);
|
|
||||||
|
|
||||||
impl MacOSDisplay {
|
|
||||||
fn new(ptr: *const c_void) -> Self {
|
|
||||||
unsafe {
|
|
||||||
CFRetain(ptr);
|
|
||||||
}
|
|
||||||
Self(ptr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for MacOSDisplay {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { CFRelease(self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
|
||||||
extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
|
|
||||||
unsafe {
|
|
||||||
let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
|
|
||||||
|
|
||||||
if sources.is_null() {
|
|
||||||
let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
|
|
||||||
} else {
|
|
||||||
let sources = CFArray::wrap_under_get_rule(sources)
|
|
||||||
.into_iter()
|
|
||||||
.map(|source| MacOSDisplay::new(*source))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let _ = tx.send(Ok(sources));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
async move { rx.await.unwrap() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn test_client() {}
|
|
||||||
}
|
|
||||||
|
422
crates/live_kit_client/src/prod.rs
Normal file
422
crates/live_kit_client/src/prod.rs
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use core_foundation::{
|
||||||
|
array::{CFArray, CFArrayRef},
|
||||||
|
base::{CFRelease, CFRetain, TCFType},
|
||||||
|
string::{CFString, CFStringRef},
|
||||||
|
};
|
||||||
|
use futures::{
|
||||||
|
channel::{mpsc, oneshot},
|
||||||
|
Future,
|
||||||
|
};
|
||||||
|
use media::core_video::{CVImageBuffer, CVImageBufferRef};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::{
|
||||||
|
ffi::c_void,
|
||||||
|
sync::{Arc, Weak},
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn LKRoomDelegateCreate(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
on_did_subscribe_to_remote_video_track: extern "C" fn(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
publisher_id: CFStringRef,
|
||||||
|
track_id: CFStringRef,
|
||||||
|
remote_track: *const c_void,
|
||||||
|
),
|
||||||
|
on_did_unsubscribe_from_remote_video_track: extern "C" fn(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
publisher_id: CFStringRef,
|
||||||
|
track_id: CFStringRef,
|
||||||
|
),
|
||||||
|
) -> *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_data: *mut c_void,
|
||||||
|
);
|
||||||
|
fn LKRoomDisconnect(room: *const c_void);
|
||||||
|
fn LKRoomPublishVideoTrack(
|
||||||
|
room: *const c_void,
|
||||||
|
track: *const c_void,
|
||||||
|
callback: extern "C" fn(*mut c_void, *mut c_void, CFStringRef),
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
);
|
||||||
|
fn LKRoomUnpublishTrack(room: *const c_void, publication: *const c_void);
|
||||||
|
fn LKRoomVideoTracksForRemoteParticipant(
|
||||||
|
room: *const c_void,
|
||||||
|
participant_id: CFStringRef,
|
||||||
|
) -> CFArrayRef;
|
||||||
|
|
||||||
|
fn LKVideoRendererCreate(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef),
|
||||||
|
on_drop: extern "C" fn(callback_data: *mut c_void),
|
||||||
|
) -> *const c_void;
|
||||||
|
|
||||||
|
fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
|
||||||
|
fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
|
||||||
|
|
||||||
|
fn LKDisplaySources(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
callback: extern "C" fn(
|
||||||
|
callback_data: *mut c_void,
|
||||||
|
sources: CFArrayRef,
|
||||||
|
error: CFStringRef,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Sid = String;
|
||||||
|
|
||||||
|
pub struct Room {
|
||||||
|
native_room: *const c_void,
|
||||||
|
remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
|
||||||
|
_delegate: RoomDelegate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Room {
|
||||||
|
pub fn new() -> Arc<Self> {
|
||||||
|
Arc::new_cyclic(|weak_room| {
|
||||||
|
let delegate = RoomDelegate::new(weak_room.clone());
|
||||||
|
Self {
|
||||||
|
native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
|
||||||
|
remote_video_track_subscribers: Default::default(),
|
||||||
|
_delegate: delegate,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
|
||||||
|
let url = CFString::new(url);
|
||||||
|
let token = CFString::new(token);
|
||||||
|
let (did_connect, tx, rx) = Self::build_done_callback();
|
||||||
|
unsafe {
|
||||||
|
LKRoomConnect(
|
||||||
|
self.native_room,
|
||||||
|
url.as_concrete_TypeRef(),
|
||||||
|
token.as_concrete_TypeRef(),
|
||||||
|
did_connect,
|
||||||
|
tx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async { rx.await.unwrap().context("error connecting to room") }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publish_video_track(
|
||||||
|
&self,
|
||||||
|
track: &LocalVideoTrack,
|
||||||
|
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
||||||
|
let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
|
||||||
|
extern "C" fn callback(tx: *mut c_void, publication: *mut c_void, error: CFStringRef) {
|
||||||
|
let tx =
|
||||||
|
unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
|
||||||
|
if error.is_null() {
|
||||||
|
let _ = tx.send(Ok(LocalTrackPublication(publication)));
|
||||||
|
} else {
|
||||||
|
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
||||||
|
let _ = tx.send(Err(anyhow!(error)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
LKRoomPublishVideoTrack(
|
||||||
|
self.native_room,
|
||||||
|
track.0,
|
||||||
|
callback,
|
||||||
|
Box::into_raw(Box::new(tx)) as *mut c_void,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
async { rx.await.unwrap().context("error publishing video track") }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpublish_track(&self, publication: LocalTrackPublication) {
|
||||||
|
unsafe {
|
||||||
|
LKRoomUnpublishTrack(self.native_room, publication.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
|
||||||
|
unsafe {
|
||||||
|
let tracks = LKRoomVideoTracksForRemoteParticipant(
|
||||||
|
self.native_room,
|
||||||
|
CFString::new(participant_id).as_concrete_TypeRef(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if tracks.is_null() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
let tracks = CFArray::wrap_under_get_rule(tracks);
|
||||||
|
tracks
|
||||||
|
.into_iter()
|
||||||
|
.map(|native_track| {
|
||||||
|
let native_track = *native_track;
|
||||||
|
let id =
|
||||||
|
CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track))
|
||||||
|
.to_string();
|
||||||
|
Arc::new(RemoteVideoTrack::new(
|
||||||
|
native_track,
|
||||||
|
id,
|
||||||
|
participant_id.into(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
|
||||||
|
let (tx, rx) = mpsc::unbounded();
|
||||||
|
self.remote_video_track_subscribers.lock().push(tx);
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
|
||||||
|
let track = Arc::new(track);
|
||||||
|
self.remote_video_track_subscribers.lock().retain(|tx| {
|
||||||
|
tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone()))
|
||||||
|
.is_ok()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) {
|
||||||
|
self.remote_video_track_subscribers.lock().retain(|tx| {
|
||||||
|
tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed {
|
||||||
|
publisher_id: publisher_id.clone(),
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
})
|
||||||
|
.is_ok()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_done_callback() -> (
|
||||||
|
extern "C" fn(*mut c_void, CFStringRef),
|
||||||
|
*mut c_void,
|
||||||
|
oneshot::Receiver<Result<()>>,
|
||||||
|
) {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
|
||||||
|
let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
|
||||||
|
if error.is_null() {
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
} else {
|
||||||
|
let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
|
||||||
|
let _ = tx.send(Err(anyhow!(error)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
done_callback,
|
||||||
|
Box::into_raw(Box::new(tx)) as *mut c_void,
|
||||||
|
rx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Room {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
LKRoomDisconnect(self.native_room);
|
||||||
|
CFRelease(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 *mut c_void,
|
||||||
|
Self::on_did_subscribe_to_remote_video_track,
|
||||||
|
Self::on_did_unsubscribe_from_remote_video_track,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
native_delegate,
|
||||||
|
weak_room,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_did_subscribe_to_remote_video_track(
|
||||||
|
room: *mut c_void,
|
||||||
|
publisher_id: CFStringRef,
|
||||||
|
track_id: CFStringRef,
|
||||||
|
track: *const c_void,
|
||||||
|
) {
|
||||||
|
let room = unsafe { Weak::from_raw(room as *mut Room) };
|
||||||
|
let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
|
||||||
|
let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
|
||||||
|
let track = RemoteVideoTrack::new(track, track_id, publisher_id);
|
||||||
|
if let Some(room) = room.upgrade() {
|
||||||
|
room.did_subscribe_to_remote_video_track(track);
|
||||||
|
}
|
||||||
|
let _ = Weak::into_raw(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_did_unsubscribe_from_remote_video_track(
|
||||||
|
room: *mut c_void,
|
||||||
|
publisher_id: CFStringRef,
|
||||||
|
track_id: CFStringRef,
|
||||||
|
) {
|
||||||
|
let room = unsafe { Weak::from_raw(room as *mut Room) };
|
||||||
|
let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() };
|
||||||
|
let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() };
|
||||||
|
if let Some(room) = room.upgrade() {
|
||||||
|
room.did_unsubscribe_from_remote_video_track(publisher_id, track_id);
|
||||||
|
}
|
||||||
|
let _ = Weak::into_raw(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RoomDelegate {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
CFRelease(self.native_delegate);
|
||||||
|
let _ = Weak::from_raw(self.weak_room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalVideoTrack(*const c_void);
|
||||||
|
|
||||||
|
impl LocalVideoTrack {
|
||||||
|
pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
|
||||||
|
Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LocalVideoTrack {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFRelease(self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalTrackPublication(*const c_void);
|
||||||
|
|
||||||
|
impl Drop for LocalTrackPublication {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFRelease(self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RemoteVideoTrack {
|
||||||
|
native_track: *const c_void,
|
||||||
|
sid: Sid,
|
||||||
|
publisher_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteVideoTrack {
|
||||||
|
fn new(native_track: *const c_void, sid: Sid, publisher_id: String) -> Self {
|
||||||
|
unsafe {
|
||||||
|
CFRetain(native_track);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
native_track,
|
||||||
|
sid,
|
||||||
|
publisher_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sid(&self) -> &str {
|
||||||
|
&self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publisher_id(&self) -> &str {
|
||||||
|
&self.publisher_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_renderer<F>(&self, callback: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(CVImageBuffer),
|
||||||
|
{
|
||||||
|
extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
|
||||||
|
where
|
||||||
|
F: FnMut(CVImageBuffer),
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
let buffer = CVImageBuffer::wrap_under_get_rule(frame);
|
||||||
|
let callback = &mut *(callback_data as *mut F);
|
||||||
|
callback(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_drop<F>(callback_data: *mut c_void) {
|
||||||
|
unsafe {
|
||||||
|
let _ = Box::from_raw(callback_data as *mut F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback_data = Box::into_raw(Box::new(callback));
|
||||||
|
unsafe {
|
||||||
|
let renderer =
|
||||||
|
LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
|
||||||
|
LKVideoTrackAddRenderer(self.native_track, renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RemoteVideoTrack {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFRelease(self.native_track) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RemoteVideoTrackUpdate {
|
||||||
|
Subscribed(Arc<RemoteVideoTrack>),
|
||||||
|
Unsubscribed { publisher_id: Sid, track_id: Sid },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MacOSDisplay(*const c_void);
|
||||||
|
|
||||||
|
impl MacOSDisplay {
|
||||||
|
fn new(ptr: *const c_void) -> Self {
|
||||||
|
unsafe {
|
||||||
|
CFRetain(ptr);
|
||||||
|
}
|
||||||
|
Self(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MacOSDisplay {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { CFRelease(self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
||||||
|
extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
|
||||||
|
unsafe {
|
||||||
|
let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
|
||||||
|
|
||||||
|
if sources.is_null() {
|
||||||
|
let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
|
||||||
|
} else {
|
||||||
|
let sources = CFArray::wrap_under_get_rule(sources)
|
||||||
|
.into_iter()
|
||||||
|
.map(|source| MacOSDisplay::new(*source))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let _ = tx.send(Ok(sources));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
async move { rx.await.unwrap() }
|
||||||
|
}
|
77
crates/live_kit_client/src/test.rs
Normal file
77
crates/live_kit_client/src/test.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use futures::{channel::mpsc, future};
|
||||||
|
use media::core_video::CVImageBuffer;
|
||||||
|
use std::{future::Future, sync::Arc};
|
||||||
|
|
||||||
|
pub type Sid = String;
|
||||||
|
|
||||||
|
pub struct Room;
|
||||||
|
|
||||||
|
impl Room {
|
||||||
|
pub fn new() -> Arc<Self> {
|
||||||
|
Arc::new(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
|
||||||
|
future::pending()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publish_video_track(
|
||||||
|
&self,
|
||||||
|
track: &LocalVideoTrack,
|
||||||
|
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
||||||
|
future::pending()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpublish_track(&self, publication: LocalTrackPublication) {}
|
||||||
|
|
||||||
|
pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
|
||||||
|
mpsc::unbounded().1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalTrackPublication;
|
||||||
|
|
||||||
|
pub struct LocalVideoTrack;
|
||||||
|
|
||||||
|
impl LocalVideoTrack {
|
||||||
|
pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RemoteVideoTrack {
|
||||||
|
sid: Sid,
|
||||||
|
publisher_id: Sid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteVideoTrack {
|
||||||
|
pub fn sid(&self) -> &str {
|
||||||
|
&self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publisher_id(&self) -> &str {
|
||||||
|
&self.publisher_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_renderer<F>(&self, callback: F)
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(CVImageBuffer),
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RemoteVideoTrackUpdate {
|
||||||
|
Subscribed(Arc<RemoteVideoTrack>),
|
||||||
|
Unsubscribed { publisher_id: Sid, track_id: Sid },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MacOSDisplay;
|
||||||
|
|
||||||
|
pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
||||||
|
future::pending()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user