diff --git a/client/src/main.rs b/client/src/main.rs index c7af1c6..a2c591a 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -15,8 +15,8 @@ use tokio::sync::broadcast; use crate::{ audio_streams::{audio_stream_receiver, audio_stream_spawner}, models::{ - CliArgs, ClientEvent, ClientInfo, InputAudioStreamsConsumers, InputAudioStreamsEncoders, - InputAudioStreamsInfo, InputAudioStreamsProducers, + AudioControl, CliArgs, ClientEvent, ClientInfo, InputAudioStreamsConsumers, + InputAudioStreamsEncoders, InputAudioStreamsInfo, InputAudioStreamsProducers, }, tcp_connection::tcp_connection, tui::Tui, @@ -41,21 +41,18 @@ async fn main() { let udp_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); - if cli.connect_with_defaults == None { - ratatui::run(|terminal| { - Tui::new().run( - terminal, - active_input_audio_streams_producers.clone(), - active_input_audio_streams_consumers.clone(), - active_input_audio_streams_encoders.clone(), - active_input_audio_streams_info.clone(), - udp_socket.clone(), - ) - }); - } else { + let audio_control: Arc = Arc::new(AudioControl { + active_input_audio_streams_producers: active_input_audio_streams_producers.clone(), + active_input_audio_streams_consumers: active_input_audio_streams_consumers.clone(), + active_input_audio_streams_encoders: active_input_audio_streams_encoders.clone(), + active_input_audio_streams_info: active_input_audio_streams_info.clone(), + udp_socket: udp_socket.clone(), + }); + + if let Some(server_ip) = cli.connect_with_defaults { let client_info: Arc> = Arc::new(RwLock::new(ClientInfo { client_id: 0, - server_address: cli.connect_with_defaults.unwrap(), + server_address: server_ip, connected: false, })); @@ -63,10 +60,7 @@ async fn main() { client_info.clone(), global_event_tx.clone(), global_event_tx.subscribe(), - active_input_audio_streams_producers.clone(), - active_input_audio_streams_consumers.clone(), - active_input_audio_streams_encoders.clone(), - active_input_audio_streams_info.clone(), + audio_control.clone(), )); let _ = global_event_tx.send(ClientEvent::NewConnection); @@ -112,4 +106,30 @@ async fn main() { tokio::signal::ctrl_c().await.expect("failed to close"); println!("shutting down"); } + + if let Some(server_ip) = cli.server_ip { + let client_info: Arc> = Arc::new(RwLock::new(ClientInfo { + client_id: 0, + server_address: server_ip, + connected: false, + })); + + tokio::spawn(tcp_connection( + client_info.clone(), + global_event_tx.clone(), + global_event_tx.subscribe(), + audio_control.clone(), + )); + + let _ = global_event_tx.send(ClientEvent::NewConnection); + + ratatui::run(|terminal| { + Tui::new().run( + terminal, + audio_control.clone(), + global_event_tx.clone(), + client_info.clone(), + ) + }); + } } diff --git a/client/src/models.rs b/client/src/models.rs index a34fd6f..f3f4187 100644 --- a/client/src/models.rs +++ b/client/src/models.rs @@ -6,17 +6,22 @@ use std::{ collections::HashMap, sync::{Arc, Mutex, RwLock}, }; +use tokio::net::UdpSocket; #[derive(Parser)] #[command(version, about)] pub struct CliArgs { #[arg(short, long, required(false))] pub connect_with_defaults: Option, + + #[arg(short, long, required(false))] + pub server_ip: Option, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum TuiScreen { DeviceSelect, + Blank, Exit, } @@ -43,3 +48,11 @@ pub type InputAudioStreamsConsumers = pub type InputAudioStreamsEncoders = Arc>>>; pub type InputAudioStreamsInfo = Arc>>; + +pub struct AudioControl { + pub active_input_audio_streams_producers: InputAudioStreamsProducers, + pub active_input_audio_streams_consumers: InputAudioStreamsConsumers, + pub active_input_audio_streams_encoders: InputAudioStreamsEncoders, + pub active_input_audio_streams_info: InputAudioStreamsInfo, + pub udp_socket: Arc, +} diff --git a/client/src/tcp_connection.rs b/client/src/tcp_connection.rs index 786ccf2..5d6f104 100644 --- a/client/src/tcp_connection.rs +++ b/client/src/tcp_connection.rs @@ -8,19 +8,13 @@ use tokio::{ sync::broadcast::{Receiver, Sender}, }; -use crate::models::{ - ClientEvent, ClientInfo, InputAudioStreamsConsumers, InputAudioStreamsEncoders, - InputAudioStreamsInfo, InputAudioStreamsProducers, -}; +use crate::models::{AudioControl, ClientEvent, ClientInfo}; pub async fn tcp_connection( client_info: Arc>, global_event_tx: Sender, mut global_event_rx: Receiver, - active_input_audio_streams_producers: InputAudioStreamsProducers, - active_input_audio_streams_consumers: InputAudioStreamsConsumers, - active_input_audio_streams_encoders: InputAudioStreamsEncoders, - active_input_audio_streams_info: InputAudioStreamsInfo, + audio_control: Arc, ) { let server_address = { let info = client_info.read().unwrap(); @@ -62,15 +56,15 @@ pub async fn tcp_connection( println!("user disconnected: {}", user_id); } TcpResponseData::AudioStreamsFullUpdate { audio_stream_info } => { - let mut producer_map = active_input_audio_streams_producers.lock().unwrap(); + let mut producer_map = audio_control.active_input_audio_streams_producers.lock().unwrap(); producer_map.clear(); - let mut consumer_map = active_input_audio_streams_consumers.lock().unwrap(); + let mut consumer_map = audio_control.active_input_audio_streams_consumers.lock().unwrap(); consumer_map.clear(); - let mut encoder_map = active_input_audio_streams_encoders.write().unwrap(); + let mut encoder_map = audio_control.active_input_audio_streams_encoders.write().unwrap(); encoder_map.clear(); - let mut info_map = active_input_audio_streams_info.write().unwrap(); + let mut info_map = audio_control.active_input_audio_streams_info.write().unwrap(); info_map.clear(); diff --git a/client/src/tui.rs b/client/src/tui.rs index 8199977..2a83eac 100644 --- a/client/src/tui.rs +++ b/client/src/tui.rs @@ -1,5 +1,5 @@ use cpal::{ - Device, SAMPLE_RATE_48K, StreamConfig, + Device, SAMPLE_RATE_48K, SampleFormat, StreamConfig, traits::{DeviceTrait, HostTrait}, }; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; @@ -8,14 +8,17 @@ use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Direction, Layout}, style::Style, - widgets::{Block, Row, Table, TableState}, + widgets::{Block, Paragraph, Row, Table, TableState}, }; -use std::{sync::Arc, vec}; -use tokio::net::UdpSocket; +use std::{ + sync::{Arc, RwLock}, + vec, +}; +use tokio::sync::broadcast::Sender; -use crate::models::{ - InputAudioStreamsConsumers, InputAudioStreamsEncoders, InputAudioStreamsInfo, - InputAudioStreamsProducers, TuiScreen, +use crate::{ + audio_streams::{audio_stream_receiver, audio_stream_spawner}, + models::{AudioControl, ClientEvent, ClientInfo, TuiScreen}, }; pub struct Tui { @@ -32,37 +35,86 @@ impl Tui { pub fn run( &mut self, terminal: &mut DefaultTerminal, - active_input_audio_streams_producers: InputAudioStreamsProducers, - active_input_audio_streams_consumers: InputAudioStreamsConsumers, - active_input_audio_streams_encoders: InputAudioStreamsEncoders, - active_input_audio_streams_info: InputAudioStreamsInfo, - udp_socket: Arc, + audio_control: Arc, + global_event_tx: Sender, + clinet_info: Arc>, ) { loop { match self.current_screen { TuiScreen::Exit => { break; } + TuiScreen::Blank => self.current_screen = BlankScreen::new().run(terminal), TuiScreen::DeviceSelect => { - self.current_screen = DeviceSelectScreen::new().run(terminal); + self.current_screen = DeviceSelectScreen::new( + audio_control.clone(), + global_event_tx.clone(), + clinet_info.clone(), + ) + .run(terminal); } } } } } -#[derive(Debug)] +struct BlankScreen { + exit: bool, +} + +impl BlankScreen { + pub fn new() -> Self { + Self { exit: false } + } + + pub fn run(&mut self, terminal: &mut DefaultTerminal) -> TuiScreen { + while !self.exit { + terminal + .draw(|frame| self.draw(frame)) + .expect("failed to draw"); + self.handle_events(); + } + + return TuiScreen::Exit; + } + + fn draw(&mut self, frame: &mut Frame) { + frame.render_widget(Paragraph::new("q - quit"), frame.area()); + } + + fn handle_events(&mut self) { + match event::read().expect("failed to read event") { + Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + match key_event.code { + KeyCode::Char('q') => self.exit = true, + _ => {} + } + } + _ => {} + } + } +} + struct DeviceSelectScreen { exit: bool, + exit_screen: TuiScreen, input_device_state: TableState, output_device_state: TableState, input_devices: Vec<(Device, StreamConfig)>, output_devices: Vec<(Device, StreamConfig)>, selected_selection: u8, + + audio_control: Arc, + global_event_tx: Sender, + clinet_info: Arc>, } impl DeviceSelectScreen { - pub fn new() -> Self { + pub fn new( + audio_control: Arc, + global_event_tx: Sender, + clinet_info: Arc>, + ) -> Self { let _print_gag = Gag::stderr().ok(); let host = cpal::default_host(); @@ -76,8 +128,10 @@ impl DeviceSelectScreen { if let Some(input_config) = supported_config.try_with_sample_rate(SAMPLE_RATE_48K) { - input_devices.push((possible_input_device, input_config.into())); - break; + if input_config.sample_format() == SampleFormat::F32 { + input_devices.push((possible_input_device, input_config.into())); + break; + } } } } @@ -93,8 +147,10 @@ impl DeviceSelectScreen { if let Some(output_config) = supported_config.try_with_sample_rate(SAMPLE_RATE_48K) { - output_devices.push((possible_output_device, output_config.into())); - break; + if output_config.sample_format() == SampleFormat::F32 { + output_devices.push((possible_output_device, output_config.into())); + break; + } } } } @@ -103,11 +159,16 @@ impl DeviceSelectScreen { Self { exit: false, + exit_screen: TuiScreen::Exit, input_device_state: TableState::default(), output_device_state: TableState::default(), input_devices: input_devices, output_devices: output_devices, selected_selection: 0, + + audio_control: audio_control, + global_event_tx: global_event_tx, + clinet_info: clinet_info, } } @@ -119,7 +180,7 @@ impl DeviceSelectScreen { self.handle_events(); } - return TuiScreen::Exit; + return self.exit_screen; } fn draw(&mut self, frame: &mut Frame) { @@ -231,8 +292,7 @@ impl DeviceSelectScreen { KeyCode::Up => self.decrement_table(), KeyCode::Left => self.selected_selection = 0, KeyCode::Right => self.selected_selection = 1, - // KeyCode::Down => self.increment_list(), - // KeyCode::Enter => self.get_value(), + KeyCode::Enter => self.connect_devices(), _ => {} } } @@ -257,6 +317,50 @@ impl DeviceSelectScreen { } } + fn connect_devices(&mut self) { + if let (Some(input_device_row), Some(output_device_row)) = ( + self.input_device_state.selected(), + self.output_device_state.selected(), + ) { + let (input_device, input_config) = self + .input_devices + .get(input_device_row) + .expect("failed to get input config"); + let (output_device, output_config) = self + .output_devices + .get(output_device_row) + .expect("failed to get output config"); + + tokio::spawn(audio_stream_receiver( + output_device.clone(), + output_config.clone(), + self.audio_control + .active_input_audio_streams_producers + .clone(), + self.audio_control + .active_input_audio_streams_consumers + .clone(), + self.audio_control + .active_input_audio_streams_encoders + .clone(), + self.audio_control.active_input_audio_streams_info.clone(), + self.audio_control.udp_socket.clone(), + )); + + tokio::spawn(audio_stream_spawner( + input_device.clone(), + input_config.clone(), + self.global_event_tx.clone(), + self.global_event_tx.subscribe(), + self.audio_control.udp_socket.clone(), + self.clinet_info.clone(), + )); + + self.exit_screen = TuiScreen::Blank; + self.exit = true; + } + } + // fn get_value(&mut self) { // self.table_state.selected(); // } diff --git a/server/src/main.rs b/server/src/main.rs index 37318f5..a151ef3 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -11,7 +11,7 @@ use tokio::{ #[derive(Parser)] #[command(version, about)] struct Cli { - #[arg(short, long, default_value("0.0.0.0:8080"))] + #[arg(short, long, default_value("0.0.0.0:6767"))] server_ip: Option, }