189 lines
6.2 KiB
Rust
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()))))
|
|
}
|