diff --git a/common/src/lib.rs b/common/src/lib.rs index 1091e65..87c3c72 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -5,6 +5,7 @@ pub struct ClientConfig { pub proxy_url: Option, pub status_url: Option, pub cert_hash: Option>, + pub any_server: bool, } #[derive(Debug, Clone, Deserialize, Serialize, Default)] diff --git a/gui/src/app.rs b/gui/src/app.rs index 4fb5f02..f9edf97 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -1,13 +1,11 @@ #![allow(non_snake_case)] -use base64::{display::Base64Display, prelude::BASE64_URL_SAFE}; use dioxus::prelude::*; use mime_guess::Mime; use mumble_web2_common::{ClientConfig, ServerStatus}; use ordermap::OrderSet; use sir::{css, global_css}; use std::collections::HashMap; -use tracing::error; use crate::imp; @@ -459,13 +457,10 @@ pub fn ControlView(config: Resource) -> Element { let current_channel_name = server.channels[&channel].name.clone(); - let Some(proxy_url) = config + let proxy_url = config .read_unchecked() .as_ref() - .and_then(|gui_config| gui_config.proxy_url.clone()) - else { - return rsx!(); - }; + .and_then(|gui_config| gui_config.proxy_url.clone()); let button_row = css!( r#" @@ -595,8 +590,10 @@ pub fn ControlView(config: Resource) -> Element { class: "{connection_info}", span { style: "width: 3px; display: inline-block;"} span { "{current_channel_name}" } - span { " — " } - span { "{proxy_url}" } + if let Some(proxy_url) = proxy_url { + span { " — " } + span { "{proxy_url}" } + } } } }, @@ -855,7 +852,7 @@ pub fn LoginView(config: Resource) -> Element { }); let mut address_input = use_signal(|| None::); - let mut address = use_memo(move || { + let address = use_memo(move || { if let Some(addr) = address_input() { addr.clone() } else { @@ -957,6 +954,21 @@ pub fn LoginView(config: Resource) -> Element { h1 { "Mumble Web" } + if config.read().as_ref().is_some_and(|cfg| cfg.any_server) { + div { + label { + for: "address-entry", + "Server Address:" + } + input { + id: "address-entry", + placeholder: "address", + value: "{address.read()}", + autofocus: "true", + oninput: move |evt| address_input.set(Some(evt.value().clone())), + } + } + } div { label { for: "username-entry", diff --git a/gui/src/effects.rs b/gui/src/effects.rs index addd072..338943a 100644 --- a/gui/src/effects.rs +++ b/gui/src/effects.rs @@ -14,14 +14,14 @@ impl AudioProcessor { } impl AudioProcessor { - pub fn process(&mut self, audio: &[f32]) -> Box<[f32]> { - let mut output: Box<[f32]> = vec![0f32; audio.len()].into(); + pub fn process(&mut self, audio: &[f32], output: &mut Vec) { if let Some(df) = &mut self.df { - df.process_frame(audio, &mut output); + let start = output.len(); + output.extend(std::iter::repeat_n(0f32, audio.len())); + df.process_frame(audio, &mut output[start..]); } else { - output.copy_from_slice(audio); + output.extend_from_slice(audio); } - output } } diff --git a/gui/src/imp/desktop.rs b/gui/src/imp/desktop.rs index ccb6bda..915d483 100644 --- a/gui/src/imp/desktop.rs +++ b/gui/src/imp/desktop.rs @@ -1,5 +1,5 @@ use crate::app::Command; -use crate::effects::AudioProcessor; +use crate::effects::{AudioProcessor, AudioProcessorSender}; use color_eyre::eyre::{eyre, Error}; use cpal::traits::{DeviceTrait, HostTrait}; use dioxus::hooks::{UnboundedReceiver, UnboundedSender}; @@ -7,6 +7,7 @@ use futures::io::{AsyncRead, AsyncWrite}; use mumble_protocol::control::{ClientControlCodec, ControlPacket}; use mumble_protocol::Serverbound; use mumble_web2_common::ClientConfig; +use std::mem::replace; use std::net::ToSocketAddrs; use std::sync::Mutex; use std::{fmt, io, sync::Arc}; @@ -18,7 +19,7 @@ use tokio_rustls::rustls::ClientConfig as RlsClientConfig; use tokio_rustls::rustls::DigitallySignedStruct; use tokio_rustls::TlsConnector; use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _}; -use tracing::{error, info, instrument, warn}; +use tracing::{debug, error, info, instrument, warn}; pub use tokio::task::spawn; pub use tokio::time::sleep; @@ -32,8 +33,13 @@ impl ImpWrite for T {} pub struct AudioSystem { output: cpal::Device, input: cpal::Device, + processors: AudioProcessorSender, + recording_stream: Option, } +const SAMPLE_RATE: u32 = 48_000; +const PACKET_SAMPLES: u32 = 960; + type Buffer = Arc>>>; impl AudioSystem { @@ -41,6 +47,7 @@ impl AudioSystem { // TODO let host = cpal::default_host(); let name = host.id(); + let processors = AudioProcessorSender::default(); Ok(AudioSystem { output: host .default_output_device() @@ -48,16 +55,66 @@ impl AudioSystem { input: host .default_input_device() .ok_or(eyre!("no input devices from {name:?}"))?, + processors, + recording_stream: None, }) } pub fn set_processor(&self, processor: AudioProcessor) { - // TODO + self.processors.store(Some(processor)) } - pub fn start_recording(&mut self, each: impl FnMut(Vec) + 'static) -> Result<(), Error> { - // TODO - Ok(()) + pub fn start_recording( + &mut self, + mut each: impl FnMut(Vec) + Send + 'static, + ) -> Result<(), Error> { + let mut encoder = + opus::Encoder::new(48_000, opus::Channels::Mono, opus::Application::Voip)?; + let mut current_processor = AudioProcessor::default(); + let mut output_buffer = Vec::new(); + let processors = self.processors.clone(); + let error_callback = move |e: cpal::StreamError| error!("error recording: {e:?}"); + let data_callback = move |frame: &[f32], _: &cpal::InputCallbackInfo| { + if let Some(new_processor) = processors.take() { + current_processor = new_processor; + } + info!("recieved {} samples", frame.len()); + current_processor.process(frame, &mut output_buffer); + if output_buffer.len() < PACKET_SAMPLES as usize { + return; + } + let remainder = output_buffer.split_off(PACKET_SAMPLES as usize); + let frame = replace(&mut output_buffer, remainder); + match encoder.encode_vec_float(&frame, frame.len() * 4) { + Ok(buf) => { + info!("encoded {} samples to {} bytes", frame.len(), buf.len()); + each(buf); + } + Err(e) => { + error!("error encoding {} samples: {e:?}", frame.len()); + } + } + }; + + match self.input.build_input_stream( + &cpal::StreamConfig { + channels: 1, + sample_rate: cpal::SampleRate(SAMPLE_RATE), + buffer_size: cpal::BufferSize::Fixed(PACKET_SAMPLES), + }, + data_callback, + error_callback, + None, + ) { + Ok(stream) => { + self.recording_stream = Some(stream); + Ok(()) + } + Err(err) => { + self.recording_stream = None; + Err(err.into()) + } + } } pub fn create_player(&mut self) -> Result { @@ -69,13 +126,13 @@ impl AudioSystem { 2400 // 50ms of buffer ], ))); - let decoder = opus::Decoder::new(48_000, opus::Channels::Mono)?; + let decoder = opus::Decoder::new(SAMPLE_RATE, opus::Channels::Mono)?; let stream = { let buffer = buffer.clone(); self.output.build_output_stream( &cpal::StreamConfig { channels: 1, - sample_rate: cpal::SampleRate(48_000), + sample_rate: cpal::SampleRate(SAMPLE_RATE), buffer_size: cpal::BufferSize::Fixed(480), // 10ms playback delay }, move |frame, info| { @@ -234,9 +291,12 @@ pub fn load_username() -> Option { } pub async fn load_config() -> color_eyre::Result { - color_eyre::eyre::bail!( - "there is no config on desktop because desktops cannot be configured as they are tables" - ) + Ok(ClientConfig { + proxy_url: None, + status_url: None, + cert_hash: None, + any_server: true, + }) } pub fn init_logging() { diff --git a/gui/src/imp/web.rs b/gui/src/imp/web.rs index 2b71e2f..9c30f0b 100644 --- a/gui/src/imp/web.rs +++ b/gui/src/imp/web.rs @@ -82,11 +82,8 @@ impl AudioSystem { // Create MediaStreams to playback decoded audio // The audio context is used to reproduce audio. let webctx = configure_audio_context(); - let processor = AudioProcessorSender::default(); - Ok(AudioSystem { - webctx, - processors: processor, - }) + let processors = AudioProcessorSender::default(); + Ok(AudioSystem { webctx, processors }) } pub fn set_processor(&self, processor: AudioProcessor) { @@ -210,7 +207,8 @@ fn process_audio(frame: &JsValue, processor: &mut AudioProcessor) { return; }; let input = samples.to_vec(); - let output = processor.process(&input); + let mut output = Vec::with_capacity(input.len()); + processor.process(&input, &mut output); samples.copy_from(&output); } diff --git a/proxy/src/main.rs b/proxy/src/main.rs index c3c8b4e..2d7c8d5 100644 --- a/proxy/src/main.rs +++ b/proxy/src/main.rs @@ -85,6 +85,7 @@ async fn main() -> Result<()> { proxy_url: Some(server_config.public_url.join("proxy")?.to_string()), status_url: Some(server_config.public_url.join("status")?.to_string()), cert_hash: None, + any_server: false, }; let (cert, key) = match (&server_config.cert_path, &server_config.key_path) {