Use fake LiveKit server to test we can send frames when screen sharing
This commit is contained in:
parent
b6e5aa3bb0
commit
723fa83909
9 changed files with 334 additions and 188 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -181,6 +181,17 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-broadcast"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"futures-core",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
|
@ -2991,7 +3002,7 @@ name = "language"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-broadcast",
|
"async-broadcast 0.3.4",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
|
@ -3164,6 +3175,7 @@ name = "live_kit_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-broadcast 0.4.1",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"block",
|
"block",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -3181,6 +3193,7 @@ dependencies = [
|
||||||
"live_kit_server",
|
"live_kit_server",
|
||||||
"log",
|
"log",
|
||||||
"media",
|
"media",
|
||||||
|
"nanoid",
|
||||||
"objc",
|
"objc",
|
||||||
"parking_lot 0.11.2",
|
"parking_lot 0.11.2",
|
||||||
"postage",
|
"postage",
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{anyhow, Result};
|
||||||
use client::{proto, User};
|
use client::{proto, User};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{Task, WeakModelHandle};
|
use gpui::{Task, WeakModelHandle};
|
||||||
use media::core_video::CVImageBuffer;
|
use live_kit_client::Frame;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -46,13 +46,13 @@ pub struct RemoteParticipant {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RemoteVideoTrack {
|
pub struct RemoteVideoTrack {
|
||||||
pub(crate) frame: Option<CVImageBuffer>,
|
pub(crate) frame: Option<Frame>,
|
||||||
pub(crate) _live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
pub(crate) _live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
||||||
pub(crate) _maintain_frame: Arc<Task<()>>,
|
pub(crate) _maintain_frame: Arc<Task<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteVideoTrack {
|
impl RemoteVideoTrack {
|
||||||
pub fn frame(&self) -> Option<&CVImageBuffer> {
|
pub fn frame(&self) -> Option<&Frame> {
|
||||||
self.frame.as_ref()
|
self.frame.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,31 +418,33 @@ impl Room {
|
||||||
_live_kit_track: track,
|
_live_kit_track: track,
|
||||||
_maintain_frame: Arc::new(cx.spawn_weak(|this, mut cx| async move {
|
_maintain_frame: Arc::new(cx.spawn_weak(|this, mut cx| async move {
|
||||||
while let Some(frame) = rx.next().await {
|
while let Some(frame) = rx.next().await {
|
||||||
let this = if let Some(this) = this.upgrade(&cx) {
|
if let Some(frame) = frame {
|
||||||
this
|
let this = if let Some(this) = this.upgrade(&cx) {
|
||||||
} else {
|
this
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
let done = this.update(&mut cx, |this, cx| {
|
|
||||||
if let Some(track) =
|
|
||||||
this.remote_participants.get_mut(&peer_id).and_then(
|
|
||||||
|participant| participant.tracks.get_mut(&track_id),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
track.frame = frame;
|
|
||||||
cx.emit(Event::Frame {
|
|
||||||
participant_id: peer_id,
|
|
||||||
track_id: track_id.clone(),
|
|
||||||
});
|
|
||||||
false
|
|
||||||
} else {
|
} else {
|
||||||
true
|
break;
|
||||||
}
|
};
|
||||||
});
|
|
||||||
|
|
||||||
if done {
|
let done = this.update(&mut cx, |this, cx| {
|
||||||
break;
|
if let Some(track) =
|
||||||
|
this.remote_participants.get_mut(&peer_id).and_then(
|
||||||
|
|participant| participant.tracks.get_mut(&track_id),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
track.frame = Some(frame);
|
||||||
|
cx.emit(Event::Frame {
|
||||||
|
participant_id: peer_id,
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
});
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if done {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
@ -620,18 +622,18 @@ impl Room {
|
||||||
return Task::ready(Err(anyhow!("screen was already shared")));
|
return Task::ready(Err(anyhow!("screen was already shared")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
|
let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||||
live_kit.screen_track = ScreenTrack::Pending { publish_id };
|
live_kit.screen_track = ScreenTrack::Pending { publish_id };
|
||||||
cx.notify();
|
cx.notify();
|
||||||
publish_id
|
(live_kit.room.display_sources(), publish_id)
|
||||||
} else {
|
} else {
|
||||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let publish_track = async {
|
let publish_track = async {
|
||||||
let displays = live_kit_client::display_sources().await?;
|
let displays = displays.await?;
|
||||||
let display = displays
|
let display = displays
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("no display found"))?;
|
.ok_or_else(|| anyhow!("no display found"))?;
|
||||||
|
@ -711,6 +713,15 @@ impl Room {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
pub fn set_display_sources(&self, sources: Vec<live_kit_client::MacOSDisplay>) {
|
||||||
|
self.live_kit
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.room
|
||||||
|
.set_display_sources(sources);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LiveKitRoom {
|
struct LiveKitRoom {
|
||||||
|
|
|
@ -5,7 +5,10 @@ use crate::{
|
||||||
};
|
};
|
||||||
use ::rpc::Peer;
|
use ::rpc::Peer;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
use call::{
|
||||||
|
room::{self, Event},
|
||||||
|
ActiveCall, ParticipantLocation, Room,
|
||||||
|
};
|
||||||
use client::{
|
use client::{
|
||||||
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
|
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
|
||||||
Credentials, EstablishConnectionError, PeerId, User, UserStore, RECEIVE_TIMEOUT,
|
Credentials, EstablishConnectionError, PeerId, User, UserStore, RECEIVE_TIMEOUT,
|
||||||
|
@ -30,6 +33,7 @@ use language::{
|
||||||
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||||
LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, Rope,
|
LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, Rope,
|
||||||
};
|
};
|
||||||
|
use live_kit_client::{Frame, MacOSDisplay};
|
||||||
use lsp::{self, FakeLanguageServer};
|
use lsp::{self, FakeLanguageServer};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::{
|
use project::{
|
||||||
|
@ -185,6 +189,45 @@ async fn test_basic_calls(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// User A shares their screen
|
||||||
|
let display = MacOSDisplay::new();
|
||||||
|
let events_b = active_call_events(cx_b);
|
||||||
|
active_call_a
|
||||||
|
.update(cx_a, |call, cx| {
|
||||||
|
call.room().unwrap().update(cx, |room, cx| {
|
||||||
|
room.set_display_sources(vec![display.clone()]);
|
||||||
|
room.share_screen(cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let frame = Frame {
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
label: "a".into(),
|
||||||
|
};
|
||||||
|
display.send_frame(frame.clone());
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
assert_eq!(events_b.borrow().len(), 1);
|
||||||
|
let event = events_b.borrow().first().unwrap().clone();
|
||||||
|
if let Event::Frame {
|
||||||
|
participant_id,
|
||||||
|
track_id,
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
assert_eq!(participant_id, client_a.peer_id().unwrap());
|
||||||
|
room_b.read_with(cx_b, |room, _| {
|
||||||
|
assert_eq!(
|
||||||
|
room.remote_participants()[&client_a.peer_id().unwrap()].tracks[&track_id].frame(),
|
||||||
|
Some(&frame)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
panic!("unexpected event")
|
||||||
|
}
|
||||||
|
|
||||||
// User A leaves the room.
|
// User A leaves the room.
|
||||||
active_call_a.update(cx_a, |call, cx| {
|
active_call_a.update(cx_a, |call, cx| {
|
||||||
call.hang_up(cx).unwrap();
|
call.hang_up(cx).unwrap();
|
||||||
|
@ -954,21 +997,21 @@ async fn test_active_call_events(
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
|
assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
|
||||||
assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
|
assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
|
fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
|
||||||
let events = Rc::new(RefCell::new(Vec::new()));
|
let events = Rc::new(RefCell::new(Vec::new()));
|
||||||
let active_call = cx.read(ActiveCall::global);
|
let active_call = cx.read(ActiveCall::global);
|
||||||
cx.update({
|
cx.update({
|
||||||
let events = events.clone();
|
let events = events.clone();
|
||||||
|cx| {
|
|cx| {
|
||||||
cx.subscribe(&active_call, move |_, event, _| {
|
cx.subscribe(&active_call, move |_, event, _| {
|
||||||
events.borrow_mut().push(event.clone())
|
events.borrow_mut().push(event.clone())
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
events
|
events
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
|
|
|
@ -13,11 +13,13 @@ name = "test_app"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = [
|
test-support = [
|
||||||
|
"async-broadcast",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"collections/test-support",
|
"collections/test-support",
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"live_kit_server"
|
"live_kit_server",
|
||||||
|
"nanoid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -27,13 +29,16 @@ live_kit_server = { path = "../live_kit_server", optional = true }
|
||||||
media = { path = "../media" }
|
media = { path = "../media" }
|
||||||
|
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-trait = { version = "0.1", optional = true }
|
|
||||||
core-foundation = "0.9.3"
|
core-foundation = "0.9.3"
|
||||||
core-graphics = "0.22.3"
|
core-graphics = "0.22.3"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lazy_static = { version = "1.4", optional = true }
|
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
|
||||||
|
async-broadcast = { version = "0.4", optional = true }
|
||||||
|
async-trait = { version = "0.1", optional = true }
|
||||||
|
lazy_static = { version = "1.4", optional = true }
|
||||||
|
nanoid = { version ="0.4", optional = true}
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{actions, keymap::Binding, Menu, MenuItem};
|
||||||
actions,
|
|
||||||
elements::{Canvas, *},
|
|
||||||
keymap::Binding,
|
|
||||||
platform::current::Surface,
|
|
||||||
Menu, MenuItem, ViewContext,
|
|
||||||
};
|
|
||||||
use live_kit_client::{LocalVideoTrack, RemoteVideoTrackUpdate, Room};
|
use live_kit_client::{LocalVideoTrack, RemoteVideoTrackUpdate, Room};
|
||||||
use live_kit_server::{
|
use live_kit_server::token::{self, VideoGrant};
|
||||||
api::Client,
|
|
||||||
token::{self, VideoGrant},
|
|
||||||
};
|
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use media::core_video::CVImageBuffer;
|
|
||||||
use postage::watch;
|
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
actions!(capture, [Quit]);
|
actions!(capture, [Quit]);
|
||||||
|
|
||||||
|
@ -62,7 +50,7 @@ fn main() {
|
||||||
|
|
||||||
let mut track_changes = room_b.remote_video_track_updates();
|
let mut track_changes = room_b.remote_video_track_updates();
|
||||||
|
|
||||||
let displays = live_kit_client::display_sources().await.unwrap();
|
let displays = room_a.display_sources().await.unwrap();
|
||||||
let display = displays.into_iter().next().unwrap();
|
let display = displays.into_iter().next().unwrap();
|
||||||
|
|
||||||
let track_a = LocalVideoTrack::screen_share_for_display(&display);
|
let track_a = LocalVideoTrack::screen_share_for_display(&display);
|
||||||
|
@ -72,6 +60,7 @@ fn main() {
|
||||||
let remote_tracks = room_b.remote_video_tracks("test-participant-1");
|
let remote_tracks = room_b.remote_video_tracks("test-participant-1");
|
||||||
assert_eq!(remote_tracks.len(), 1);
|
assert_eq!(remote_tracks.len(), 1);
|
||||||
assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1");
|
assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1");
|
||||||
|
dbg!(track.sid());
|
||||||
assert_eq!(track.publisher_id(), "test-participant-1");
|
assert_eq!(track.publisher_id(), "test-participant-1");
|
||||||
} else {
|
} else {
|
||||||
panic!("unexpected message");
|
panic!("unexpected message");
|
||||||
|
@ -100,73 +89,6 @@ fn main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScreenCaptureView {
|
|
||||||
image_buffer: Option<CVImageBuffer>,
|
|
||||||
_room: Arc<Room>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl gpui::Entity for ScreenCaptureView {
|
|
||||||
type Event = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScreenCaptureView {
|
|
||||||
pub fn new(room: Arc<Room>, cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
let mut remote_video_tracks = room.remote_video_track_updates();
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
|
||||||
if let Some(video_track) = remote_video_tracks.next().await {
|
|
||||||
let (mut frames_tx, mut frames_rx) = watch::channel_with(None);
|
|
||||||
// video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame));
|
|
||||||
|
|
||||||
while let Some(frame) = frames_rx.next().await {
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.image_buffer = frame;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
image_buffer: None,
|
|
||||||
_room: room,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl gpui::View for ScreenCaptureView {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"View"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
|
|
||||||
let image_buffer = self.image_buffer.clone();
|
|
||||||
let canvas = Canvas::new(move |bounds, _, cx| {
|
|
||||||
if let Some(image_buffer) = image_buffer.clone() {
|
|
||||||
cx.scene.push_surface(Surface {
|
|
||||||
bounds,
|
|
||||||
image_buffer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(image_buffer) = self.image_buffer.as_ref() {
|
|
||||||
canvas
|
|
||||||
.constrained()
|
|
||||||
.with_width(image_buffer.width() as f32)
|
|
||||||
.with_height(image_buffer.height() as f32)
|
|
||||||
.aligned()
|
|
||||||
.boxed()
|
|
||||||
} else {
|
|
||||||
canvas.boxed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
|
fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
|
||||||
cx.platform().quit();
|
cx.platform().quit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
Future,
|
Future,
|
||||||
};
|
};
|
||||||
use media::core_video::{CVImageBuffer, CVImageBufferRef};
|
pub use media::core_video::CVImageBuffer;
|
||||||
|
use media::core_video::CVImageBufferRef;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{
|
use std::{
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
|
@ -109,8 +110,35 @@ impl Room {
|
||||||
async { rx.await.unwrap().context("error connecting to room") }
|
async { rx.await.unwrap().context("error connecting to room") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_sources(self: &Arc<Self>) -> 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() }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn publish_video_track(
|
pub fn publish_video_track(
|
||||||
&self,
|
self: &Arc<Self>,
|
||||||
track: &LocalVideoTrack,
|
track: &LocalVideoTrack,
|
||||||
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
||||||
let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
|
let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
|
||||||
|
@ -338,16 +366,16 @@ impl RemoteVideoTrack {
|
||||||
|
|
||||||
pub fn add_renderer<F>(&self, callback: F)
|
pub fn add_renderer<F>(&self, callback: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(CVImageBuffer),
|
F: 'static + Send + Sync + FnMut(Frame),
|
||||||
{
|
{
|
||||||
extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
|
extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
|
||||||
where
|
where
|
||||||
F: FnMut(CVImageBuffer),
|
F: FnMut(Frame),
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let buffer = CVImageBuffer::wrap_under_get_rule(frame);
|
let buffer = CVImageBuffer::wrap_under_get_rule(frame);
|
||||||
let callback = &mut *(callback_data as *mut F);
|
let callback = &mut *(callback_data as *mut F);
|
||||||
callback(buffer);
|
callback(Frame(buffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,29 +422,18 @@ impl Drop for MacOSDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
pub struct Frame(CVImageBuffer);
|
||||||
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() {
|
impl Frame {
|
||||||
let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
|
pub fn width(&self) -> usize {
|
||||||
} else {
|
self.0.width()
|
||||||
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();
|
pub fn height(&self) -> usize {
|
||||||
|
self.0.height()
|
||||||
unsafe {
|
|
||||||
LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async move { rx.await.unwrap() }
|
pub fn image(&self) -> CVImageBuffer {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{channel::mpsc, future};
|
use futures::{Stream, StreamExt};
|
||||||
use gpui::executor::Background;
|
use gpui::executor::{self, Background};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use live_kit_server::token;
|
use live_kit_server::token;
|
||||||
use media::core_video::CVImageBuffer;
|
use media::core_video::CVImageBuffer;
|
||||||
|
@ -96,14 +96,14 @@ impl TestServer {
|
||||||
let room = server_rooms
|
let room = server_rooms
|
||||||
.get_mut(&*room_name)
|
.get_mut(&*room_name)
|
||||||
.ok_or_else(|| anyhow!("room {:?} does not exist", room_name))?;
|
.ok_or_else(|| anyhow!("room {:?} does not exist", room_name))?;
|
||||||
if room.clients.contains_key(&identity) {
|
if room.client_rooms.contains_key(&identity) {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"{:?} attempted to join room {:?} twice",
|
"{:?} attempted to join room {:?} twice",
|
||||||
identity,
|
identity,
|
||||||
room_name
|
room_name
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
room.clients.insert(identity, client_room);
|
room.client_rooms.insert(identity, client_room);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ impl TestServer {
|
||||||
let room = server_rooms
|
let room = server_rooms
|
||||||
.get_mut(&*room_name)
|
.get_mut(&*room_name)
|
||||||
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
|
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
|
||||||
room.clients.remove(&identity).ok_or_else(|| {
|
room.client_rooms.remove(&identity).ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"{:?} attempted to leave room {:?} before joining it",
|
"{:?} attempted to leave room {:?} before joining it",
|
||||||
identity,
|
identity,
|
||||||
|
@ -135,7 +135,7 @@ impl TestServer {
|
||||||
let room = server_rooms
|
let room = server_rooms
|
||||||
.get_mut(&room_name)
|
.get_mut(&room_name)
|
||||||
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
|
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
|
||||||
room.clients.remove(&identity).ok_or_else(|| {
|
room.client_rooms.remove(&identity).ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"participant {:?} did not join room {:?}",
|
"participant {:?} did not join room {:?}",
|
||||||
identity,
|
identity,
|
||||||
|
@ -144,11 +144,44 @@ impl TestServer {
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> {
|
||||||
|
self.background.simulate_random_delay().await;
|
||||||
|
let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
|
||||||
|
let identity = claims.sub.unwrap().to_string();
|
||||||
|
let room_name = claims.video.room.unwrap();
|
||||||
|
|
||||||
|
let mut server_rooms = self.rooms.lock();
|
||||||
|
let room = server_rooms
|
||||||
|
.get_mut(&*room_name)
|
||||||
|
.ok_or_else(|| anyhow!("room {} does not exist", room_name))?;
|
||||||
|
|
||||||
|
let update = RemoteVideoTrackUpdate::Subscribed(Arc::new(RemoteVideoTrack {
|
||||||
|
sid: nanoid::nanoid!(17),
|
||||||
|
publisher_id: identity.clone(),
|
||||||
|
frames_rx: local_track.frames_rx.clone(),
|
||||||
|
background: self.background.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (id, client_room) in &room.client_rooms {
|
||||||
|
if *id != identity {
|
||||||
|
let _ = client_room
|
||||||
|
.0
|
||||||
|
.lock()
|
||||||
|
.video_track_updates
|
||||||
|
.0
|
||||||
|
.try_broadcast(update.clone())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestServerRoom {
|
struct TestServerRoom {
|
||||||
clients: HashMap<Sid, Arc<Room>>,
|
client_rooms: HashMap<Sid, Arc<Room>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestServerRoom {}
|
impl TestServerRoom {}
|
||||||
|
@ -194,17 +227,29 @@ impl live_kit_server::api::Client for TestApiClient {
|
||||||
|
|
||||||
pub type Sid = String;
|
pub type Sid = String;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct RoomState {
|
struct RoomState {
|
||||||
token: Option<String>,
|
connection: Option<ConnectionState>,
|
||||||
|
display_sources: Vec<MacOSDisplay>,
|
||||||
|
video_track_updates: (
|
||||||
|
async_broadcast::Sender<RemoteVideoTrackUpdate>,
|
||||||
|
async_broadcast::Receiver<RemoteVideoTrackUpdate>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConnectionState {
|
||||||
|
url: String,
|
||||||
|
token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Room(Mutex<RoomState>);
|
pub struct Room(Mutex<RoomState>);
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
pub fn new() -> Arc<Self> {
|
pub fn new() -> Arc<Self> {
|
||||||
Default::default()
|
Arc::new(Self(Mutex::new(RoomState {
|
||||||
|
connection: None,
|
||||||
|
display_sources: Default::default(),
|
||||||
|
video_track_updates: async_broadcast::broadcast(128),
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
|
pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
|
||||||
|
@ -214,36 +259,75 @@ impl Room {
|
||||||
async move {
|
async move {
|
||||||
let server = TestServer::get(&url)?;
|
let server = TestServer::get(&url)?;
|
||||||
server.join_room(token.clone(), this.clone()).await?;
|
server.join_room(token.clone(), this.clone()).await?;
|
||||||
this.0.lock().token = Some(token);
|
this.0.lock().connection = Some(ConnectionState { url, token });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn publish_video_track(
|
pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
||||||
&self,
|
let this = self.clone();
|
||||||
track: &LocalVideoTrack,
|
async move {
|
||||||
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
let server = this.test_server();
|
||||||
future::pending()
|
server.background.simulate_random_delay().await;
|
||||||
|
Ok(this.0.lock().display_sources.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unpublish_track(&self, publication: LocalTrackPublication) {}
|
pub fn publish_video_track(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
track: &LocalVideoTrack,
|
||||||
|
) -> impl Future<Output = Result<LocalTrackPublication>> {
|
||||||
|
let this = self.clone();
|
||||||
|
let track = track.clone();
|
||||||
|
async move {
|
||||||
|
this.test_server()
|
||||||
|
.publish_video_track(this.token(), track)
|
||||||
|
.await?;
|
||||||
|
Ok(LocalTrackPublication)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remote_video_tracks(&self, participant_id: &str) -> Vec<Arc<RemoteVideoTrack>> {
|
pub fn unpublish_track(&self, _: LocalTrackPublication) {}
|
||||||
|
|
||||||
|
pub fn remote_video_tracks(&self, _: &str) -> Vec<Arc<RemoteVideoTrack>> {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
|
pub fn remote_video_track_updates(&self) -> impl Stream<Item = RemoteVideoTrackUpdate> {
|
||||||
mpsc::unbounded().1
|
self.0.lock().video_track_updates.1.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_display_sources(&self, sources: Vec<MacOSDisplay>) {
|
||||||
|
self.0.lock().display_sources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_server(&self) -> Arc<TestServer> {
|
||||||
|
let this = self.0.lock();
|
||||||
|
let connection = this
|
||||||
|
.connection
|
||||||
|
.as_ref()
|
||||||
|
.expect("must be connected to call this method");
|
||||||
|
TestServer::get(&connection.url).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token(&self) -> String {
|
||||||
|
self.0
|
||||||
|
.lock()
|
||||||
|
.connection
|
||||||
|
.as_ref()
|
||||||
|
.expect("must be connected to call this method")
|
||||||
|
.token
|
||||||
|
.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Room {
|
impl Drop for Room {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(token) = self.0.lock().token.take() {
|
if let Some(connection) = self.0.lock().connection.take() {
|
||||||
if let Ok(server) = TestServer::get(&token) {
|
if let Ok(server) = TestServer::get(&connection.token) {
|
||||||
let background = server.background.clone();
|
let background = server.background.clone();
|
||||||
background
|
background
|
||||||
.spawn(async move { server.leave_room(token).await.unwrap() })
|
.spawn(async move { server.leave_room(connection.token).await.unwrap() })
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,17 +336,24 @@ impl Drop for Room {
|
||||||
|
|
||||||
pub struct LocalTrackPublication;
|
pub struct LocalTrackPublication;
|
||||||
|
|
||||||
pub struct LocalVideoTrack;
|
#[derive(Clone)]
|
||||||
|
pub struct LocalVideoTrack {
|
||||||
|
frames_rx: async_broadcast::Receiver<Frame>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LocalVideoTrack {
|
impl LocalVideoTrack {
|
||||||
pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
|
pub fn screen_share_for_display(display: &MacOSDisplay) -> Self {
|
||||||
Self
|
Self {
|
||||||
|
frames_rx: display.frames.1.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RemoteVideoTrack {
|
pub struct RemoteVideoTrack {
|
||||||
sid: Sid,
|
sid: Sid,
|
||||||
publisher_id: Sid,
|
publisher_id: Sid,
|
||||||
|
frames_rx: async_broadcast::Receiver<Frame>,
|
||||||
|
background: Arc<executor::Background>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteVideoTrack {
|
impl RemoteVideoTrack {
|
||||||
|
@ -274,20 +365,64 @@ impl RemoteVideoTrack {
|
||||||
&self.publisher_id
|
&self.publisher_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_renderer<F>(&self, callback: F)
|
pub fn add_renderer<F>(&self, mut callback: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(CVImageBuffer),
|
F: 'static + Send + Sync + FnMut(Frame),
|
||||||
{
|
{
|
||||||
|
let mut frames_rx = self.frames_rx.clone();
|
||||||
|
self.background
|
||||||
|
.spawn(async move {
|
||||||
|
while let Some(frame) = frames_rx.next().await {
|
||||||
|
callback(frame)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum RemoteVideoTrackUpdate {
|
pub enum RemoteVideoTrackUpdate {
|
||||||
Subscribed(Arc<RemoteVideoTrack>),
|
Subscribed(Arc<RemoteVideoTrack>),
|
||||||
Unsubscribed { publisher_id: Sid, track_id: Sid },
|
Unsubscribed { publisher_id: Sid, track_id: Sid },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MacOSDisplay;
|
#[derive(Clone)]
|
||||||
|
pub struct MacOSDisplay {
|
||||||
pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
frames: (
|
||||||
future::pending()
|
async_broadcast::Sender<Frame>,
|
||||||
|
async_broadcast::Receiver<Frame>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacOSDisplay {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
frames: async_broadcast::broadcast(128),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_frame(&self, frame: Frame) {
|
||||||
|
self.frames.0.try_broadcast(frame).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Frame {
|
||||||
|
pub label: String,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image(&self) -> CVImageBuffer {
|
||||||
|
unimplemented!("you can't call this in test mode")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,7 @@ impl Member {
|
||||||
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
|
let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
|
||||||
cx.scene.push_surface(gpui::mac::Surface {
|
cx.scene.push_surface(gpui::mac::Surface {
|
||||||
bounds: RectF::new(origin, size),
|
bounds: RectF::new(origin, size),
|
||||||
image_buffer: frame,
|
image_buffer: frame.image(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue