WIP: Start integrating screen-sharing
This commit is contained in:
parent
cce00526b9
commit
81d83841ab
8 changed files with 130 additions and 53 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -731,6 +731,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"futures 0.3.24",
|
"futures 0.3.24",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"live_kit_client",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"util",
|
"util",
|
||||||
|
|
|
@ -19,6 +19,7 @@ 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" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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 project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -26,6 +27,7 @@ pub enum Event {
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
id: u64,
|
id: u64,
|
||||||
|
live_kit_room: Option<Arc<live_kit_client::Room>>,
|
||||||
status: RoomStatus,
|
status: RoomStatus,
|
||||||
local_participant: LocalParticipant,
|
local_participant: LocalParticipant,
|
||||||
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
||||||
|
@ -50,6 +52,7 @@ impl Entity for Room {
|
||||||
impl Room {
|
impl Room {
|
||||||
fn new(
|
fn new(
|
||||||
id: u64,
|
id: u64,
|
||||||
|
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
|
@ -69,8 +72,27 @@ impl Room {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||||
|
let room = live_kit_client::Room::new();
|
||||||
|
let mut tracks = room.remote_video_tracks();
|
||||||
|
cx.foreground()
|
||||||
|
.spawn(async move {
|
||||||
|
while let Some(track) = tracks.next().await {
|
||||||
|
dbg!("received track");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
cx.foreground()
|
||||||
|
.spawn(room.connect(&connection_info.server_url, &connection_info.token))
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
Some(room)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
live_kit_room,
|
||||||
status: RoomStatus::Online,
|
status: RoomStatus::Online,
|
||||||
participant_user_ids: Default::default(),
|
participant_user_ids: Default::default(),
|
||||||
local_participant: Default::default(),
|
local_participant: Default::default(),
|
||||||
|
@ -95,7 +117,15 @@ impl Room {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let response = client.request(proto::CreateRoom {}).await?;
|
let response = client.request(proto::CreateRoom {}).await?;
|
||||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||||
let room = cx.add_model(|cx| Self::new(room_proto.id, client, user_store, cx));
|
let room = cx.add_model(|cx| {
|
||||||
|
Self::new(
|
||||||
|
room_proto.id,
|
||||||
|
response.live_kit_connection_info,
|
||||||
|
client,
|
||||||
|
user_store,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||||
let initial_project_id = room
|
let initial_project_id = room
|
||||||
|
@ -131,7 +161,15 @@ impl Room {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let response = client.request(proto::JoinRoom { id: room_id }).await?;
|
let response = client.request(proto::JoinRoom { id: room_id }).await?;
|
||||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||||
let room = cx.add_model(|cx| Self::new(room_id, client, user_store, cx));
|
let room = cx.add_model(|cx| {
|
||||||
|
Self::new(
|
||||||
|
room_id,
|
||||||
|
response.live_kit_connection_info,
|
||||||
|
client,
|
||||||
|
user_store,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
room.update(&mut cx, |room, cx| {
|
room.update(&mut cx, |room, cx| {
|
||||||
room.leave_when_empty = true;
|
room.leave_when_empty = true;
|
||||||
room.apply_room_update(room_proto, cx)?;
|
room.apply_room_update(room_proto, cx)?;
|
||||||
|
@ -458,6 +496,28 @@ impl Room {
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
|
if self.status.is_offline() {
|
||||||
|
return Task::ready(Err(anyhow!("room is offline")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let room = if let Some(room) = self.live_kit_room.as_ref() {
|
||||||
|
room.clone()
|
||||||
|
} else {
|
||||||
|
return Task::ready(Err(anyhow!("not connected to LiveKit")));
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.foreground().spawn(async move {
|
||||||
|
let displays = live_kit_client::display_sources().await?;
|
||||||
|
let display = displays
|
||||||
|
.first()
|
||||||
|
.ok_or_else(|| anyhow!("no display found"))?;
|
||||||
|
let track = LocalVideoTrack::screen_share_for_display(display);
|
||||||
|
room.publish_video_track(&track).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
db::{self, ChannelId, MessageId, ProjectId, User, UserId},
|
db::{self, ChannelId, MessageId, ProjectId, User, UserId},
|
||||||
AppState, Result,
|
AppState, Result,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::anyhow;
|
||||||
use async_tungstenite::tungstenite::{
|
use async_tungstenite::tungstenite::{
|
||||||
protocol::CloseFrame as TungsteniteCloseFrame, Message as TungsteniteMessage,
|
protocol::CloseFrame as TungsteniteCloseFrame, Message as TungsteniteMessage,
|
||||||
};
|
};
|
||||||
|
@ -605,27 +605,24 @@ impl Server {
|
||||||
room = store.create_room(request.sender_id)?.clone();
|
room = store.create_room(request.sender_id)?.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let live_kit_token = if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
let live_kit_connection_info =
|
||||||
|
if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
||||||
if let Some(_) = live_kit
|
if let Some(_) = live_kit
|
||||||
.create_room(room.live_kit_room.clone())
|
.create_room(room.live_kit_room.clone())
|
||||||
.await
|
.await
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"error creating LiveKit room (LiveKit room: {}, Zed room: {})",
|
|
||||||
room.live_kit_room, room.id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.trace_err()
|
.trace_err()
|
||||||
{
|
{
|
||||||
live_kit
|
if let Some(token) = live_kit
|
||||||
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"error creating LiveKit access token (LiveKit room: {}, Zed room: {})",
|
|
||||||
room.live_kit_room, room.id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.trace_err()
|
.trace_err()
|
||||||
|
{
|
||||||
|
Some(proto::LiveKitConnectionInfo {
|
||||||
|
server_url: live_kit.url().into(),
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -635,7 +632,7 @@ impl Server {
|
||||||
|
|
||||||
response.send(proto::CreateRoomResponse {
|
response.send(proto::CreateRoomResponse {
|
||||||
room: Some(room),
|
room: Some(room),
|
||||||
live_kit_token,
|
live_kit_connection_info,
|
||||||
})?;
|
})?;
|
||||||
self.update_user_contacts(user_id).await?;
|
self.update_user_contacts(user_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -658,23 +655,26 @@ impl Server {
|
||||||
.trace_err();
|
.trace_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
let live_kit_token = if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
let live_kit_connection_info =
|
||||||
live_kit
|
if let Some(live_kit) = self.app_state.live_kit_client.as_ref() {
|
||||||
|
if let Some(token) = live_kit
|
||||||
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
.room_token_for_user(&room.live_kit_room, &user_id.to_string())
|
||||||
.with_context(|| {
|
|
||||||
format!(
|
|
||||||
"error creating LiveKit access token (LiveKit room: {}, Zed room: {})",
|
|
||||||
room.live_kit_room, room.id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.trace_err()
|
.trace_err()
|
||||||
|
{
|
||||||
|
Some(proto::LiveKitConnectionInfo {
|
||||||
|
server_url: live_kit.url().into(),
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
response.send(proto::JoinRoomResponse {
|
response.send(proto::JoinRoomResponse {
|
||||||
room: Some(room.clone()),
|
room: Some(room.clone()),
|
||||||
live_kit_token,
|
live_kit_connection_info,
|
||||||
})?;
|
})?;
|
||||||
self.room_updated(room);
|
self.room_updated(room);
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRaw
|
||||||
|
|
||||||
@_cdecl("LKDisplaySources")
|
@_cdecl("LKDisplaySources")
|
||||||
public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) {
|
public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) {
|
||||||
MacOSScreenCapturer.displaySources().then { displaySources in
|
MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in
|
||||||
callback(data, displaySources as CFArray, nil)
|
callback(data, displaySources as CFArray, nil)
|
||||||
}.catch { error in
|
}.catch { error in
|
||||||
callback(data, nil, error.localizedDescription as CFString)
|
callback(data, nil, error.localizedDescription as CFString)
|
||||||
|
|
|
@ -7,25 +7,29 @@ use std::{future::Future, sync::Arc};
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
http: reqwest::Client,
|
http: reqwest::Client,
|
||||||
uri: Arc<str>,
|
url: Arc<str>,
|
||||||
key: Arc<str>,
|
key: Arc<str>,
|
||||||
secret: Arc<str>,
|
secret: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(mut uri: String, key: String, secret: String) -> Self {
|
pub fn new(mut url: String, key: String, secret: String) -> Self {
|
||||||
if uri.ends_with('/') {
|
if url.ends_with('/') {
|
||||||
uri.pop();
|
url.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
http: reqwest::Client::new(),
|
http: reqwest::Client::new(),
|
||||||
uri: uri.into(),
|
url: url.into(),
|
||||||
key: key.into(),
|
key: key.into(),
|
||||||
secret: secret.into(),
|
secret: secret.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn url(&self) -> &str {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_room(&self, name: String) -> impl Future<Output = Result<proto::Room>> {
|
pub fn create_room(&self, name: String) -> impl Future<Output = Result<proto::Room>> {
|
||||||
self.request(
|
self.request(
|
||||||
"twirp/livekit.RoomService/CreateRoom",
|
"twirp/livekit.RoomService/CreateRoom",
|
||||||
|
@ -101,11 +105,11 @@ impl Client {
|
||||||
{
|
{
|
||||||
let client = self.http.clone();
|
let client = self.http.clone();
|
||||||
let token = token::create(&self.key, &self.secret, None, grant);
|
let token = token::create(&self.key, &self.secret, None, grant);
|
||||||
let uri = format!("{}/{}", self.uri, path);
|
let url = format!("{}/{}", self.url, path);
|
||||||
async move {
|
async move {
|
||||||
let token = token?;
|
let token = token?;
|
||||||
let response = client
|
let response = client
|
||||||
.post(&uri)
|
.post(&url)
|
||||||
.header(CONTENT_TYPE, "application/protobuf")
|
.header(CONTENT_TYPE, "application/protobuf")
|
||||||
.bearer_auth(token)
|
.bearer_auth(token)
|
||||||
.body(body.encode_to_vec())
|
.body(body.encode_to_vec())
|
||||||
|
@ -116,7 +120,7 @@ impl Client {
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"POST {} failed with status code {:?}, {:?}",
|
"POST {} failed with status code {:?}, {:?}",
|
||||||
uri,
|
url,
|
||||||
response.status(),
|
response.status(),
|
||||||
response.text().await
|
response.text().await
|
||||||
))
|
))
|
||||||
|
|
|
@ -141,7 +141,7 @@ message CreateRoom {}
|
||||||
|
|
||||||
message CreateRoomResponse {
|
message CreateRoomResponse {
|
||||||
Room room = 1;
|
Room room = 1;
|
||||||
optional string live_kit_token = 2;
|
optional LiveKitConnectionInfo live_kit_connection_info = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message JoinRoom {
|
message JoinRoom {
|
||||||
|
@ -150,7 +150,7 @@ message JoinRoom {
|
||||||
|
|
||||||
message JoinRoomResponse {
|
message JoinRoomResponse {
|
||||||
Room room = 1;
|
Room room = 1;
|
||||||
optional string live_kit_token = 2;
|
optional LiveKitConnectionInfo live_kit_connection_info = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LeaveRoom {
|
message LeaveRoom {
|
||||||
|
@ -225,6 +225,11 @@ message RoomUpdated {
|
||||||
Room room = 1;
|
Room room = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LiveKitConnectionInfo {
|
||||||
|
string server_url = 1;
|
||||||
|
string token = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ShareProject {
|
message ShareProject {
|
||||||
uint64 room_id = 1;
|
uint64 room_id = 1;
|
||||||
repeated WorktreeMetadata worktrees = 2;
|
repeated WorktreeMetadata worktrees = 2;
|
||||||
|
|
|
@ -7,6 +7,12 @@ fn main() {
|
||||||
println!("cargo:rustc-env=ZED_AMPLITUDE_API_KEY={api_key}");
|
println!("cargo:rustc-env=ZED_AMPLITUDE_API_KEY={api_key}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
let output = Command::new("npm")
|
let output = Command::new("npm")
|
||||||
.current_dir("../../styles")
|
.current_dir("../../styles")
|
||||||
.args(["install", "--no-save"])
|
.args(["install", "--no-save"])
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue