Add mute toggling controls

Mikayla Maki created

Change summary

crates/call/src/room.rs                                                        | 110 
crates/collab_ui/src/collab_ui.rs                                              |  15 
crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift |  44 
crates/live_kit_client/src/prod.rs                                             |  67 
crates/live_kit_client/src/test.rs                                             |  14 
5 files changed, 189 insertions(+), 61 deletions(-)

Detailed changes

crates/call/src/room.rs 🔗

@@ -154,8 +154,8 @@ impl Room {
 
             Some(LiveKitRoom {
                 room,
-                screen_track: Track::None,
-                microphone_track: Track::None,
+                screen_track: LocalTrack::None,
+                microphone_track: LocalTrack::None,
                 next_publish_id: 0,
                 _maintain_room,
                 _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks],
@@ -985,13 +985,13 @@ impl Room {
 
     pub fn is_screen_sharing(&self) -> bool {
         self.live_kit.as_ref().map_or(false, |live_kit| {
-            !matches!(live_kit.screen_track, Track::None)
+            !matches!(live_kit.screen_track, LocalTrack::None)
         })
     }
 
     pub fn is_sharing_mic(&self) -> bool {
         self.live_kit.as_ref().map_or(false, |live_kit| {
-            !matches!(live_kit.microphone_track, Track::None)
+            !matches!(live_kit.microphone_track, LocalTrack::None)
         })
     }
 
@@ -1004,7 +1004,10 @@ impl Room {
 
         let publish_id = if let Some(live_kit) = self.live_kit.as_mut() {
             let publish_id = post_inc(&mut live_kit.next_publish_id);
-            live_kit.microphone_track = Track::Pending { publish_id };
+            live_kit.microphone_track = LocalTrack::Pending {
+                publish_id,
+                muted: false,
+            };
             cx.notify();
             publish_id
         } else {
@@ -1034,13 +1037,14 @@ impl Room {
                         .as_mut()
                         .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
 
-                    let canceled = if let Track::Pending {
+                    let (canceled, muted) = if let LocalTrack::Pending {
                         publish_id: cur_publish_id,
+                        muted
                     } = &live_kit.microphone_track
                     {
-                        *cur_publish_id != publish_id
+                        (*cur_publish_id != publish_id, *muted)
                     } else {
-                        true
+                        (true, false)
                     };
 
                     match publication {
@@ -1048,7 +1052,13 @@ impl Room {
                             if canceled {
                                 live_kit.room.unpublish_track(publication);
                             } else {
-                                live_kit.microphone_track = Track::Published(publication);
+                                if muted {
+                                    cx.background().spawn(publication.mute()).detach();
+                                }
+                                live_kit.microphone_track = LocalTrack::Published {
+                                    track_publication: publication,
+                                    muted
+                                };
                                 cx.notify();
                             }
                             Ok(())
@@ -1057,7 +1067,7 @@ impl Room {
                             if canceled {
                                 Ok(())
                             } else {
-                                live_kit.microphone_track = Track::None;
+                                live_kit.microphone_track = LocalTrack::None;
                                 cx.notify();
                                 Err(error)
                             }
@@ -1076,7 +1086,10 @@ impl Room {
 
         let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
             let publish_id = post_inc(&mut live_kit.next_publish_id);
-            live_kit.screen_track = Track::Pending { publish_id };
+            live_kit.screen_track = LocalTrack::Pending {
+                publish_id,
+                muted: false,
+            };
             cx.notify();
             (live_kit.room.display_sources(), publish_id)
         } else {
@@ -1110,13 +1123,14 @@ impl Room {
                         .as_mut()
                         .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
 
-                    let canceled = if let Track::Pending {
+                    let (canceled, muted) = if let LocalTrack::Pending {
                         publish_id: cur_publish_id,
+                        muted,
                     } = &live_kit.screen_track
                     {
-                        *cur_publish_id != publish_id
+                        (*cur_publish_id != publish_id, *muted)
                     } else {
-                        true
+                        (true, false)
                     };
 
                     match publication {
@@ -1124,7 +1138,13 @@ impl Room {
                             if canceled {
                                 live_kit.room.unpublish_track(publication);
                             } else {
-                                live_kit.screen_track = Track::Published(publication);
+                                if muted {
+                                    cx.background().spawn(publication.mute()).detach();
+                                }
+                                live_kit.screen_track = LocalTrack::Published {
+                                    track_publication: publication,
+                                    muted,
+                                };
                                 cx.notify();
                             }
                             Ok(())
@@ -1133,7 +1153,7 @@ impl Room {
                             if canceled {
                                 Ok(())
                             } else {
-                                live_kit.screen_track = Track::None;
+                                live_kit.screen_track = LocalTrack::None;
                                 cx.notify();
                                 Err(error)
                             }
@@ -1143,13 +1163,33 @@ impl Room {
         })
     }
 
-    pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        // https://docs.livekit.io/client/publish/
-        // Should be acessible from local participant / publication
-        todo!();
+    pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
+        if let Some(live_kit) = self.live_kit.as_mut() {
+            match &mut live_kit.microphone_track {
+                LocalTrack::None => Err(anyhow!("microphone was not shared")),
+                LocalTrack::Pending { muted, .. } => {
+                    *muted = !*muted;
+                    Ok(Task::Ready(Some(Ok(()))))
+                }
+                LocalTrack::Published {
+                    track_publication,
+                    muted,
+                } => {
+                    *muted = !*muted;
+
+                    if *muted {
+                        Ok(cx.background().spawn(track_publication.mute()))
+                    } else {
+                        Ok(cx.background().spawn(track_publication.unmute()))
+                    }
+                }
+            }
+        } else {
+            Err(anyhow!("LiveKit not started"))
+        }
     }
 
-    pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+    pub fn toggle_deafen(&mut self, _cx: &mut ModelContext<Self>) -> Task<Result<()>> {
         // iterate through publications and mute (?????)
         todo!();
     }
@@ -1164,13 +1204,15 @@ impl Room {
             .as_mut()
             .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
         match mem::take(&mut live_kit.screen_track) {
-            Track::None => Err(anyhow!("screen was not shared")),
-            Track::Pending { .. } => {
+            LocalTrack::None => Err(anyhow!("screen was not shared")),
+            LocalTrack::Pending { .. } => {
                 cx.notify();
                 Ok(())
             }
-            Track::Published(track) => {
-                live_kit.room.unpublish_track(track);
+            LocalTrack::Published {
+                track_publication, ..
+            } => {
+                live_kit.room.unpublish_track(track_publication);
                 cx.notify();
                 Ok(())
             }
@@ -1189,20 +1231,26 @@ impl Room {
 
 struct LiveKitRoom {
     room: Arc<live_kit_client::Room>,
-    screen_track: Track,
-    microphone_track: Track,
+    screen_track: LocalTrack,
+    microphone_track: LocalTrack,
     next_publish_id: usize,
     _maintain_room: Task<()>,
     _maintain_tracks: [Task<()>; 2],
 }
 
-enum Track {
+enum LocalTrack {
     None,
-    Pending { publish_id: usize },
-    Published(LocalTrackPublication),
+    Pending {
+        publish_id: usize,
+        muted: bool,
+    },
+    Published {
+        track_publication: LocalTrackPublication,
+        muted: bool,
+    },
 }
 
-impl Default for Track {
+impl Default for LocalTrack {
     fn default() -> Self {
         Self::None
     }

crates/collab_ui/src/collab_ui.rs 🔗

@@ -9,9 +9,10 @@ mod notifications;
 mod project_shared_notification;
 mod sharing_status_indicator;
 
-use call::ActiveCall;
+use call::{ActiveCall, Room};
 pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
 use gpui::{actions, AppContext, Task};
+use util::ResultExt;
 use std::sync::Arc;
 use workspace::AppState;
 
@@ -46,20 +47,12 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
 
 pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
     if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-        let toggle_mut = room.update(cx, |room, cx| {
-            if room.is_sharing_mic() {
-                room.toggle_mute(cx)
-            } else {
-                room.share_mic(cx)
-            }
-        });
-        toggle_mut.detach_and_log_err(cx);
+        room.update(cx, Room::toggle_mute).map(Task::detach).log_err();
     }
 }
 
 pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
     if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-        let toggle_deafan = room.update(cx, |room, cx| room.toggle_deafen(cx));
-        toggle_deafan.detach_and_log_err(cx);
+        room.update(cx, Room::toggle_deafen).detach_and_log_err(cx);
     }
 }

crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift 🔗

@@ -201,19 +201,6 @@ public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer)
     return Unmanaged.passRetained(track).toOpaque()
 }
 
-@_cdecl("LKRemoteAudioTrackStart")
-public func LKRemoteAudioTrackStart(track: UnsafeRawPointer, onStart: @escaping @convention(c) (UnsafeRawPointer, Bool) -> Void, callbackData: UnsafeRawPointer) {
-    let track = Unmanaged<Track>.fromOpaque(track).takeUnretainedValue() as! RemoteAudioTrack
-
-    track.start().then { success in
-        onStart(callbackData, success)
-    }
-    .catch { _ in
-        onStart(callbackData, false)
-    }
-}
-
-
 @_cdecl("LKVideoRendererCreate")
 public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer {
     Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque()
@@ -247,3 +234,34 @@ public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @conven
         callback(data, nil, error.localizedDescription as CFString)
     }
 }
+
+@_cdecl("LKLocalTrackPublicationMute")
+public func LKLocalTrackPublicationMute(
+    publication: UnsafeRawPointer,
+    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
+    callback_data: UnsafeRawPointer
+) {
+    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+    
+    publication.mute().then {
+        on_complete(callback_data, nil)
+    }.catch { error in
+        on_complete(callback_data, error.localizedDescription as CFString)
+    }
+
+}
+
+@_cdecl("LKLocalTrackPublicationUnmute")
+public func LKLocalTrackPublicationUnmute(
+    publication: UnsafeRawPointer,
+    on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void,
+    callback_data: UnsafeRawPointer
+) {
+    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+    
+    publication.unmute().then {
+        on_complete(callback_data, nil)
+    }.catch { error in
+        on_complete(callback_data, error.localizedDescription as CFString)
+    }
+}

crates/live_kit_client/src/prod.rs 🔗

@@ -84,12 +84,6 @@ extern "C" {
     ) -> *const c_void;
 
     fn LKRemoteAudioTrackGetSid(track: *const c_void) -> CFStringRef;
-    // fn LKRemoteAudioTrackStart(
-    //     track: *const c_void,
-    //     callback: extern "C" fn(*mut c_void, bool),
-    //     callback_data: *mut c_void
-    // );
-
     fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
     fn LKRemoteVideoTrackGetSid(track: *const c_void) -> CFStringRef;
 
@@ -103,6 +97,17 @@ extern "C" {
     );
     fn LKCreateScreenShareTrackForDisplay(display: *const c_void) -> *const c_void;
     fn LKLocalAudioTrackCreateTrack() -> *const c_void;
+
+    fn LKLocalTrackPublicationMute(
+        publication: *const c_void,
+        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
+        callback_data: *mut c_void,
+    );
+    fn LKLocalTrackPublicationUnmute(
+        publication: *const c_void,
+        on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef),
+        callback_data: *mut c_void,
+    );
 }
 
 pub type Sid = String;
@@ -525,6 +530,56 @@ impl Drop for LocalVideoTrack {
 
 pub struct LocalTrackPublication(*const c_void);
 
+impl LocalTrackPublication {
+    pub fn mute(&self) -> impl Future<Output = Result<()>> {
+        let (tx, rx) = futures::channel::oneshot::channel();
+
+        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                tx.send(Ok(())).ok();
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                tx.send(Err(anyhow!(error))).ok();
+            }
+        }
+
+        unsafe {
+            LKLocalTrackPublicationMute(
+                self.0,
+                complete_callback,
+                Box::into_raw(Box::new(tx)) as *mut c_void,
+            )
+        }
+
+        async move { rx.await.unwrap() }
+    }
+
+    pub fn unmute(&self) -> impl Future<Output = Result<()>> {
+        let (tx, rx) = futures::channel::oneshot::channel();
+
+        extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                tx.send(Ok(())).ok();
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                tx.send(Err(anyhow!(error))).ok();
+            }
+        }
+
+        unsafe {
+            LKLocalTrackPublicationUnmute(
+                self.0,
+                complete_callback,
+                Box::into_raw(Box::new(tx)) as *mut c_void,
+            )
+        }
+
+        async move { rx.await.unwrap() }
+    }
+}
+
 impl Drop for LocalTrackPublication {
     fn drop(&mut self) {
         unsafe { CFRelease(self.0) }

crates/live_kit_client/src/test.rs 🔗

@@ -475,6 +475,20 @@ impl Drop for Room {
 
 pub struct LocalTrackPublication;
 
+impl LocalTrackPublication {
+    pub fn mute(&self) -> impl Future<Output = Result<()>> {
+        async {
+            Ok(())
+        }
+    }
+
+    pub fn unmute(&self) -> impl Future<Output = Result<()>> {
+        async {
+            Ok(())
+        }
+    }
+}
+
 #[derive(Clone)]
 pub struct LocalVideoTrack {
     frames_rx: async_broadcast::Receiver<Frame>,