diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index aa0cb1f20e..b8da95bfb5 100644 --- a/crates/call/src/room.rs +++ b/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) -> Task> { - // https://docs.livekit.io/client/publish/ - // Should be acessible from local participant / publication - todo!(); + pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> 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) -> Task> { + pub fn toggle_deafen(&mut self, _cx: &mut ModelContext) -> Task> { // 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, - 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 } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index d6e62d146f..9de8cbf9dd 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/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); } } diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 40c1319e8f..d28dc828f1 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/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.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.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.fromOpaque(publication).takeUnretainedValue() + + publication.unmute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } +} diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 6ae493d306..46516d49be 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/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> { + 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>) }; + 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> { + 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>) }; + 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) } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 232fc7cf4b..21211ce473 100644 --- a/crates/live_kit_client/src/test.rs +++ b/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> { + async { + Ok(()) + } + } + + pub fn unmute(&self) -> impl Future> { + async { + Ok(()) + } + } +} + #[derive(Clone)] pub struct LocalVideoTrack { frames_rx: async_broadcast::Receiver,