From dd39023a4efc10fdfde6af61acc505d5f809d3cc Mon Sep 17 00:00:00 2001 From: sdwoodbury Date: Wed, 1 Nov 2023 10:43:03 -0400 Subject: [PATCH] fix(settings): fix audio (#1421) Co-authored-by: Darius Clark Co-authored-by: Phill Wisniewski <93608357+phillsatellite@users.noreply.github.com> --- common/locales/en-US/main.ftl | 1 + common/src/state/action.rs | 2 + common/src/state/configuration.rs | 42 +-- .../manager/commands/blink_commands.rs | 58 ++-- kit/src/elements/range/mod.rs | 3 + ui/src/components/settings/sub_pages/audio.rs | 290 ++++++++++-------- 6 files changed, 190 insertions(+), 206 deletions(-) diff --git a/common/locales/en-US/main.ftl b/common/locales/en-US/main.ftl index 06cbddb4905..1b018f93951 100644 --- a/common/locales/en-US/main.ftl +++ b/common/locales/en-US/main.ftl @@ -244,6 +244,7 @@ settings-audio = Audio & Sound Settings .media-sounds-description = When enabled, media related events such as toggling microphone or headphones and other real time events, will play sounds. .message-sounds = Message Sounds .message-sounds-description = When enabled you will hear a notification when a new message is received. + .failed = Failed to update settings settings-files = Files Settings .local-sync = Local Sync diff --git a/common/src/state/action.rs b/common/src/state/action.rs index ee5bde8e5d9..2b59509c8d4 100644 --- a/common/src/state/action.rs +++ b/common/src/state/action.rs @@ -226,4 +226,6 @@ pub enum ConfigAction { SetSettingsNotificationsEnabled(bool), #[display(fmt = "SetAutoEnableExtensions {_0}")] SetAutoEnableExtensions(bool), + #[display(fmt = "SetEchoCancellation {_0}")] + SetEchoCancellation(bool), } diff --git a/common/src/state/configuration.rs b/common/src/state/configuration.rs index dbb8290681d..b9a3aebe974 100644 --- a/common/src/state/configuration.rs +++ b/common/src/state/configuration.rs @@ -57,22 +57,17 @@ pub struct Privacy { #[derive(Debug, Deserialize, Serialize, Copy, Clone, Eq, PartialEq)] pub struct AudioVideo { - #[serde(default)] - pub noise_suppression: bool, - #[serde(default)] + pub echo_cancellation: bool, pub call_timer: bool, - #[serde(default)] pub interface_sounds: bool, - #[serde(default = "bool_true")] pub message_sounds: bool, - #[serde(default = "bool_true")] pub media_sounds: bool, } impl Default for AudioVideo { fn default() -> Self { Self { - noise_suppression: false, + echo_cancellation: true, call_timer: false, interface_sounds: false, message_sounds: true, @@ -105,16 +100,10 @@ fn bool_true() -> bool { // This is a good place to start. #[derive(Debug, Deserialize, Serialize, Copy, Clone, Eq, PartialEq)] pub struct Notifications { - #[serde(default = "bool_true")] pub enabled: bool, - #[serde(default)] pub show_app_icon: bool, - #[serde(default = "bool_true")] pub friends_notifications: bool, - #[serde(default = "bool_true")] pub messages_notifications: bool, - // By default we leave this one off. - #[serde(default)] pub settings_notifications: bool, } @@ -125,6 +114,7 @@ impl Default for Notifications { show_app_icon: false, friends_notifications: true, messages_notifications: true, + // By default we leave this one off. settings_notifications: false, } } @@ -177,6 +167,7 @@ impl Configuration { ConfigAction::SetAutoEnableExtensions(flag) => { self.extensions.enable_automatically = flag } + ConfigAction::SetEchoCancellation(flag) => self.audiovideo.echo_cancellation = flag, } if self.audiovideo != old_audiovideo { @@ -193,28 +184,3 @@ impl Configuration { } } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn deserialize_notifications_config() { - let empty_str = String::from("{}"); - let serde_notifications: Notifications = - serde_json::from_str(&empty_str).expect("failed to deserialize empty string"); - let default_notifications = Notifications::default(); - - assert_eq!(default_notifications, serde_notifications); - } - - #[test] - fn deserialize_audiovideo_config() { - let empty_str = String::from("{}"); - let serde_audiovideo: AudioVideo = - serde_json::from_str(&empty_str).expect("failed to deserialize empty string"); - let default_audiovideo = AudioVideo::default(); - - assert_eq!(default_audiovideo, serde_audiovideo); - } -} diff --git a/common/src/warp_runner/manager/commands/blink_commands.rs b/common/src/warp_runner/manager/commands/blink_commands.rs index 7e53666905a..ee5dd859aae 100644 --- a/common/src/warp_runner/manager/commands/blink_commands.rs +++ b/common/src/warp_runner/manager/commands/blink_commands.rs @@ -1,15 +1,10 @@ use derive_more::Display; use futures::channel::oneshot; use uuid::Uuid; -use warp::crypto::DID; +use warp::{blink::AudioDeviceConfig, crypto::DID}; use crate::warp_runner::Calling; -pub struct Devices { - pub available_devices: Vec, - pub selected: Option, -} - #[derive(Display)] pub enum BlinkCmd { #[display(fmt = "OfferCall")] @@ -46,19 +41,11 @@ pub enum BlinkCmd { volume: f32, rsp: oneshot::Sender>, }, - #[display(fmt = "GetAllMicrophones")] - GetAllMicrophones { - rsp: oneshot::Sender>, - }, #[display(fmt = "SetMicrophone")] SetMicrophone { device_name: String, rsp: oneshot::Sender>, }, - #[display(fmt = "GetAllSpeakers")] - GetAllSpeakers { - rsp: oneshot::Sender>, - }, #[display(fmt = "SetSpeaker")] SetSpeaker { device_name: String, @@ -73,6 +60,15 @@ pub enum BlinkCmd { StopRecording { rsp: oneshot::Sender>, }, + #[display(fmt = "GetAudioDeviceConfig")] + GetAudioDeviceConfig { + rsp: oneshot::Sender>, + }, + #[display(fmt = "SetEchoCancellation")] + SetEchoCancellation { + flag: bool, + rsp: oneshot::Sender>, + }, } pub async fn handle_blink_cmd(cmd: BlinkCmd, blink: &mut Calling) { @@ -102,35 +98,11 @@ pub async fn handle_blink_cmd(cmd: BlinkCmd, blink: &mut Calling) { BlinkCmd::AdjustVolume { user, volume, rsp } => { let _ = rsp.send(blink.set_peer_audio_gain(user, volume).await); } - BlinkCmd::GetAllMicrophones { rsp } => { - let audio_config = blink.get_audio_device_config().await; - let selected = audio_config.microphone_device_name(); - let result = audio_config - .get_available_microphones() - .map(|available_devices| Devices { - available_devices, - selected, - }) - .map_err(warp::error::Error::from); - let _ = rsp.send(result); - } BlinkCmd::SetMicrophone { device_name, rsp } => { let mut audio_config = blink.get_audio_device_config().await; audio_config.set_microphone(&device_name); let _ = rsp.send(blink.set_audio_device_config(audio_config).await); } - BlinkCmd::GetAllSpeakers { rsp } => { - let audio_config = blink.get_audio_device_config().await; - let selected = audio_config.speaker_device_name(); - let result = audio_config - .get_available_speakers() - .map(|available_devices| Devices { - available_devices, - selected, - }) - .map_err(warp::error::Error::from); - let _ = rsp.send(result); - } BlinkCmd::SetSpeaker { device_name, rsp } => { let mut audio_config = blink.get_audio_device_config().await; audio_config.set_speaker(&device_name); @@ -142,5 +114,15 @@ pub async fn handle_blink_cmd(cmd: BlinkCmd, blink: &mut Calling) { BlinkCmd::StopRecording { rsp } => { let _ = rsp.send(blink.stop_recording().await); } + BlinkCmd::GetAudioDeviceConfig { rsp } => { + let _ = rsp.send(blink.get_audio_device_config().await); + } + BlinkCmd::SetEchoCancellation { flag, rsp } => { + if flag { + let _ = rsp.send(blink.enable_automute()); + } else { + let _ = rsp.send(blink.disable_automute()); + } + } } } diff --git a/kit/src/elements/range/mod.rs b/kit/src/elements/range/mod.rs index 106fc85f017..7df92c7c9aa 100644 --- a/kit/src/elements/range/mod.rs +++ b/kit/src/elements/range/mod.rs @@ -18,6 +18,7 @@ pub struct Props<'a> { icon_left: Option, icon_right: Option, aria_label: Option, + disabled: Option, } #[allow(non_snake_case)] @@ -42,6 +43,7 @@ pub fn Range<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { rsx!(Button { icon: Icon::Minus, appearance: Appearance::PrimaryAlternative, + disabled: cx.props.disabled.unwrap_or_default(), aria_label: "decrease_range_value_button".into(), onpress: move |_| { if internal_state.get() > &cx.props.min { @@ -69,6 +71,7 @@ pub fn Range<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { aria_label: "range-input", step: "{step}", value: "{internal_state}", + disabled: cx.props.disabled.unwrap_or_default(), oninput: move |event| { internal_state.set(event.value.parse().unwrap_or_default()); cx.props.onchange.call(event.value.parse().unwrap_or_default()); diff --git a/ui/src/components/settings/sub_pages/audio.rs b/ui/src/components/settings/sub_pages/audio.rs index 15c58daf9ef..87015117e9e 100644 --- a/ui/src/components/settings/sub_pages/audio.rs +++ b/ui/src/components/settings/sub_pages/audio.rs @@ -1,28 +1,26 @@ -use std::vec; - -use common::icons::outline::Shape; use common::language::get_local_text; +use common::state::ToastNotification; use common::warp_runner::{BlinkCmd, WarpCmd}; use dioxus::prelude::*; use futures::{channel::oneshot, StreamExt}; -use kit::elements::radio_list::RadioList; -use kit::elements::range::Range; + use kit::elements::select::Select; use kit::elements::switch::Switch; use warp::logging::tracing::log; -use crate::components::settings::{SettingSection, SettingSectionSimple}; +use crate::components::settings::SettingSection; use common::state::{action::ConfigAction, Action, State}; use common::{sounds, WARP_CMD_CH}; -pub const VOL_MIN: f32 = 0.0; -pub const VOL_MAX: f32 = 200.0; +// pub const VOL_MIN: f32 = 0.0; +// pub const VOL_MAX: f32 = 200.0; enum AudioCmd { FetchOutputDevices, SetOutputDevice(String), FetchInputDevices, SetInputDevice(String), + SetEchoCancellation(bool), } #[allow(non_snake_case)] @@ -36,89 +34,113 @@ pub fn AudioSettings(cx: Scope) -> Element { to_owned![state, input_devices, output_devices]; async move { let warp_cmd_tx = WARP_CMD_CH.tx.clone(); - while let Some(cmd) = rx.next().await { - match cmd { - AudioCmd::FetchInputDevices => { - let (tx, rx) = oneshot::channel(); - if let Err(_e) = warp_cmd_tx - .send(WarpCmd::Blink(BlinkCmd::GetAllMicrophones { rsp: tx })) - { - log::error!("failed to send blink command"); - continue; - } - let res = rx.await.expect("warp runner failed to get input devices"); - match res { - Ok(res) => { - state.write_silent().settings.input_device = res.selected; - *input_devices.write() = res.available_devices; - } - Err(e) => { - log::error!("could not get input devices: {e}"); - } - } - } - AudioCmd::FetchOutputDevices => { - let (tx, rx) = oneshot::channel(); - if let Err(_e) = - warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::GetAllSpeakers { rsp: tx })) - { - log::error!("failed to send blink command"); - continue; - } + 'GET_AUDIO_CONFIG: loop { + let audio_config = { + let (tx, rx) = oneshot::channel(); + warp_cmd_tx + .send(WarpCmd::Blink(BlinkCmd::GetAudioDeviceConfig { rsp: tx })) + .expect("failed to send command"); + rx.await.expect("warp runner failed to get audio config") + }; - let res = rx.await.expect("warp runner failed to get output devices"); - match res { - Ok(res) => { - state.write_silent().settings.output_device = res.selected; - *output_devices.write() = res.available_devices; + while let Some(cmd) = rx.next().await { + match cmd { + AudioCmd::SetEchoCancellation(flag) => { + let (tx, rx) = oneshot::channel(); + if let Err(_e) = + warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::SetEchoCancellation { + flag, + rsp: tx, + })) + { + log::error!("failed to send blink command"); + continue; } - Err(e) => { - log::error!("could not get output devices: {e}"); + + match rx.await { + Ok(_) => {} + Err(e) => { + log::error!("warp_runner failed to set echo cancellation: {e}"); + state.write().mutate(Action::Config( + ConfigAction::SetEchoCancellation(!flag), + )); + state + .write() + .mutate(common::state::Action::AddToastNotification( + ToastNotification::init( + get_local_text("warning-messages.error"), + get_local_text("settings-audio.failed"), + Some( + common::icons::outline::Shape::ExclamationTriangle, + ), + 2, + ), + )); + } } } - } - AudioCmd::SetInputDevice(device_name) => { - let device = device_name.clone(); - let (tx, rx) = oneshot::channel(); - if let Err(_e) = warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::SetMicrophone { - device_name, - rsp: tx, - })) { - log::error!("failed to send blink command"); - continue; + AudioCmd::FetchInputDevices => { + state.write_silent().settings.input_device = + audio_config.microphone_device_name(); + *input_devices.write() = + audio_config.get_available_microphones().unwrap_or_default(); } - - match rx.await { - Ok(_) => { - state.write_silent().settings.input_device = Some(device); + AudioCmd::FetchOutputDevices => { + state.write_silent().settings.output_device = + audio_config.speaker_device_name(); + *output_devices.write() = + audio_config.get_available_speakers().unwrap_or_default(); + } + AudioCmd::SetInputDevice(device_name) => { + let device = device_name.clone(); + let (tx, rx) = oneshot::channel(); + if let Err(_e) = + warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::SetMicrophone { + device_name, + rsp: tx, + })) + { + log::error!("failed to send blink command"); + continue; } - Err(e) => { - log::error!("warp_runner failed to set input device: {e}"); + + match rx.await { + Ok(_) => { + state.write_silent().settings.input_device = Some(device); + continue 'GET_AUDIO_CONFIG; + } + Err(e) => { + log::error!("warp_runner failed to set input device: {e}"); + } } } - } - AudioCmd::SetOutputDevice(device_name) => { - let device = device_name.clone(); - let (tx, rx) = oneshot::channel(); - if let Err(_e) = warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::SetSpeaker { - device_name, - rsp: tx, - })) { - log::error!("failed to send blink command"); - continue; - } - - match rx.await { - Ok(_) => { - state.write_silent().settings.output_device = Some(device); + AudioCmd::SetOutputDevice(device_name) => { + let device = device_name.clone(); + let (tx, rx) = oneshot::channel(); + if let Err(_e) = + warp_cmd_tx.send(WarpCmd::Blink(BlinkCmd::SetSpeaker { + device_name, + rsp: tx, + })) + { + log::error!("failed to send blink command"); + continue; } - Err(e) => { - log::error!("warp_runner failed to set output device: {e}"); + + match rx.await { + Ok(_) => { + state.write_silent().settings.output_device = Some(device); + continue 'GET_AUDIO_CONFIG; + } + Err(e) => { + log::error!("warp_runner failed to set output device: {e}"); + } } } } } + break; } } }); @@ -127,9 +149,9 @@ pub fn AudioSettings(cx: Scope) -> Element { to_owned![ch]; async move { loop { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; ch.send(AudioCmd::FetchInputDevices); ch.send(AudioCmd::FetchOutputDevices); + tokio::time::sleep(std::time::Duration::from_secs(10)).await; } } }); @@ -150,17 +172,18 @@ pub fn AudioSettings(cx: Scope) -> Element { } }, }, - SettingSectionSimple { - Range { - aria_label: "range-input-device".into(), - initial_value: 100.0, - min: VOL_MIN, - max: VOL_MAX, - icon_left: Shape::Microphone, - icon_right: Shape::MicrophoneWave, - onchange: move |_| {} - } - } + // SettingSectionSimple { + // Range { + // aria_label: "range-input-device".into(), + // initial_value: 100.0, + // min: VOL_MIN, + // max: VOL_MAX, + // icon_left: Shape::Microphone, + // icon_right: Shape::MicrophoneWave, + // disabled: true, + // onchange: move |_| {} + // } + // } SettingSection { section_label: get_local_text("settings-audio.output-device"), section_description: get_local_text("settings-audio.output-device-description"), @@ -169,57 +192,64 @@ pub fn AudioSettings(cx: Scope) -> Element { initial_value: state.read().settings.output_device.as_ref().cloned().unwrap_or("default".into()), options: output_devices.read().clone(), onselect: move |device| { - ch.send(AudioCmd::SetOutputDevice(device)) + ch.send(AudioCmd::SetOutputDevice(device)); } }, }, - SettingSectionSimple { - Range { - aria_label: "range-output-device".into(), - initial_value: VOL_MIN, - min: 0.0, - max: VOL_MAX, - icon_left: Shape::Speaker, - icon_right: Shape::SpeakerWave, - onchange: move |_| {} - } - } + // SettingSectionSimple { + // Range { + // aria_label: "range-output-device".into(), + // initial_value: 100.0, + // min: VOL_MIN, + // max: VOL_MAX, + // icon_left: Shape::Speaker, + // icon_right: Shape::SpeakerWave, + // disabled: true, + // onchange: move |_| {} + // } + // } - SettingSection { - section_label: get_local_text("settings-audio.sample-rate"), - section_description: get_local_text("settings-audio.sample-rate-description"), - Select { - initial_value: "48000 Hz".into(), - options: vec!["24000 Hz".into(), "48000 Hz".into(), "96000 Hz".into()], - onselect: move |_| {} - }, - }, + // currently does nothing + //SettingSection { + // section_label: get_local_text("settings-audio.sample-rate"), + // section_description: get_local_text("settings-audio.sample-rate-description"), + // Select { + // initial_value: "48000 Hz".into(), + // options: vec!["24000 Hz".into(), "48000 Hz".into(), "96000 Hz".into()], + // onselect: move |_| {} + // }, + //}, - SettingSection { - section_label: get_local_text("settings-audio.noise-suppression"), - section_description: get_local_text("settings-audio.noise-suppression-description"), - no_border: true, - }, - SettingSectionSimple { - RadioList { - initial_value: "None".into(), - values: vec!["None".into(), "Low".into(), "Medium".into(), "High".into()], - onchange: move |_| {} - }, - } + // currently not implemented + //SettingSection { + // section_label: get_local_text("settings-audio.noise-suppression"), + // section_description: get_local_text("settings-audio.noise-suppression-description"), + // no_border: true, + //}, + //SettingSectionSimple { + // RadioList { + // initial_value: "None".into(), + // values: vec!["None".into(), "Low".into(), "Medium".into(), "High".into()], + // onchange: move |_| {} + // }, + //} SettingSection { section_label: get_local_text("settings-audio.echo-cancellation"), section_description: get_local_text("settings-audio.echo-cancellation-description"), - no_border: true, + Switch { + active: state.read().configuration.audiovideo.echo_cancellation, + onflipped: move |e| { + if state.read().configuration.audiovideo.interface_sounds { + sounds::Play(sounds::Sounds::Flip); + } + ch.send(AudioCmd::SetEchoCancellation(e)); + state.write().mutate(Action::Config( + ConfigAction::SetEchoCancellation(e), + )); + } + } }, - SettingSectionSimple { - RadioList { - initial_value: "None".into(), - values: vec!["None".into(), "Low".into(), "Medium".into(), "High".into()], - onchange: move |_| {} - }, - } SettingSection { section_label: get_local_text("settings-audio.interface-sounds"),