Cargo.lock 🔗
@@ -1052,6 +1052,10 @@ dependencies = [
"media",
"postage",
"project",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
"settings",
"util",
]
Mikayla Maki created
This adds a setting to mute mics by default.
fixes https://github.com/zed-industries/community/issues/1769
Release notes:
- Fixed a bug with gutter spacing on files that end on a new significant
digit
- Added a setting for muting on join, and set it to true by default.
Cargo.lock | 4
assets/settings/default.json | 5
crates/call/Cargo.toml | 4
crates/call/src/call.rs | 4
crates/call/src/call_settings.rs | 27
crates/call/src/room.rs | 41
crates/collab_ui/src/collab_titlebar_item.rs | 8
crates/collab_ui/src/collab_ui.rs | 16
crates/editor/src/element.rs | 2
crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift | 46
crates/live_kit_client/examples/test_app.rs | 2
crates/live_kit_client/src/prod.rs | 32
crates/live_kit_client/src/test.rs | 17
13 files changed, 157 insertions(+), 51 deletions(-)
@@ -1052,6 +1052,10 @@ dependencies = [
"media",
"postage",
"project",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
"settings",
"util",
]
@@ -66,6 +66,11 @@
// 3. Draw all invisible symbols:
// "all"
"show_whitespaces": "selection",
+ // Settings related to calls in Zed
+ "calls": {
+ // Join calls with the microphone muted by default
+ "mute_on_join": true
+ },
// Scrollbar related settings
"scrollbar": {
// When to show the scrollbar in the editor.
@@ -36,6 +36,10 @@ anyhow.workspace = true
async-broadcast = "0.4"
futures.workspace = true
postage.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+serde_derive.workspace = true
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
@@ -1,9 +1,11 @@
+pub mod call_settings;
pub mod participant;
pub mod room;
use std::sync::Arc;
use anyhow::{anyhow, Result};
+use call_settings::CallSettings;
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
use collections::HashSet;
use futures::{future::Shared, FutureExt};
@@ -19,6 +21,8 @@ pub use participant::ParticipantLocation;
pub use room::Room;
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
+ settings::register::<CallSettings>(cx);
+
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
cx.set_global(active_call);
}
@@ -0,0 +1,27 @@
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+use settings::Setting;
+
+#[derive(Deserialize, Debug)]
+pub struct CallSettings {
+ pub mute_on_join: bool,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+pub struct CallSettingsContent {
+ pub mute_on_join: Option<bool>,
+}
+
+impl Setting for CallSettings {
+ const KEY: Option<&'static str> = Some("calls");
+
+ type FileContent = CallSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &gpui::AppContext,
+ ) -> anyhow::Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
@@ -1,4 +1,5 @@
use crate::{
+ call_settings::CallSettings,
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
IncomingCall,
};
@@ -19,7 +20,7 @@ use live_kit_client::{
};
use postage::stream::Stream;
use project::Project;
-use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
+use std::{future::Future, mem, panic::Location, pin::Pin, sync::Arc, time::Duration};
use util::{post_inc, ResultExt, TryFutureExt};
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -153,8 +154,10 @@ impl Room {
cx.spawn(|this, mut cx| async move {
connect.await?;
- this.update(&mut cx, |this, cx| this.share_microphone(cx))
- .await?;
+ if !cx.read(|cx| settings::get::<CallSettings>(cx).mute_on_join) {
+ this.update(&mut cx, |this, cx| this.share_microphone(cx))
+ .await?;
+ }
anyhow::Ok(())
})
@@ -656,7 +659,7 @@ impl Room {
peer_id,
projects: participant.projects,
location,
- muted: false,
+ muted: true,
speaking: false,
video_tracks: Default::default(),
audio_tracks: Default::default(),
@@ -670,6 +673,10 @@ impl Room {
live_kit.room.remote_video_tracks(&user.id.to_string());
let audio_tracks =
live_kit.room.remote_audio_tracks(&user.id.to_string());
+ let publications = live_kit
+ .room
+ .remote_audio_track_publications(&user.id.to_string());
+
for track in video_tracks {
this.remote_video_track_updated(
RemoteVideoTrackUpdate::Subscribed(track),
@@ -677,9 +684,15 @@ impl Room {
)
.log_err();
}
- for track in audio_tracks {
+
+ for (track, publication) in
+ audio_tracks.iter().zip(publications.iter())
+ {
this.remote_audio_track_updated(
- RemoteAudioTrackUpdate::Subscribed(track),
+ RemoteAudioTrackUpdate::Subscribed(
+ track.clone(),
+ publication.clone(),
+ ),
cx,
)
.log_err();
@@ -819,8 +832,8 @@ impl Room {
cx.notify();
}
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
+ let mut found = false;
for participant in &mut self.remote_participants.values_mut() {
- let mut found = false;
for track in participant.audio_tracks.values() {
if track.sid() == track_id {
found = true;
@@ -832,16 +845,20 @@ impl Room {
break;
}
}
+
cx.notify();
}
- RemoteAudioTrackUpdate::Subscribed(track) => {
+ RemoteAudioTrackUpdate::Subscribed(track, publication) => {
let user_id = track.publisher_id().parse()?;
let track_id = track.sid().to_string();
let participant = self
.remote_participants
.get_mut(&user_id)
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
+
participant.audio_tracks.insert(track_id.clone(), track);
+ participant.muted = publication.is_muted();
+
cx.emit(Event::RemoteAudioTracksChanged {
participant_id: participant.peer_id,
});
@@ -1053,7 +1070,7 @@ impl Room {
self.live_kit
.as_ref()
.and_then(|live_kit| match &live_kit.microphone_track {
- LocalTrack::None => None,
+ LocalTrack::None => Some(true),
LocalTrack::Pending { muted, .. } => Some(*muted),
LocalTrack::Published { muted, .. } => Some(*muted),
})
@@ -1070,7 +1087,9 @@ impl Room {
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
}
+ #[track_caller]
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+ dbg!(Location::caller());
if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline")));
} else if self.is_sharing_mic() {
@@ -1244,6 +1263,10 @@ impl Room {
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
let should_mute = !self.is_muted();
if let Some(live_kit) = self.live_kit.as_mut() {
+ if matches!(live_kit.microphone_track, LocalTrack::None) {
+ return Ok(self.share_microphone(cx));
+ }
+
let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?;
live_kit.muted_by_user = should_mute;
@@ -652,10 +652,10 @@ impl CollabTitlebarItem {
let is_muted = room.read(cx).is_muted();
if is_muted {
icon = "icons/radix/mic-mute.svg";
- tooltip = "Unmute microphone\nRight click for options";
+ tooltip = "Unmute microphone";
} else {
icon = "icons/radix/mic.svg";
- tooltip = "Mute microphone\nRight click for options";
+ tooltip = "Mute microphone";
}
let titlebar = &theme.titlebar;
@@ -705,10 +705,10 @@ impl CollabTitlebarItem {
let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
if is_deafened {
icon = "icons/radix/speaker-off.svg";
- tooltip = "Unmute speakers\nRight click for options";
+ tooltip = "Unmute speakers";
} else {
icon = "icons/radix/speaker-loud.svg";
- tooltip = "Mute speakers\nRight click for options";
+ tooltip = "Mute speakers";
}
let titlebar = &theme.titlebar;
@@ -18,13 +18,7 @@ use workspace::AppState;
actions!(
collab,
- [
- ToggleScreenSharing,
- ToggleMute,
- ToggleDeafen,
- LeaveCall,
- ShareMicrophone
- ]
+ [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
);
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -40,7 +34,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
cx.add_global_action(toggle_screen_sharing);
cx.add_global_action(toggle_mute);
cx.add_global_action(toggle_deafen);
- cx.add_global_action(share_microphone);
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -85,10 +78,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
.log_err();
}
}
-
-pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) {
- if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
- room.update(cx, Room::share_microphone)
- .detach_and_log_err(cx)
- }
-}
@@ -1311,7 +1311,7 @@ impl EditorElement {
}
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
- let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
+ let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
let style = &self.style;
cx.text_layout_cache()
@@ -6,7 +6,7 @@ import ScreenCaptureKit
class LKRoomDelegate: RoomDelegate {
var data: UnsafeRawPointer
var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void
- var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void
+ var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void
var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void
var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void
var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void
@@ -16,7 +16,7 @@ class LKRoomDelegate: RoomDelegate {
init(
data: UnsafeRawPointer,
onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
- onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
+ onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void,
onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void,
@@ -43,7 +43,7 @@ class LKRoomDelegate: RoomDelegate {
if track.kind == .video {
self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque())
} else if track.kind == .audio {
- self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque())
+ self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque())
}
}
@@ -52,12 +52,12 @@ class LKRoomDelegate: RoomDelegate {
self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted)
}
}
-
+
func room(_ room: Room, didUpdate speakers: [Participant]) {
guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return }
self.onActiveSpeakersChanged(self.data, speaker_ids)
}
-
+
func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
if track.kind == .video {
self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString)
@@ -104,7 +104,7 @@ class LKVideoRenderer: NSObject, VideoRenderer {
public func LKRoomDelegateCreate(
data: UnsafeRawPointer,
onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void,
- onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void,
+ onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void,
onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void,
onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void,
onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void,
@@ -180,39 +180,39 @@ public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawP
@_cdecl("LKRoomAudioTracksForRemoteParticipant")
public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
+
for (_, participant) in room.remoteParticipants {
if participant.identity == participantId as String {
return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray?
}
}
-
+
return nil;
}
@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant")
public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
+
for (_, participant) in room.remoteParticipants {
if participant.identity == participantId as String {
return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray?
}
}
-
+
return nil;
}
@_cdecl("LKRoomVideoTracksForRemoteParticipant")
public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue()
-
+
for (_, participant) in room.remoteParticipants {
if participant.identity == participantId as String {
return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray?
}
}
-
+
return nil;
}
@@ -222,7 +222,7 @@ public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer {
echoCancellation: true,
noiseSuppression: true
))
-
+
return Unmanaged.passRetained(track).toOpaque()
}
@@ -276,7 +276,7 @@ public func LKLocalTrackPublicationSetMute(
callback_data: UnsafeRawPointer
) {
let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
-
+
if muted {
publication.mute().then {
on_complete(callback_data, nil)
@@ -307,3 +307,21 @@ public func LKRemoteTrackPublicationSetEnabled(
on_complete(callback_data, error.localizedDescription as CFString)
}
}
+
+@_cdecl("LKRemoteTrackPublicationIsMuted")
+public func LKRemoteTrackPublicationIsMuted(
+ publication: UnsafeRawPointer
+) -> Bool {
+ let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+
+ return publication.muted
+}
+
+@_cdecl("LKRemoteTrackPublicationGetSid")
+public func LKRemoteTrackPublicationGetSid(
+ publication: UnsafeRawPointer
+) -> CFString {
+ let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+
+ return publication.sid as CFString
+}
@@ -63,7 +63,7 @@ fn main() {
let audio_track = LocalAudioTrack::create();
let audio_track_publication = room_a.publish_audio_track(&audio_track).await.unwrap();
- if let RemoteAudioTrackUpdate::Subscribed(track) =
+ if let RemoteAudioTrackUpdate::Subscribed(track, _) =
audio_track_updates.next().await.unwrap()
{
let remote_tracks = room_b.remote_audio_tracks("test-participant-1");
@@ -26,6 +26,7 @@ extern "C" {
publisher_id: CFStringRef,
track_id: CFStringRef,
remote_track: *const c_void,
+ remote_publication: *const c_void,
),
on_did_unsubscribe_from_remote_audio_track: extern "C" fn(
callback_data: *mut c_void,
@@ -125,6 +126,9 @@ extern "C" {
on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
callback_data: *mut c_void,
);
+
+ fn LKRemoteTrackPublicationIsMuted(publication: *const c_void) -> bool;
+ fn LKRemoteTrackPublicationGetSid(publication: *const c_void) -> CFStringRef;
}
pub type Sid = String;
@@ -372,11 +376,19 @@ impl Room {
rx
}
- fn did_subscribe_to_remote_audio_track(&self, track: RemoteAudioTrack) {
+ fn did_subscribe_to_remote_audio_track(
+ &self,
+ track: RemoteAudioTrack,
+ publication: RemoteTrackPublication,
+ ) {
let track = Arc::new(track);
+ let publication = Arc::new(publication);
self.remote_audio_track_subscribers.lock().retain(|tx| {
- tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(track.clone()))
- .is_ok()
+ tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed(
+ track.clone(),
+ publication.clone(),
+ ))
+ .is_ok()
});
}
@@ -501,13 +513,15 @@ impl RoomDelegate {
publisher_id: CFStringRef,
track_id: CFStringRef,
track: *const c_void,
+ publication: *const c_void,
) {
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() };
let track = RemoteAudioTrack::new(track, track_id, publisher_id);
+ let publication = RemoteTrackPublication::new(publication);
if let Some(room) = room.upgrade() {
- room.did_subscribe_to_remote_audio_track(track);
+ room.did_subscribe_to_remote_audio_track(track, publication);
}
let _ = Weak::into_raw(room);
}
@@ -682,6 +696,14 @@ impl RemoteTrackPublication {
Self(native_track_publication)
}
+ pub fn sid(&self) -> String {
+ unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() }
+ }
+
+ pub fn is_muted(&self) -> bool {
+ unsafe { LKRemoteTrackPublicationIsMuted(self.0) }
+ }
+
pub fn set_enabled(&self, enabled: bool) -> impl Future<Output = Result<()>> {
let (tx, rx) = futures::channel::oneshot::channel();
@@ -832,7 +854,7 @@ pub enum RemoteVideoTrackUpdate {
pub enum RemoteAudioTrackUpdate {
ActiveSpeakersChanged { speakers: Vec<Sid> },
MuteChanged { track_id: Sid, muted: bool },
- Subscribed(Arc<RemoteAudioTrack>),
+ Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
Unsubscribed { publisher_id: Sid, track_id: Sid },
}
@@ -216,6 +216,8 @@ impl TestServer {
publisher_id: identity.clone(),
});
+ let publication = Arc::new(RemoteTrackPublication);
+
room.audio_tracks.push(track.clone());
for (id, client_room) in &room.client_rooms {
@@ -225,7 +227,10 @@ impl TestServer {
.lock()
.audio_track_updates
.0
- .try_broadcast(RemoteAudioTrackUpdate::Subscribed(track.clone()))
+ .try_broadcast(RemoteAudioTrackUpdate::Subscribed(
+ track.clone(),
+ publication.clone(),
+ ))
.unwrap();
}
}
@@ -501,6 +506,14 @@ impl RemoteTrackPublication {
pub fn set_enabled(&self, _enabled: bool) -> impl Future<Output = Result<()>> {
async { Ok(()) }
}
+
+ pub fn is_muted(&self) -> bool {
+ false
+ }
+
+ pub fn sid(&self) -> String {
+ "".to_string()
+ }
}
#[derive(Clone)]
@@ -579,7 +592,7 @@ pub enum RemoteVideoTrackUpdate {
pub enum RemoteAudioTrackUpdate {
ActiveSpeakersChanged { speakers: Vec<Sid> },
MuteChanged { track_id: Sid, muted: bool },
- Subscribed(Arc<RemoteAudioTrack>),
+ Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
Unsubscribed { publisher_id: Sid, track_id: Sid },
}