Compare commits
3 commits
main
...
read-only-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9ec697390d | ||
![]() |
be56f775d4 | ||
![]() |
a0049f8d16 |
10 changed files with 118 additions and 118 deletions
|
@ -1,5 +1,4 @@
|
|||
pub mod call_settings;
|
||||
pub mod participant;
|
||||
pub mod room;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
|
@ -18,7 +17,7 @@ use room::Event;
|
|||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use participant::ParticipantLocation;
|
||||
pub use live_kit_client::{Frame, RemoteAudioTrack, RemoteVideoTrack};
|
||||
pub use room::Room;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use client::ParticipantIndex;
|
||||
use client::{proto, User};
|
||||
use collections::HashMap;
|
||||
use gpui::WeakModel;
|
||||
pub use live_kit_client::Frame;
|
||||
pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParticipantLocation {
|
||||
SharedProject { project_id: u64 },
|
||||
UnsharedProject,
|
||||
External,
|
||||
}
|
||||
|
||||
impl ParticipantLocation {
|
||||
pub fn from_proto(location: Option<proto::ParticipantLocation>) -> Result<Self> {
|
||||
match location.and_then(|l| l.variant) {
|
||||
Some(proto::participant_location::Variant::SharedProject(project)) => {
|
||||
Ok(Self::SharedProject {
|
||||
project_id: project.id,
|
||||
})
|
||||
}
|
||||
Some(proto::participant_location::Variant::UnsharedProject(_)) => {
|
||||
Ok(Self::UnsharedProject)
|
||||
}
|
||||
Some(proto::participant_location::Variant::External(_)) => Ok(Self::External),
|
||||
None => Err(anyhow!("participant location was not provided")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct LocalParticipant {
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub active_project: Option<WeakModel<Project>>,
|
||||
pub role: proto::ChannelRole,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteParticipant {
|
||||
pub user: Arc<User>,
|
||||
pub peer_id: proto::PeerId,
|
||||
pub role: proto::ChannelRole,
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub location: ParticipantLocation,
|
||||
pub participant_index: ParticipantIndex,
|
||||
pub muted: bool,
|
||||
pub speaking: bool,
|
||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
use crate::{
|
||||
call_settings::CallSettings,
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
||||
};
|
||||
use crate::call_settings::CallSettings;
|
||||
use anyhow::{anyhow, Result};
|
||||
use audio::{Audio, Sound};
|
||||
use client::{
|
||||
proto::{self, PeerId},
|
||||
Client, ParticipantIndex, TypedEnvelope, User, UserStore,
|
||||
Client, Participant, ParticipantIndex, ParticipantLocation, TypedEnvelope, User, UserStore,
|
||||
};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
|
@ -16,8 +13,8 @@ use gpui::{
|
|||
};
|
||||
use language::LanguageRegistry;
|
||||
use live_kit_client::{
|
||||
LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
|
||||
RemoteVideoTrackUpdate,
|
||||
LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrack,
|
||||
RemoteAudioTrackUpdate, RemoteVideoTrack, RemoteVideoTrackUpdate,
|
||||
};
|
||||
use postage::{sink::Sink, stream::Stream, watch};
|
||||
use project::Project;
|
||||
|
@ -63,7 +60,9 @@ pub struct Room {
|
|||
shared_projects: HashSet<WeakModel<Project>>,
|
||||
joined_projects: HashSet<WeakModel<Project>>,
|
||||
local_participant: LocalParticipant,
|
||||
remote_participants: BTreeMap<u64, RemoteParticipant>,
|
||||
remote_participants: BTreeMap<u64, Participant>,
|
||||
remote_audio_tracks: BTreeMap<u64, HashMap<String, Arc<RemoteAudioTrack>>>,
|
||||
remote_video_tracks: BTreeMap<u64, HashMap<String, Arc<RemoteVideoTrack>>>,
|
||||
pending_participants: Vec<Arc<User>>,
|
||||
participant_user_ids: HashSet<u64>,
|
||||
pending_call_count: usize,
|
||||
|
@ -79,6 +78,13 @@ pub struct Room {
|
|||
maintain_connection: Option<Task<Option<()>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct LocalParticipant {
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub active_project: Option<WeakModel<Project>>,
|
||||
pub role: proto::ChannelRole,
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for Room {}
|
||||
|
||||
impl Room {
|
||||
|
@ -227,6 +233,8 @@ impl Room {
|
|||
],
|
||||
leave_when_empty: false,
|
||||
pending_room_update: None,
|
||||
remote_audio_tracks: Default::default(),
|
||||
remote_video_tracks: Default::default(),
|
||||
client,
|
||||
user_store,
|
||||
follows_by_leader_id_project_id: Default::default(),
|
||||
|
@ -600,16 +608,22 @@ impl Room {
|
|||
&self.local_participant
|
||||
}
|
||||
|
||||
pub fn remote_participants(&self) -> &BTreeMap<u64, RemoteParticipant> {
|
||||
pub fn remote_participants(&self) -> &BTreeMap<u64, Participant> {
|
||||
&self.remote_participants
|
||||
}
|
||||
|
||||
pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&RemoteParticipant> {
|
||||
pub fn remote_participant_for_peer_id(&self, peer_id: PeerId) -> Option<&Participant> {
|
||||
self.remote_participants
|
||||
.values()
|
||||
.find(|p| p.peer_id == peer_id)
|
||||
}
|
||||
|
||||
pub fn video_track_for_participant(&self, user_id: u64) -> Option<Arc<RemoteVideoTrack>> {
|
||||
self.remote_video_tracks
|
||||
.get(&user_id)
|
||||
.and_then(|tracks| tracks.values().next().cloned())
|
||||
}
|
||||
|
||||
pub fn role_for_user(&self, user_id: u64) -> Option<proto::ChannelRole> {
|
||||
self.remote_participants
|
||||
.get(&user_id)
|
||||
|
@ -814,7 +828,7 @@ impl Room {
|
|||
} else {
|
||||
this.remote_participants.insert(
|
||||
participant.user_id,
|
||||
RemoteParticipant {
|
||||
Participant {
|
||||
user: user.clone(),
|
||||
participant_index,
|
||||
peer_id,
|
||||
|
@ -823,8 +837,6 @@ impl Room {
|
|||
role,
|
||||
muted: true,
|
||||
speaking: false,
|
||||
video_tracks: Default::default(),
|
||||
audio_tracks: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -875,6 +887,10 @@ impl Room {
|
|||
false
|
||||
}
|
||||
});
|
||||
this.remote_audio_tracks
|
||||
.retain(|user_id, _| this.participant_user_ids.contains(user_id));
|
||||
this.remote_video_tracks
|
||||
.retain(|user_id, _| this.participant_user_ids.contains(user_id));
|
||||
}
|
||||
|
||||
if let Some(pending_participants) = pending_participants.log_err() {
|
||||
|
@ -953,9 +969,12 @@ impl Room {
|
|||
let track_id = track.sid().to_string();
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.get(&user_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
participant.video_tracks.insert(track_id.clone(), track);
|
||||
self.remote_video_tracks
|
||||
.entry(user_id)
|
||||
.or_default()
|
||||
.insert(track_id, track);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
|
@ -967,9 +986,11 @@ impl Room {
|
|||
let user_id = publisher_id.parse()?;
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.get(&user_id)
|
||||
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||
participant.video_tracks.remove(&track_id);
|
||||
if let Some(tracks) = self.remote_video_tracks.get_mut(&user_id) {
|
||||
tracks.remove(&track_id);
|
||||
}
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
|
@ -1012,11 +1033,13 @@ impl Room {
|
|||
}
|
||||
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
|
||||
let mut found = false;
|
||||
for participant in &mut self.remote_participants.values_mut() {
|
||||
for track in participant.audio_tracks.values() {
|
||||
if track.sid() == track_id {
|
||||
found = true;
|
||||
break;
|
||||
for (user_id, participant) in self.remote_participants.iter_mut() {
|
||||
if let Some(tracks) = &self.remote_audio_tracks.get(user_id) {
|
||||
for track in tracks.values() {
|
||||
if track.sid() == track_id {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
|
@ -1034,7 +1057,10 @@ impl Room {
|
|||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
participant.audio_tracks.insert(track_id.clone(), track);
|
||||
self.remote_audio_tracks
|
||||
.entry(user_id)
|
||||
.or_default()
|
||||
.insert(track_id, track);
|
||||
participant.muted = publication.is_muted();
|
||||
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
|
@ -1050,7 +1076,9 @@ impl Room {
|
|||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||
participant.audio_tracks.remove(&track_id);
|
||||
if let Some(tracks) = self.remote_audio_tracks.get_mut(&user_id) {
|
||||
tracks.remove(&track_id);
|
||||
}
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
|
|
|
@ -29,6 +29,25 @@ pub struct Collaborator {
|
|||
pub user_id: UserId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Participant {
|
||||
pub user: Arc<User>,
|
||||
pub peer_id: proto::PeerId,
|
||||
pub role: proto::ChannelRole,
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub location: ParticipantLocation,
|
||||
pub participant_index: ParticipantIndex,
|
||||
pub muted: bool,
|
||||
pub speaking: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParticipantLocation {
|
||||
SharedProject { project_id: u64 },
|
||||
UnsharedProject,
|
||||
External,
|
||||
}
|
||||
|
||||
impl PartialOrd for User {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
|
@ -692,3 +711,20 @@ impl Collaborator {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticipantLocation {
|
||||
pub fn from_proto(location: Option<proto::ParticipantLocation>) -> Result<Self> {
|
||||
match location.and_then(|l| l.variant) {
|
||||
Some(proto::participant_location::Variant::SharedProject(project)) => {
|
||||
Ok(Self::SharedProject {
|
||||
project_id: project.id,
|
||||
})
|
||||
}
|
||||
Some(proto::participant_location::Variant::UnsharedProject(_)) => {
|
||||
Ok(Self::UnsharedProject)
|
||||
}
|
||||
Some(proto::participant_location::Variant::External(_)) => Ok(Self::External),
|
||||
None => Err(anyhow!("participant location was not provided")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ use crate::{
|
|||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{channel_id, room_participants, RoomParticipants, TestClient, TestServer},
|
||||
};
|
||||
use call::{room, ActiveCall, ParticipantLocation, Room};
|
||||
use client::{User, RECEIVE_TIMEOUT};
|
||||
use call::{room, ActiveCall, Room};
|
||||
use client::{ParticipantLocation, User, RECEIVE_TIMEOUT};
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::StreamExt as _;
|
||||
|
@ -248,12 +248,9 @@ async fn test_basic_calls(
|
|||
assert_eq!(participant_id, client_a.peer_id().unwrap());
|
||||
|
||||
room_b.read_with(cx_b, |room, _| {
|
||||
assert_eq!(
|
||||
room.remote_participants()[&client_a.user_id().unwrap()]
|
||||
.video_tracks
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert!(room
|
||||
.video_track_for_participant(client_a.user_id().unwrap())
|
||||
.is_some());
|
||||
});
|
||||
} else {
|
||||
panic!("unexpected event")
|
||||
|
@ -266,12 +263,9 @@ async fn test_basic_calls(
|
|||
assert_eq!(participant_id, client_a.peer_id().unwrap());
|
||||
|
||||
room_c.read_with(cx_c, |room, _| {
|
||||
assert_eq!(
|
||||
room.remote_participants()[&client_a.user_id().unwrap()]
|
||||
.video_tracks
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert!(room
|
||||
.video_track_for_participant(client_a.user_id().unwrap())
|
||||
.is_some());
|
||||
});
|
||||
} else {
|
||||
panic!("unexpected event")
|
||||
|
@ -5710,15 +5704,9 @@ async fn test_join_call_after_screen_was_shared(
|
|||
);
|
||||
|
||||
// Ensure User B sees User A's screenshare.
|
||||
|
||||
room_b.read_with(cx_b, |room, _| {
|
||||
assert_eq!(
|
||||
room.remote_participants()
|
||||
.get(&client_a.user_id().unwrap())
|
||||
.unwrap()
|
||||
.video_tracks
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
assert!(room
|
||||
.video_track_for_participant(client_a.user_id().unwrap())
|
||||
.is_some());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -466,6 +466,9 @@ impl CollabPanel {
|
|||
for mat in matches {
|
||||
let user_id = mat.candidate_id as u64;
|
||||
let participant = &room.remote_participants()[&user_id];
|
||||
let has_shared_screen = room
|
||||
.video_track_for_participant(participant.user.id)
|
||||
.is_some();
|
||||
self.entries.push(ListEntry::CallParticipant {
|
||||
user: participant.user.clone(),
|
||||
peer_id: Some(participant.peer_id),
|
||||
|
@ -477,11 +480,10 @@ impl CollabPanel {
|
|||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
host_user_id: participant.user.id,
|
||||
is_last: projects.peek().is_none()
|
||||
&& participant.video_tracks.is_empty(),
|
||||
is_last: projects.peek().is_none() && !has_shared_screen,
|
||||
});
|
||||
}
|
||||
if !participant.video_tracks.is_empty() {
|
||||
if has_shared_screen {
|
||||
self.entries.push(ListEntry::ParticipantScreen {
|
||||
peer_id: Some(participant.peer_id),
|
||||
is_last: true,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::face_pile::FacePile;
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::{ActiveCall, ParticipantLocation, Room};
|
||||
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
|
||||
use call::{ActiveCall, Room};
|
||||
use client::{proto::PeerId, Client, ParticipantIndex, ParticipantLocation, User, UserStore};
|
||||
use gpui::{
|
||||
actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
|
||||
use anyhow::{anyhow, Result};
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use call::ActiveCall;
|
||||
use client::ParticipantLocation;
|
||||
use collections::HashMap;
|
||||
use gpui::{
|
||||
point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
ItemNavHistory, WorkspaceId,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use call::participant::{Frame, RemoteVideoTrack};
|
||||
use call::{Frame, RemoteVideoTrack};
|
||||
use client::{proto::PeerId, User};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
|
|
|
@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result};
|
|||
use call::ActiveCall;
|
||||
use client::{
|
||||
proto::{self, PeerId},
|
||||
Client, Status, TypedEnvelope, UserStore,
|
||||
Client, ParticipantLocation, Status, TypedEnvelope, UserStore,
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
|
||||
|
@ -2398,9 +2398,9 @@ impl Workspace {
|
|||
let project = self.project.read(cx);
|
||||
|
||||
let other_project_id = match remote_participant.location {
|
||||
call::ParticipantLocation::External => None,
|
||||
call::ParticipantLocation::UnsharedProject => None,
|
||||
call::ParticipantLocation::SharedProject { project_id } => {
|
||||
ParticipantLocation::External => None,
|
||||
ParticipantLocation::UnsharedProject => None,
|
||||
ParticipantLocation::SharedProject { project_id } => {
|
||||
if Some(project_id) == project.remote_id() {
|
||||
None
|
||||
} else {
|
||||
|
@ -2789,15 +2789,15 @@ impl Workspace {
|
|||
let leader_in_this_app;
|
||||
let leader_in_this_project;
|
||||
match participant.location {
|
||||
call::ParticipantLocation::SharedProject { project_id } => {
|
||||
ParticipantLocation::SharedProject { project_id } => {
|
||||
leader_in_this_app = true;
|
||||
leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
|
||||
}
|
||||
call::ParticipantLocation::UnsharedProject => {
|
||||
ParticipantLocation::UnsharedProject => {
|
||||
leader_in_this_app = true;
|
||||
leader_in_this_project = false;
|
||||
}
|
||||
call::ParticipantLocation::External => {
|
||||
ParticipantLocation::External => {
|
||||
leader_in_this_app = false;
|
||||
leader_in_this_project = false;
|
||||
}
|
||||
|
@ -2848,7 +2848,7 @@ impl Workspace {
|
|||
let call = self.active_call()?;
|
||||
let room = call.read(cx).room()?.read(cx);
|
||||
let participant = room.remote_participant_for_peer_id(peer_id)?;
|
||||
let track = participant.video_tracks.values().next()?.clone();
|
||||
let track = room.video_track_for_participant(participant.user.id)?;
|
||||
let user = participant.user.clone();
|
||||
|
||||
for item in pane.read(cx).items_of_type::<SharedScreen>() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue