almost working device selector
This commit is contained in:
+38
-18
@@ -15,8 +15,8 @@ use tokio::sync::broadcast;
|
|||||||
use crate::{
|
use crate::{
|
||||||
audio_streams::{audio_stream_receiver, audio_stream_spawner},
|
audio_streams::{audio_stream_receiver, audio_stream_spawner},
|
||||||
models::{
|
models::{
|
||||||
CliArgs, ClientEvent, ClientInfo, InputAudioStreamsConsumers, InputAudioStreamsEncoders,
|
AudioControl, CliArgs, ClientEvent, ClientInfo, InputAudioStreamsConsumers,
|
||||||
InputAudioStreamsInfo, InputAudioStreamsProducers,
|
InputAudioStreamsEncoders, InputAudioStreamsInfo, InputAudioStreamsProducers,
|
||||||
},
|
},
|
||||||
tcp_connection::tcp_connection,
|
tcp_connection::tcp_connection,
|
||||||
tui::Tui,
|
tui::Tui,
|
||||||
@@ -41,21 +41,18 @@ async fn main() {
|
|||||||
|
|
||||||
let udp_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap());
|
let udp_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap());
|
||||||
|
|
||||||
if cli.connect_with_defaults == None {
|
let audio_control: Arc<AudioControl> = Arc::new(AudioControl {
|
||||||
ratatui::run(|terminal| {
|
active_input_audio_streams_producers: active_input_audio_streams_producers.clone(),
|
||||||
Tui::new().run(
|
active_input_audio_streams_consumers: active_input_audio_streams_consumers.clone(),
|
||||||
terminal,
|
active_input_audio_streams_encoders: active_input_audio_streams_encoders.clone(),
|
||||||
active_input_audio_streams_producers.clone(),
|
active_input_audio_streams_info: active_input_audio_streams_info.clone(),
|
||||||
active_input_audio_streams_consumers.clone(),
|
udp_socket: udp_socket.clone(),
|
||||||
active_input_audio_streams_encoders.clone(),
|
|
||||||
active_input_audio_streams_info.clone(),
|
|
||||||
udp_socket.clone(),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
|
if let Some(server_ip) = cli.connect_with_defaults {
|
||||||
let client_info: Arc<RwLock<ClientInfo>> = Arc::new(RwLock::new(ClientInfo {
|
let client_info: Arc<RwLock<ClientInfo>> = Arc::new(RwLock::new(ClientInfo {
|
||||||
client_id: 0,
|
client_id: 0,
|
||||||
server_address: cli.connect_with_defaults.unwrap(),
|
server_address: server_ip,
|
||||||
connected: false,
|
connected: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -63,10 +60,7 @@ async fn main() {
|
|||||||
client_info.clone(),
|
client_info.clone(),
|
||||||
global_event_tx.clone(),
|
global_event_tx.clone(),
|
||||||
global_event_tx.subscribe(),
|
global_event_tx.subscribe(),
|
||||||
active_input_audio_streams_producers.clone(),
|
audio_control.clone(),
|
||||||
active_input_audio_streams_consumers.clone(),
|
|
||||||
active_input_audio_streams_encoders.clone(),
|
|
||||||
active_input_audio_streams_info.clone(),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let _ = global_event_tx.send(ClientEvent::NewConnection);
|
let _ = global_event_tx.send(ClientEvent::NewConnection);
|
||||||
@@ -112,4 +106,30 @@ async fn main() {
|
|||||||
tokio::signal::ctrl_c().await.expect("failed to close");
|
tokio::signal::ctrl_c().await.expect("failed to close");
|
||||||
println!("shutting down");
|
println!("shutting down");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(server_ip) = cli.server_ip {
|
||||||
|
let client_info: Arc<RwLock<ClientInfo>> = 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(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -6,17 +6,22 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
use tokio::net::UdpSocket;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about)]
|
#[command(version, about)]
|
||||||
pub struct CliArgs {
|
pub struct CliArgs {
|
||||||
#[arg(short, long, required(false))]
|
#[arg(short, long, required(false))]
|
||||||
pub connect_with_defaults: Option<String>,
|
pub connect_with_defaults: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short, long, required(false))]
|
||||||
|
pub server_ip: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum TuiScreen {
|
pub enum TuiScreen {
|
||||||
DeviceSelect,
|
DeviceSelect,
|
||||||
|
Blank,
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,3 +48,11 @@ pub type InputAudioStreamsConsumers =
|
|||||||
pub type InputAudioStreamsEncoders = Arc<RwLock<HashMap<AudioStreamKey, Mutex<Decoder>>>>;
|
pub type InputAudioStreamsEncoders = Arc<RwLock<HashMap<AudioStreamKey, Mutex<Decoder>>>>;
|
||||||
|
|
||||||
pub type InputAudioStreamsInfo = Arc<RwLock<HashMap<AudioStreamKey, AudioStreamInfo>>>;
|
pub type InputAudioStreamsInfo = Arc<RwLock<HashMap<AudioStreamKey, AudioStreamInfo>>>;
|
||||||
|
|
||||||
|
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<UdpSocket>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,19 +8,13 @@ use tokio::{
|
|||||||
sync::broadcast::{Receiver, Sender},
|
sync::broadcast::{Receiver, Sender},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::models::{
|
use crate::models::{AudioControl, ClientEvent, ClientInfo};
|
||||||
ClientEvent, ClientInfo, InputAudioStreamsConsumers, InputAudioStreamsEncoders,
|
|
||||||
InputAudioStreamsInfo, InputAudioStreamsProducers,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn tcp_connection(
|
pub async fn tcp_connection(
|
||||||
client_info: Arc<RwLock<ClientInfo>>,
|
client_info: Arc<RwLock<ClientInfo>>,
|
||||||
global_event_tx: Sender<ClientEvent>,
|
global_event_tx: Sender<ClientEvent>,
|
||||||
mut global_event_rx: Receiver<ClientEvent>,
|
mut global_event_rx: Receiver<ClientEvent>,
|
||||||
active_input_audio_streams_producers: InputAudioStreamsProducers,
|
audio_control: Arc<AudioControl>,
|
||||||
active_input_audio_streams_consumers: InputAudioStreamsConsumers,
|
|
||||||
active_input_audio_streams_encoders: InputAudioStreamsEncoders,
|
|
||||||
active_input_audio_streams_info: InputAudioStreamsInfo,
|
|
||||||
) {
|
) {
|
||||||
let server_address = {
|
let server_address = {
|
||||||
let info = client_info.read().unwrap();
|
let info = client_info.read().unwrap();
|
||||||
@@ -62,15 +56,15 @@ pub async fn tcp_connection(
|
|||||||
println!("user disconnected: {}", user_id);
|
println!("user disconnected: {}", user_id);
|
||||||
}
|
}
|
||||||
TcpResponseData::AudioStreamsFullUpdate { audio_stream_info } => {
|
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();
|
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();
|
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();
|
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();
|
info_map.clear();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+122
-18
@@ -1,5 +1,5 @@
|
|||||||
use cpal::{
|
use cpal::{
|
||||||
Device, SAMPLE_RATE_48K, StreamConfig,
|
Device, SAMPLE_RATE_48K, SampleFormat, StreamConfig,
|
||||||
traits::{DeviceTrait, HostTrait},
|
traits::{DeviceTrait, HostTrait},
|
||||||
};
|
};
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
|
||||||
@@ -8,14 +8,17 @@ use ratatui::{
|
|||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
style::Style,
|
style::Style,
|
||||||
widgets::{Block, Row, Table, TableState},
|
widgets::{Block, Paragraph, Row, Table, TableState},
|
||||||
};
|
};
|
||||||
use std::{sync::Arc, vec};
|
use std::{
|
||||||
use tokio::net::UdpSocket;
|
sync::{Arc, RwLock},
|
||||||
|
vec,
|
||||||
|
};
|
||||||
|
use tokio::sync::broadcast::Sender;
|
||||||
|
|
||||||
use crate::models::{
|
use crate::{
|
||||||
InputAudioStreamsConsumers, InputAudioStreamsEncoders, InputAudioStreamsInfo,
|
audio_streams::{audio_stream_receiver, audio_stream_spawner},
|
||||||
InputAudioStreamsProducers, TuiScreen,
|
models::{AudioControl, ClientEvent, ClientInfo, TuiScreen},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
@@ -32,37 +35,86 @@ impl Tui {
|
|||||||
pub fn run(
|
pub fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
terminal: &mut DefaultTerminal,
|
terminal: &mut DefaultTerminal,
|
||||||
active_input_audio_streams_producers: InputAudioStreamsProducers,
|
audio_control: Arc<AudioControl>,
|
||||||
active_input_audio_streams_consumers: InputAudioStreamsConsumers,
|
global_event_tx: Sender<ClientEvent>,
|
||||||
active_input_audio_streams_encoders: InputAudioStreamsEncoders,
|
clinet_info: Arc<RwLock<ClientInfo>>,
|
||||||
active_input_audio_streams_info: InputAudioStreamsInfo,
|
|
||||||
udp_socket: Arc<UdpSocket>,
|
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
match self.current_screen {
|
match self.current_screen {
|
||||||
TuiScreen::Exit => {
|
TuiScreen::Exit => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
TuiScreen::Blank => self.current_screen = BlankScreen::new().run(terminal),
|
||||||
TuiScreen::DeviceSelect => {
|
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 {
|
struct DeviceSelectScreen {
|
||||||
exit: bool,
|
exit: bool,
|
||||||
|
exit_screen: TuiScreen,
|
||||||
input_device_state: TableState,
|
input_device_state: TableState,
|
||||||
output_device_state: TableState,
|
output_device_state: TableState,
|
||||||
input_devices: Vec<(Device, StreamConfig)>,
|
input_devices: Vec<(Device, StreamConfig)>,
|
||||||
output_devices: Vec<(Device, StreamConfig)>,
|
output_devices: Vec<(Device, StreamConfig)>,
|
||||||
selected_selection: u8,
|
selected_selection: u8,
|
||||||
|
|
||||||
|
audio_control: Arc<AudioControl>,
|
||||||
|
global_event_tx: Sender<ClientEvent>,
|
||||||
|
clinet_info: Arc<RwLock<ClientInfo>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceSelectScreen {
|
impl DeviceSelectScreen {
|
||||||
pub fn new() -> Self {
|
pub fn new(
|
||||||
|
audio_control: Arc<AudioControl>,
|
||||||
|
global_event_tx: Sender<ClientEvent>,
|
||||||
|
clinet_info: Arc<RwLock<ClientInfo>>,
|
||||||
|
) -> Self {
|
||||||
let _print_gag = Gag::stderr().ok();
|
let _print_gag = Gag::stderr().ok();
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
@@ -76,6 +128,7 @@ impl DeviceSelectScreen {
|
|||||||
if let Some(input_config) =
|
if let Some(input_config) =
|
||||||
supported_config.try_with_sample_rate(SAMPLE_RATE_48K)
|
supported_config.try_with_sample_rate(SAMPLE_RATE_48K)
|
||||||
{
|
{
|
||||||
|
if input_config.sample_format() == SampleFormat::F32 {
|
||||||
input_devices.push((possible_input_device, input_config.into()));
|
input_devices.push((possible_input_device, input_config.into()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -83,6 +136,7 @@ impl DeviceSelectScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut output_devices: Vec<(Device, StreamConfig)> = Vec::new();
|
let mut output_devices: Vec<(Device, StreamConfig)> = Vec::new();
|
||||||
|
|
||||||
@@ -93,6 +147,7 @@ impl DeviceSelectScreen {
|
|||||||
if let Some(output_config) =
|
if let Some(output_config) =
|
||||||
supported_config.try_with_sample_rate(SAMPLE_RATE_48K)
|
supported_config.try_with_sample_rate(SAMPLE_RATE_48K)
|
||||||
{
|
{
|
||||||
|
if output_config.sample_format() == SampleFormat::F32 {
|
||||||
output_devices.push((possible_output_device, output_config.into()));
|
output_devices.push((possible_output_device, output_config.into()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -100,14 +155,20 @@ impl DeviceSelectScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
exit: false,
|
exit: false,
|
||||||
|
exit_screen: TuiScreen::Exit,
|
||||||
input_device_state: TableState::default(),
|
input_device_state: TableState::default(),
|
||||||
output_device_state: TableState::default(),
|
output_device_state: TableState::default(),
|
||||||
input_devices: input_devices,
|
input_devices: input_devices,
|
||||||
output_devices: output_devices,
|
output_devices: output_devices,
|
||||||
selected_selection: 0,
|
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();
|
self.handle_events();
|
||||||
}
|
}
|
||||||
|
|
||||||
return TuiScreen::Exit;
|
return self.exit_screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame) {
|
fn draw(&mut self, frame: &mut Frame) {
|
||||||
@@ -231,8 +292,7 @@ impl DeviceSelectScreen {
|
|||||||
KeyCode::Up => self.decrement_table(),
|
KeyCode::Up => self.decrement_table(),
|
||||||
KeyCode::Left => self.selected_selection = 0,
|
KeyCode::Left => self.selected_selection = 0,
|
||||||
KeyCode::Right => self.selected_selection = 1,
|
KeyCode::Right => self.selected_selection = 1,
|
||||||
// KeyCode::Down => self.increment_list(),
|
KeyCode::Enter => self.connect_devices(),
|
||||||
// KeyCode::Enter => self.get_value(),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) {
|
// fn get_value(&mut self) {
|
||||||
// self.table_state.selected();
|
// self.table_state.selected();
|
||||||
// }
|
// }
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ use tokio::{
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about)]
|
#[command(version, about)]
|
||||||
struct Cli {
|
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<String>,
|
server_ip: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user