WIP
This commit is contained in:
parent
81d83841ab
commit
499b8f5f55
13 changed files with 245 additions and 263 deletions
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -732,6 +732,7 @@ dependencies = [
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"gpui",
|
"gpui",
|
||||||
"live_kit_client",
|
"live_kit_client",
|
||||||
|
"media",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"util",
|
"util",
|
||||||
|
@ -803,31 +804,6 @@ dependencies = [
|
||||||
"winx",
|
"winx",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "capture"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bindgen",
|
|
||||||
"block",
|
|
||||||
"byteorder",
|
|
||||||
"bytes 1.2.1",
|
|
||||||
"cocoa",
|
|
||||||
"core-foundation",
|
|
||||||
"core-graphics",
|
|
||||||
"foreign-types",
|
|
||||||
"futures 0.3.24",
|
|
||||||
"gpui",
|
|
||||||
"live_kit_client",
|
|
||||||
"live_kit_server",
|
|
||||||
"log",
|
|
||||||
"media",
|
|
||||||
"objc",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"postage",
|
|
||||||
"simplelog",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "castaway"
|
name = "castaway"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
|
|
@ -19,8 +19,9 @@ test-support = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
live_kit_client = { path = "../live_kit_client" }
|
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
live_kit_client = { path = "../live_kit_client" }
|
||||||
|
media = { path = "../media" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,8 @@ impl ActiveCall {
|
||||||
Room::create(recipient_user_id, initial_project, client, user_store, cx)
|
Room::create(recipient_user_id, initial_project, client, user_store, cx)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
room.update(&mut cx, |room, cx| room.share_screen(cx))
|
||||||
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| this.set_room(Some(room), cx));
|
this.update(&mut cx, |this, cx| this.set_room(Some(room), cx));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use client::{proto, User};
|
use client::{proto, User};
|
||||||
use gpui::WeakModelHandle;
|
use collections::HashMap;
|
||||||
|
use gpui::{Task, WeakModelHandle};
|
||||||
|
use media::core_video::CVImageBuffer;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -34,9 +36,23 @@ pub struct LocalParticipant {
|
||||||
pub active_project: Option<WeakModelHandle<Project>>,
|
pub active_project: Option<WeakModelHandle<Project>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct RemoteParticipant {
|
pub struct RemoteParticipant {
|
||||||
pub user: Arc<User>,
|
pub user: Arc<User>,
|
||||||
pub projects: Vec<proto::ParticipantProject>,
|
pub projects: Vec<proto::ParticipantProject>,
|
||||||
pub location: ParticipantLocation,
|
pub location: ParticipantLocation,
|
||||||
|
pub tracks: HashMap<String, RemoteVideoTrack>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RemoteVideoTrack {
|
||||||
|
pub(crate) frame: Option<CVImageBuffer>,
|
||||||
|
pub(crate) _live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
||||||
|
pub(crate) _maintain_frame: Arc<Task<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteVideoTrack {
|
||||||
|
pub fn frame(&self) -> Option<&CVImageBuffer> {
|
||||||
|
self.frame.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
|
||||||
IncomingCall,
|
IncomingCall,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
@ -7,7 +7,8 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||||
use collections::{BTreeMap, HashSet};
|
use collections::{BTreeMap, HashSet};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
|
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
|
||||||
use live_kit_client::LocalVideoTrack;
|
use live_kit_client::{LocalVideoTrack, RemoteVideoTrackChange};
|
||||||
|
use postage::watch;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -27,7 +28,7 @@ pub enum Event {
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
id: u64,
|
id: u64,
|
||||||
live_kit_room: Option<Arc<live_kit_client::Room>>,
|
live_kit_room: Option<(Arc<live_kit_client::Room>, Task<()>)>,
|
||||||
status: RoomStatus,
|
status: RoomStatus,
|
||||||
local_participant: LocalParticipant,
|
local_participant: LocalParticipant,
|
||||||
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
||||||
|
@ -75,17 +76,23 @@ impl Room {
|
||||||
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||||
let room = live_kit_client::Room::new();
|
let room = live_kit_client::Room::new();
|
||||||
let mut tracks = room.remote_video_tracks();
|
let mut tracks = room.remote_video_tracks();
|
||||||
cx.foreground()
|
let maintain_room = cx.spawn_weak(|this, mut cx| async move {
|
||||||
.spawn(async move {
|
while let Some(track_change) = tracks.next().await {
|
||||||
while let Some(track) = tracks.next().await {
|
let this = if let Some(this) = this.upgrade(&cx) {
|
||||||
dbg!("received track");
|
this
|
||||||
}
|
} else {
|
||||||
})
|
break;
|
||||||
.detach();
|
};
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.remote_video_track_changed(track_change, cx).log_err()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
cx.foreground()
|
cx.foreground()
|
||||||
.spawn(room.connect(&connection_info.server_url, &connection_info.token))
|
.spawn(room.connect(&connection_info.server_url, &connection_info.token))
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
Some(room)
|
Some((room, maintain_room))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -318,8 +325,20 @@ impl Room {
|
||||||
projects: participant.projects,
|
projects: participant.projects,
|
||||||
location: ParticipantLocation::from_proto(participant.location)
|
location: ParticipantLocation::from_proto(participant.location)
|
||||||
.unwrap_or(ParticipantLocation::External),
|
.unwrap_or(ParticipantLocation::External),
|
||||||
|
tracks: Default::default(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let Some((room, _)) = this.live_kit_room.as_ref() {
|
||||||
|
for track in
|
||||||
|
room.video_tracks_for_remote_participant(peer_id.0.to_string())
|
||||||
|
{
|
||||||
|
this.remote_video_track_changed(
|
||||||
|
RemoteVideoTrackChange::Subscribed(track),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.remote_participants.retain(|_, participant| {
|
this.remote_participants.retain(|_, participant| {
|
||||||
|
@ -357,6 +376,74 @@ impl Room {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remote_video_track_changed(
|
||||||
|
&mut self,
|
||||||
|
change: RemoteVideoTrackChange,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
match change {
|
||||||
|
RemoteVideoTrackChange::Subscribed(track) => {
|
||||||
|
let peer_id = PeerId(track.publisher_id().parse()?);
|
||||||
|
let track_id = track.id().to_string();
|
||||||
|
let participant = self
|
||||||
|
.remote_participants
|
||||||
|
.get_mut(&peer_id)
|
||||||
|
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||||
|
let (mut tx, mut rx) = watch::channel();
|
||||||
|
track.add_renderer(move |frame| *tx.borrow_mut() = Some(frame));
|
||||||
|
participant.tracks.insert(
|
||||||
|
track_id.clone(),
|
||||||
|
RemoteVideoTrack {
|
||||||
|
frame: None,
|
||||||
|
_live_kit_track: track,
|
||||||
|
_maintain_frame: Arc::new(cx.spawn_weak(|this, mut cx| async move {
|
||||||
|
while let Some(frame) = rx.next().await {
|
||||||
|
let this = if let Some(this) = this.upgrade(&cx) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
let done = this.update(&mut cx, |this, cx| {
|
||||||
|
// TODO: replace this with an emit.
|
||||||
|
cx.notify();
|
||||||
|
if let Some(track) =
|
||||||
|
this.remote_participants.get_mut(&peer_id).and_then(
|
||||||
|
|participant| participant.tracks.get_mut(&track_id),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
track.frame = frame;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if done {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RemoteVideoTrackChange::Unsubscribed {
|
||||||
|
publisher_id,
|
||||||
|
track_id,
|
||||||
|
} => {
|
||||||
|
let peer_id = PeerId(publisher_id.parse()?);
|
||||||
|
let participant = self
|
||||||
|
.remote_participants
|
||||||
|
.get_mut(&peer_id)
|
||||||
|
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||||
|
participant.tracks.remove(&track_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn check_invariants(&self) {
|
fn check_invariants(&self) {
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
{
|
{
|
||||||
|
@ -502,7 +589,7 @@ impl Room {
|
||||||
return Task::ready(Err(anyhow!("room is offline")));
|
return Task::ready(Err(anyhow!("room is offline")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let room = if let Some(room) = self.live_kit_room.as_ref() {
|
let room = if let Some((room, _)) = self.live_kit_room.as_ref() {
|
||||||
room.clone()
|
room.clone()
|
||||||
} else {
|
} else {
|
||||||
return Task::ready(Err(anyhow!("not connected to LiveKit")));
|
return Task::ready(Err(anyhow!("not connected to LiveKit")));
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "capture"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "An example of screen capture"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
gpui = { path = "../gpui" }
|
|
||||||
live_kit_client = { path = "../live_kit_client" }
|
|
||||||
live_kit_server = { path = "../live_kit_server" }
|
|
||||||
media = { path = "../media" }
|
|
||||||
|
|
||||||
anyhow = "1.0.38"
|
|
||||||
block = "0.1"
|
|
||||||
bytes = "1.2"
|
|
||||||
byteorder = "1.4"
|
|
||||||
cocoa = "0.24"
|
|
||||||
core-foundation = "0.9.3"
|
|
||||||
core-graphics = "0.22.3"
|
|
||||||
foreign-types = "0.3"
|
|
||||||
futures = "0.3"
|
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
|
||||||
objc = "0.2"
|
|
||||||
parking_lot = "0.11.1"
|
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
|
||||||
simplelog = "0.9"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
bindgen = "0.59.2"
|
|
|
@ -1,7 +0,0 @@
|
||||||
fn main() {
|
|
||||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle
|
|
||||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
|
||||||
|
|
||||||
// Register exported Objective-C selectors, protocols, etc
|
|
||||||
println!("cargo:rustc-link-arg=-Wl,-ObjC");
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
use futures::StreamExt;
|
|
||||||
use gpui::{
|
|
||||||
actions,
|
|
||||||
elements::{Canvas, *},
|
|
||||||
keymap::Binding,
|
|
||||||
platform::current::Surface,
|
|
||||||
Menu, MenuItem, ViewContext,
|
|
||||||
};
|
|
||||||
use live_kit_client::{LocalVideoTrack, Room};
|
|
||||||
use log::LevelFilter;
|
|
||||||
use media::core_video::CVImageBuffer;
|
|
||||||
use postage::watch;
|
|
||||||
use simplelog::SimpleLogger;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
actions!(capture, [Quit]);
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
|
||||||
|
|
||||||
gpui::App::new(()).unwrap().run(|cx| {
|
|
||||||
cx.platform().activate(true);
|
|
||||||
cx.add_global_action(quit);
|
|
||||||
|
|
||||||
cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
|
|
||||||
cx.set_menus(vec![Menu {
|
|
||||||
name: "Zed",
|
|
||||||
items: vec![MenuItem::Action {
|
|
||||||
name: "Quit",
|
|
||||||
action: Box::new(Quit),
|
|
||||||
}],
|
|
||||||
}]);
|
|
||||||
|
|
||||||
let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap();
|
|
||||||
let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap();
|
|
||||||
let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap();
|
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
|
||||||
let user1_token = live_kit_server::token::create(
|
|
||||||
&live_kit_key,
|
|
||||||
&live_kit_secret,
|
|
||||||
Some("test-participant-1"),
|
|
||||||
live_kit_server::token::VideoGrant {
|
|
||||||
room: Some("test-room"),
|
|
||||||
room_join: Some(true),
|
|
||||||
can_publish: Some(true),
|
|
||||||
can_subscribe: Some(true),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let room1 = Room::new();
|
|
||||||
room1.connect(&live_kit_url, &user1_token).await.unwrap();
|
|
||||||
|
|
||||||
let user2_token = live_kit_server::token::create(
|
|
||||||
&live_kit_key,
|
|
||||||
&live_kit_secret,
|
|
||||||
Some("test-participant-2"),
|
|
||||||
live_kit_server::token::VideoGrant {
|
|
||||||
room: Some("test-room"),
|
|
||||||
room_join: Some(true),
|
|
||||||
can_publish: Some(true),
|
|
||||||
can_subscribe: Some(true),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let room2 = Room::new();
|
|
||||||
room2.connect(&live_kit_url, &user2_token).await.unwrap();
|
|
||||||
cx.add_window(Default::default(), |cx| ScreenCaptureView::new(room2, cx));
|
|
||||||
|
|
||||||
let display_sources = live_kit_client::display_sources().await.unwrap();
|
|
||||||
let track = LocalVideoTrack::screen_share_for_display(display_sources.first().unwrap());
|
|
||||||
room1.publish_video_track(&track).await.unwrap();
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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_tracks();
|
|
||||||
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) {
|
|
||||||
cx.platform().quit();
|
|
||||||
}
|
|
|
@ -504,7 +504,7 @@ impl Server {
|
||||||
|
|
||||||
if let Some(room) = removed_connection.room {
|
if let Some(room) = removed_connection.room {
|
||||||
self.room_updated(&room);
|
self.room_updated(&room);
|
||||||
room_left = Some(self.room_left(&room, removed_connection.user_id));
|
room_left = Some(self.room_left(&room, connection_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
contacts_to_update.insert(removed_connection.user_id);
|
contacts_to_update.insert(removed_connection.user_id);
|
||||||
|
@ -613,7 +613,7 @@ impl Server {
|
||||||
.trace_err()
|
.trace_err()
|
||||||
{
|
{
|
||||||
if let Some(token) = live_kit
|
if let Some(token) = live_kit
|
||||||
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
.room_token(&room.live_kit_room, &request.sender_id.to_string())
|
||||||
.trace_err()
|
.trace_err()
|
||||||
{
|
{
|
||||||
Some(proto::LiveKitConnectionInfo {
|
Some(proto::LiveKitConnectionInfo {
|
||||||
|
@ -658,7 +658,7 @@ impl Server {
|
||||||
let live_kit_connection_info =
|
let live_kit_connection_info =
|
||||||
if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
||||||
if let Some(token) = live_kit
|
if let Some(token) = live_kit
|
||||||
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
.room_token(&room.live_kit_room, &request.sender_id.to_string())
|
||||||
.trace_err()
|
.trace_err()
|
||||||
{
|
{
|
||||||
Some(proto::LiveKitConnectionInfo {
|
Some(proto::LiveKitConnectionInfo {
|
||||||
|
@ -724,7 +724,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.room_updated(&left_room.room);
|
self.room_updated(&left_room.room);
|
||||||
room_left = self.room_left(&left_room.room, user_id);
|
room_left = self.room_left(&left_room.room, message.sender_id);
|
||||||
|
|
||||||
for connection_id in left_room.canceled_call_connection_ids {
|
for connection_id in left_room.canceled_call_connection_ids {
|
||||||
self.peer
|
self.peer
|
||||||
|
@ -883,13 +883,17 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn room_left(&self, room: &proto::Room, user_id: UserId) -> impl Future<Output = Result<()>> {
|
fn room_left(
|
||||||
|
&self,
|
||||||
|
room: &proto::Room,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
) -> impl Future<Output = Result<()>> {
|
||||||
let client = self.app_state.live_kit_client.clone();
|
let client = self.app_state.live_kit_client.clone();
|
||||||
let room_name = room.live_kit_room.clone();
|
let room_name = room.live_kit_room.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(client) = client {
|
if let Some(client) = client {
|
||||||
client
|
client
|
||||||
.remove_participant(room_name, user_id.to_string())
|
.remove_participant(room_name, connection_id.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,24 @@ import WebRTC
|
||||||
|
|
||||||
class LKRoomDelegate: RoomDelegate {
|
class LKRoomDelegate: RoomDelegate {
|
||||||
var data: UnsafeRawPointer
|
var data: UnsafeRawPointer
|
||||||
var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void
|
var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
|
||||||
|
var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
|
||||||
|
|
||||||
init(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) {
|
init(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) {
|
||||||
self.data = data
|
self.data = data
|
||||||
self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
|
self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack
|
||||||
|
self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
|
func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
|
||||||
if track.kind == .video {
|
if track.kind == .video {
|
||||||
self.onDidSubscribeToRemoteVideoTrack(self.data, Unmanaged.passRetained(track).toOpaque())
|
self.onDidSubscribeToRemoteVideoTrack(self.data, participant.sid as CFString, track.id as CFString, Unmanaged.passRetained(track).toOpaque())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
|
||||||
|
if track.kind == .video {
|
||||||
|
self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.sid as CFString, track.id as CFString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +61,8 @@ public func LKRelease(ptr: UnsafeRawPointer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@_cdecl("LKRoomDelegateCreate")
|
@_cdecl("LKRoomDelegateCreate")
|
||||||
public func LKRoomDelegateCreate(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
|
public func LKRoomDelegateCreate(data: UnsafeRawPointer, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) -> UnsafeMutableRawPointer {
|
||||||
let delegate = LKRoomDelegate(data: data, onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack)
|
let delegate = LKRoomDelegate(data: data, onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack)
|
||||||
return Unmanaged.passRetained(delegate).toOpaque()
|
return Unmanaged.passRetained(delegate).toOpaque()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +94,13 @@ public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPoin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_cdecl("LKRoomVideoTracksForRemoteParticipant")
|
||||||
|
public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
|
||||||
|
let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
|
||||||
|
let tracks = room.remoteParticipants[participantId as Sid]?.videoTracks.compactMap { $0.track as? RemoteVideoTrack }
|
||||||
|
return tracks as CFArray?
|
||||||
|
}
|
||||||
|
|
||||||
@_cdecl("LKCreateScreenShareTrackForDisplay")
|
@_cdecl("LKCreateScreenShareTrackForDisplay")
|
||||||
public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
|
public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer {
|
||||||
let display = Unmanaged<MacOSDisplay>.fromOpaque(display).takeUnretainedValue()
|
let display = Unmanaged<MacOSDisplay>.fromOpaque(display).takeUnretainedValue()
|
||||||
|
|
|
@ -22,8 +22,15 @@ extern "C" {
|
||||||
callback_data: *mut c_void,
|
callback_data: *mut c_void,
|
||||||
on_did_subscribe_to_remote_video_track: extern "C" fn(
|
on_did_subscribe_to_remote_video_track: extern "C" fn(
|
||||||
callback_data: *mut c_void,
|
callback_data: *mut c_void,
|
||||||
|
publisher_id: CFStringRef,
|
||||||
|
track_id: CFStringRef,
|
||||||
remote_track: *const c_void,
|
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;
|
) -> *const c_void;
|
||||||
|
|
||||||
fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
|
fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
|
||||||
|
@ -62,7 +69,7 @@ extern "C" {
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
native_room: *const c_void,
|
native_room: *const c_void,
|
||||||
remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
|
remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackChange>>>,
|
||||||
_delegate: RoomDelegate,
|
_delegate: RoomDelegate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +110,7 @@ impl Room {
|
||||||
async { rx.await.unwrap().context("error publishing video track") }
|
async { rx.await.unwrap().context("error publishing video track") }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
|
pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackChange> {
|
||||||
let (tx, rx) = mpsc::unbounded();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
self.remote_video_track_subscribers.lock().push(tx);
|
self.remote_video_track_subscribers.lock().push(tx);
|
||||||
rx
|
rx
|
||||||
|
@ -111,9 +118,20 @@ impl Room {
|
||||||
|
|
||||||
fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
|
fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
|
||||||
let track = Arc::new(track);
|
let track = Arc::new(track);
|
||||||
self.remote_video_track_subscribers
|
self.remote_video_track_subscribers.lock().retain(|tx| {
|
||||||
.lock()
|
tx.unbounded_send(RemoteVideoTrackChange::Subscribed(track.clone()))
|
||||||
.retain(|tx| tx.unbounded_send(track.clone()).is_ok());
|
.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(RemoteVideoTrackChange::Unsubscribed {
|
||||||
|
publisher_id: publisher_id.clone(),
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
})
|
||||||
|
.is_ok()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_done_callback() -> (
|
fn build_done_callback() -> (
|
||||||
|
@ -157,6 +175,7 @@ impl RoomDelegate {
|
||||||
LKRoomDelegateCreate(
|
LKRoomDelegateCreate(
|
||||||
weak_room as *mut c_void,
|
weak_room as *mut c_void,
|
||||||
Self::on_did_subscribe_to_remote_video_track,
|
Self::on_did_subscribe_to_remote_video_track,
|
||||||
|
Self::on_did_unsubscribe_from_remote_video_track,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
|
@ -165,14 +184,39 @@ impl RoomDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
|
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 room = unsafe { Weak::from_raw(room as *mut Room) };
|
||||||
let track = RemoteVideoTrack(track);
|
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 {
|
||||||
|
id: track_id,
|
||||||
|
native_track: track,
|
||||||
|
publisher_id,
|
||||||
|
};
|
||||||
if let Some(room) = room.upgrade() {
|
if let Some(room) = room.upgrade() {
|
||||||
room.did_subscribe_to_remote_video_track(track);
|
room.did_subscribe_to_remote_video_track(track);
|
||||||
}
|
}
|
||||||
let _ = Weak::into_raw(room);
|
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 {
|
impl Drop for RoomDelegate {
|
||||||
|
@ -198,9 +242,22 @@ impl Drop for LocalVideoTrack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RemoteVideoTrack(*const c_void);
|
#[derive(Debug)]
|
||||||
|
pub struct RemoteVideoTrack {
|
||||||
|
id: String,
|
||||||
|
native_track: *const c_void,
|
||||||
|
publisher_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl RemoteVideoTrack {
|
impl RemoteVideoTrack {
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn publisher_id(&self) -> &str {
|
||||||
|
&self.publisher_id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_renderer<F>(&self, callback: F)
|
pub fn add_renderer<F>(&self, callback: F)
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(CVImageBuffer),
|
F: 'static + FnMut(CVImageBuffer),
|
||||||
|
@ -226,17 +283,25 @@ impl RemoteVideoTrack {
|
||||||
unsafe {
|
unsafe {
|
||||||
let renderer =
|
let renderer =
|
||||||
LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
|
LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
|
||||||
LKVideoTrackAddRenderer(self.0, renderer);
|
LKVideoTrackAddRenderer(self.native_track, renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RemoteVideoTrack {
|
impl Drop for RemoteVideoTrack {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe { LKRelease(self.0) }
|
unsafe { LKRelease(self.native_track) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum RemoteVideoTrackChange {
|
||||||
|
Subscribed(Arc<RemoteVideoTrack>),
|
||||||
|
Unsubscribed {
|
||||||
|
publisher_id: String,
|
||||||
|
track_id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MacOSDisplay(*const c_void);
|
pub struct MacOSDisplay(*const c_void);
|
||||||
|
|
||||||
impl Drop for MacOSDisplay {
|
impl Drop for MacOSDisplay {
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn room_token_for_user(&self, room: &str, identity: &str) -> Result<String> {
|
pub fn room_token(&self, room: &str, identity: &str) -> Result<String> {
|
||||||
token::create(
|
token::create(
|
||||||
&self.key,
|
&self.key,
|
||||||
&self.secret,
|
&self.secret,
|
||||||
|
|
|
@ -201,21 +201,23 @@ impl Member {
|
||||||
.right()
|
.right()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
),
|
),
|
||||||
call::ParticipantLocation::External => Some(
|
call::ParticipantLocation::External => {
|
||||||
Label::new(
|
let frame = leader
|
||||||
format!(
|
.tracks
|
||||||
"{} is viewing a window outside of Zed",
|
.values()
|
||||||
leader.user.github_login
|
.next()
|
||||||
),
|
.and_then(|track| track.frame())
|
||||||
theme.workspace.external_location_message.text.clone(),
|
.cloned();
|
||||||
)
|
return Canvas::new(move |bounds, _, cx| {
|
||||||
.contained()
|
if let Some(frame) = frame.clone() {
|
||||||
.with_style(theme.workspace.external_location_message.container)
|
cx.scene.push_surface(gpui::mac::Surface {
|
||||||
.aligned()
|
bounds,
|
||||||
.bottom()
|
image_buffer: frame,
|
||||||
.right()
|
});
|
||||||
.boxed(),
|
}
|
||||||
),
|
})
|
||||||
|
.boxed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue