Files
tap/tap_gui/src/main.rs
T

189 lines
6.2 KiB
Rust

use cpal::traits::{DeviceTrait, HostTrait};
use eframe::{App, Frame};
use egui::Ui;
use env_logger::Env;
use log::error;
use std::sync::mpsc::{Receiver, channel};
use std::thread;
use std::thread::JoinHandle;
use tap_core::audio_source_thread::{AudioSourceThread, Commands};
use tap_core::udp_connection::OpusUdpConnection;
use thiserror::Error;
#[derive(Default)]
pub enum State {
#[default]
Init,
Connecting,
Connected,
}
#[derive(Debug, Error)]
pub enum ThreadError {
#[error("Failed to establish udp connection: {0}")]
IOError(#[from] std::io::Error),
#[error("Audio thread error: {0}")]
TapError(#[from] tap_core::audio_source_thread::Error),
}
pub struct Tap {
mode: State,
device: String,
denny_jack_host: String,
warning: String,
key: String,
decoded_key: Vec<u8>,
join_handle: Option<JoinHandle<Result<(), ThreadError>>>,
channel: Option<std::sync::mpsc::Sender<Commands>>,
host: cpal::Host,
}
impl Default for Tap {
fn default() -> Self {
Self::new()
}
}
impl Tap {
pub fn new() -> Self {
Self {
mode: Default::default(),
device: "".to_string(),
denny_jack_host: "158.69.121.46:9999".to_string(),
warning: "".to_string(),
key: "".to_string(),
decoded_key: vec![],
join_handle: None,
channel: None,
host: cpal::default_host(),
}
}
pub fn bg_threads(
device: &str,
host: &str,
key: &[u8],
channel: Receiver<Commands>,
) -> Result<(), ThreadError> {
let udp_connection =
match OpusUdpConnection::new("0.0.0.0:42069".parse().unwrap(), host.parse().unwrap()) {
Ok(c) => c,
Err(err) => {
return Err(err.into());
}
};
let host = cpal::default_host();
let audio_source_thread = AudioSourceThread::new(device.to_string(), host, channel);
// Run until completion
audio_source_thread.worker(udp_connection, key)?;
Ok(())
}
}
impl App for Tap {
fn ui(&mut self, ui: &mut Ui, _frame: &mut Frame) {
let panel_frame = egui::containers::CentralPanel::default_margins();
panel_frame.show(ui, |ui| {
ui.heading("Tap GUI");
ui.add_space(4.0);
match self.mode {
State::Init => {
ui.label("Denny Jack Host");
ui.text_edit_singleline(&mut self.denny_jack_host);
ui.label("Denny Jack Session Key");
ui.text_edit_singleline(&mut self.key);
egui::containers::ComboBox::from_label("Device")
.selected_text(&self.device)
.show_ui(ui, |ui| {
for device in self.host.devices().unwrap() {
ui.selectable_value(
&mut self.device,
device.id().unwrap().to_string(),
device.to_string(),
);
}
});
if ui.button("Connect").clicked() {
if self.device.is_empty()
|| self.denny_jack_host.is_empty()
|| self.key.is_empty()
{
self.warning = "Enter connection details and try again".to_string();
} else {
if let Ok(key) = hex::decode(&self.key)
&& key.len() == 32
{
self.warning = "".to_string();
self.mode = State::Connecting;
self.decoded_key = key;
} else {
self.warning = "Invalid session key".to_string()
}
}
}
if !self.warning.is_empty() {
ui.label(format!("Error: {}", self.warning));
}
}
State::Connecting => {
ui.label("Connecting");
let device = self.device.to_string();
let host = self.denny_jack_host.to_string();
let (sender, receiver) = channel();
let decoded_key = self.decoded_key.clone();
let join = thread::spawn(move || {
Self::bg_threads(&device, &host, &decoded_key, receiver)
});
self.join_handle = Some(join);
self.channel = Some(sender);
self.mode = State::Connected;
}
State::Connected => {
ui.label("Connected");
if self.join_handle.as_ref().unwrap().is_finished() {
let join = self.join_handle.take().unwrap();
let err = join.join();
error!("Thread joined early: {err:?}");
self.warning = "Disconnected...".to_string();
self.channel = None;
self.join_handle = None;
self.mode = State::Init;
}
if ui.button("Disconnect").clicked() {
self.channel = None;
let handle = self.join_handle.take().unwrap();
let _ = handle.join();
self.join_handle = None;
self.mode = State::Init;
self.decoded_key = vec![];
}
}
}
});
}
}
#[tokio::main]
async fn main() -> Result<(), eframe::Error> {
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let options = eframe::NativeOptions {
..Default::default()
};
eframe::run_native("Tap", options, Box::new(|_cc| Ok(Box::new(Tap::new()))))
}