Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Max Brunsfeld
9ec697390d Move Participant and ParticipantLocation down into client crate 2024-01-05 17:09:18 -08:00
Max Brunsfeld
be56f775d4 Move live_kit re-exports to main call module 2024-01-05 17:01:40 -08:00
Max Brunsfeld
a0049f8d16 Move audio + video tracks off of participant struct
This way, that struct can be used in the project crate.
2024-01-05 16:57:52 -08:00
10 changed files with 118 additions and 118 deletions

View file

@ -1,5 +1,4 @@
pub mod call_settings; pub mod call_settings;
pub mod participant;
pub mod room; pub mod room;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
@ -18,7 +17,7 @@ use room::Event;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
pub use participant::ParticipantLocation; pub use live_kit_client::{Frame, RemoteAudioTrack, RemoteVideoTrack};
pub use room::Room; pub use room::Room;
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {

View file

@ -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>>,
}

View file

@ -1,12 +1,9 @@
use crate::{ use crate::call_settings::CallSettings;
call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use audio::{Audio, Sound}; use audio::{Audio, Sound};
use client::{ use client::{
proto::{self, PeerId}, proto::{self, PeerId},
Client, ParticipantIndex, TypedEnvelope, User, UserStore, Client, Participant, ParticipantIndex, ParticipantLocation, TypedEnvelope, User, UserStore,
}; };
use collections::{BTreeMap, HashMap, HashSet}; use collections::{BTreeMap, HashMap, HashSet};
use fs::Fs; use fs::Fs;
@ -16,8 +13,8 @@ use gpui::{
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use live_kit_client::{ use live_kit_client::{
LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrack,
RemoteVideoTrackUpdate, RemoteAudioTrackUpdate, RemoteVideoTrack, RemoteVideoTrackUpdate,
}; };
use postage::{sink::Sink, stream::Stream, watch}; use postage::{sink::Sink, stream::Stream, watch};
use project::Project; use project::Project;
@ -63,7 +60,9 @@ pub struct Room {
shared_projects: HashSet<WeakModel<Project>>, shared_projects: HashSet<WeakModel<Project>>,
joined_projects: HashSet<WeakModel<Project>>, joined_projects: HashSet<WeakModel<Project>>,
local_participant: LocalParticipant, 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>>, pending_participants: Vec<Arc<User>>,
participant_user_ids: HashSet<u64>, participant_user_ids: HashSet<u64>,
pending_call_count: usize, pending_call_count: usize,
@ -79,6 +78,13 @@ pub struct Room {
maintain_connection: Option<Task<Option<()>>>, 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 EventEmitter<Event> for Room {}
impl Room { impl Room {
@ -227,6 +233,8 @@ impl Room {
], ],
leave_when_empty: false, leave_when_empty: false,
pending_room_update: None, pending_room_update: None,
remote_audio_tracks: Default::default(),
remote_video_tracks: Default::default(),
client, client,
user_store, user_store,
follows_by_leader_id_project_id: Default::default(), follows_by_leader_id_project_id: Default::default(),
@ -600,16 +608,22 @@ impl Room {
&self.local_participant &self.local_participant
} }
pub fn remote_participants(&self) -> &BTreeMap<u64, RemoteParticipant> { pub fn remote_participants(&self) -> &BTreeMap<u64, Participant> {
&self.remote_participants &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 self.remote_participants
.values() .values()
.find(|p| p.peer_id == peer_id) .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> { pub fn role_for_user(&self, user_id: u64) -> Option<proto::ChannelRole> {
self.remote_participants self.remote_participants
.get(&user_id) .get(&user_id)
@ -814,7 +828,7 @@ impl Room {
} else { } else {
this.remote_participants.insert( this.remote_participants.insert(
participant.user_id, participant.user_id,
RemoteParticipant { Participant {
user: user.clone(), user: user.clone(),
participant_index, participant_index,
peer_id, peer_id,
@ -823,8 +837,6 @@ impl Room {
role, role,
muted: true, muted: true,
speaking: false, speaking: false,
video_tracks: Default::default(),
audio_tracks: Default::default(),
}, },
); );
@ -875,6 +887,10 @@ impl Room {
false 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() { if let Some(pending_participants) = pending_participants.log_err() {
@ -953,9 +969,12 @@ impl Room {
let track_id = track.sid().to_string(); let track_id = track.sid().to_string();
let participant = self let participant = self
.remote_participants .remote_participants
.get_mut(&user_id) .get(&user_id)
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; .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 { cx.emit(Event::RemoteVideoTracksChanged {
participant_id: participant.peer_id, participant_id: participant.peer_id,
}); });
@ -967,9 +986,11 @@ impl Room {
let user_id = publisher_id.parse()?; let user_id = publisher_id.parse()?;
let participant = self let participant = self
.remote_participants .remote_participants
.get_mut(&user_id) .get(&user_id)
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; .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 { cx.emit(Event::RemoteVideoTracksChanged {
participant_id: participant.peer_id, participant_id: participant.peer_id,
}); });
@ -1012,11 +1033,13 @@ impl Room {
} }
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
let mut found = false; let mut found = false;
for participant in &mut self.remote_participants.values_mut() { for (user_id, participant) in self.remote_participants.iter_mut() {
for track in participant.audio_tracks.values() { if let Some(tracks) = &self.remote_audio_tracks.get(user_id) {
if track.sid() == track_id { for track in tracks.values() {
found = true; if track.sid() == track_id {
break; found = true;
break;
}
} }
} }
if found { if found {
@ -1034,7 +1057,10 @@ impl Room {
.remote_participants .remote_participants
.get_mut(&user_id) .get_mut(&user_id)
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; .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(); participant.muted = publication.is_muted();
cx.emit(Event::RemoteAudioTracksChanged { cx.emit(Event::RemoteAudioTracksChanged {
@ -1050,7 +1076,9 @@ impl Room {
.remote_participants .remote_participants
.get_mut(&user_id) .get_mut(&user_id)
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; .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 { cx.emit(Event::RemoteAudioTracksChanged {
participant_id: participant.peer_id, participant_id: participant.peer_id,
}); });

View file

@ -29,6 +29,25 @@ pub struct Collaborator {
pub user_id: UserId, 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 { impl PartialOrd for User {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) 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")),
}
}
}

View file

@ -2,8 +2,8 @@ use crate::{
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT}, rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
tests::{channel_id, room_participants, RoomParticipants, TestClient, TestServer}, tests::{channel_id, room_participants, RoomParticipants, TestClient, TestServer},
}; };
use call::{room, ActiveCall, ParticipantLocation, Room}; use call::{room, ActiveCall, Room};
use client::{User, RECEIVE_TIMEOUT}; use client::{ParticipantLocation, User, RECEIVE_TIMEOUT};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions}; use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions};
use futures::StreamExt as _; use futures::StreamExt as _;
@ -248,12 +248,9 @@ async fn test_basic_calls(
assert_eq!(participant_id, client_a.peer_id().unwrap()); assert_eq!(participant_id, client_a.peer_id().unwrap());
room_b.read_with(cx_b, |room, _| { room_b.read_with(cx_b, |room, _| {
assert_eq!( assert!(room
room.remote_participants()[&client_a.user_id().unwrap()] .video_track_for_participant(client_a.user_id().unwrap())
.video_tracks .is_some());
.len(),
1
);
}); });
} else { } else {
panic!("unexpected event") panic!("unexpected event")
@ -266,12 +263,9 @@ async fn test_basic_calls(
assert_eq!(participant_id, client_a.peer_id().unwrap()); assert_eq!(participant_id, client_a.peer_id().unwrap());
room_c.read_with(cx_c, |room, _| { room_c.read_with(cx_c, |room, _| {
assert_eq!( assert!(room
room.remote_participants()[&client_a.user_id().unwrap()] .video_track_for_participant(client_a.user_id().unwrap())
.video_tracks .is_some());
.len(),
1
);
}); });
} else { } else {
panic!("unexpected event") panic!("unexpected event")
@ -5710,15 +5704,9 @@ async fn test_join_call_after_screen_was_shared(
); );
// Ensure User B sees User A's screenshare. // Ensure User B sees User A's screenshare.
room_b.read_with(cx_b, |room, _| { room_b.read_with(cx_b, |room, _| {
assert_eq!( assert!(room
room.remote_participants() .video_track_for_participant(client_a.user_id().unwrap())
.get(&client_a.user_id().unwrap()) .is_some());
.unwrap()
.video_tracks
.len(),
1
);
}); });
} }

View file

@ -466,6 +466,9 @@ impl CollabPanel {
for mat in matches { for mat in matches {
let user_id = mat.candidate_id as u64; let user_id = mat.candidate_id as u64;
let participant = &room.remote_participants()[&user_id]; 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 { self.entries.push(ListEntry::CallParticipant {
user: participant.user.clone(), user: participant.user.clone(),
peer_id: Some(participant.peer_id), peer_id: Some(participant.peer_id),
@ -477,11 +480,10 @@ impl CollabPanel {
project_id: project.id, project_id: project.id,
worktree_root_names: project.worktree_root_names.clone(), worktree_root_names: project.worktree_root_names.clone(),
host_user_id: participant.user.id, host_user_id: participant.user.id,
is_last: projects.peek().is_none() is_last: projects.peek().is_none() && !has_shared_screen,
&& participant.video_tracks.is_empty(),
}); });
} }
if !participant.video_tracks.is_empty() { if has_shared_screen {
self.entries.push(ListEntry::ParticipantScreen { self.entries.push(ListEntry::ParticipantScreen {
peer_id: Some(participant.peer_id), peer_id: Some(participant.peer_id),
is_last: true, is_last: true,

View file

@ -1,7 +1,7 @@
use crate::face_pile::FacePile; use crate::face_pile::FacePile;
use auto_update::AutoUpdateStatus; use auto_update::AutoUpdateStatus;
use call::{ActiveCall, ParticipantLocation, Room}; use call::{ActiveCall, Room};
use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use client::{proto::PeerId, Client, ParticipantIndex, ParticipantLocation, User, UserStore};
use gpui::{ use gpui::{
actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla, actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla,
InteractiveElement, IntoElement, Model, ParentElement, Path, Render, InteractiveElement, IntoElement, Model, ParentElement, Path, Render,

View file

@ -1,6 +1,7 @@
use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace}; use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation}; use call::ActiveCall;
use client::ParticipantLocation;
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{
point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View, point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,

View file

@ -3,7 +3,7 @@ use crate::{
ItemNavHistory, WorkspaceId, ItemNavHistory, WorkspaceId,
}; };
use anyhow::Result; use anyhow::Result;
use call::participant::{Frame, RemoteVideoTrack}; use call::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User}; use client::{proto::PeerId, User};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{

View file

@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result};
use call::ActiveCall; use call::ActiveCall;
use client::{ use client::{
proto::{self, PeerId}, proto::{self, PeerId},
Client, Status, TypedEnvelope, UserStore, Client, ParticipantLocation, Status, TypedEnvelope, UserStore,
}; };
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
@ -2398,9 +2398,9 @@ impl Workspace {
let project = self.project.read(cx); let project = self.project.read(cx);
let other_project_id = match remote_participant.location { let other_project_id = match remote_participant.location {
call::ParticipantLocation::External => None, ParticipantLocation::External => None,
call::ParticipantLocation::UnsharedProject => None, ParticipantLocation::UnsharedProject => None,
call::ParticipantLocation::SharedProject { project_id } => { ParticipantLocation::SharedProject { project_id } => {
if Some(project_id) == project.remote_id() { if Some(project_id) == project.remote_id() {
None None
} else { } else {
@ -2789,15 +2789,15 @@ impl Workspace {
let leader_in_this_app; let leader_in_this_app;
let leader_in_this_project; let leader_in_this_project;
match participant.location { match participant.location {
call::ParticipantLocation::SharedProject { project_id } => { ParticipantLocation::SharedProject { project_id } => {
leader_in_this_app = true; leader_in_this_app = true;
leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); 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_app = true;
leader_in_this_project = false; leader_in_this_project = false;
} }
call::ParticipantLocation::External => { ParticipantLocation::External => {
leader_in_this_app = false; leader_in_this_app = false;
leader_in_this_project = false; leader_in_this_project = false;
} }
@ -2848,7 +2848,7 @@ impl Workspace {
let call = self.active_call()?; let call = self.active_call()?;
let room = call.read(cx).room()?.read(cx); let room = call.read(cx).room()?.read(cx);
let participant = room.remote_participant_for_peer_id(peer_id)?; 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(); let user = participant.user.clone();
for item in pane.read(cx).items_of_type::<SharedScreen>() { for item in pane.read(cx).items_of_type::<SharedScreen>() {