feat: new protocol client implementation

This commit is contained in:
Ahmet Kaan Gümüş 2025-06-14 06:04:48 +03:00
parent 51c29f7921
commit 5ad6184f13
9 changed files with 497 additions and 267 deletions

View file

@ -8,6 +8,7 @@ protocol = { path = "../protocol" }
chrono = { workspace = true }
tokio = { workspace = true }
s2n-quic = { workspace = true }
cpal = "0.15.3"
cpal = "0.16.0"
iced = { features = ["tokio"], git = "https://github.com/iced-rs/iced", rev = "d39022432c778a8cda455f40b9c12245db86ce45" }
fixed-resample = "0.8.0"
rodio = {git = "https://github.com/RustAudio/rodio", rev = "071db6df2adfccfe1032be43dd87e5681e34292c"}

View file

@ -18,13 +18,13 @@ use crate::{
};
#[derive(Debug)]
struct Signal {
struct Controller {
record_control: mpsc::Sender<State>,
play_control: mpsc::Sender<State>,
connection_stop_sender: RwLock<Option<oneshot::Sender<bool>>>,
}
impl Signal {
impl Controller {
fn reset_connection(&self) -> Result<(), Error> {
let connection_stop_sender = self.connection_stop_sender.write().unwrap().take();
match connection_stop_sender {
@ -43,7 +43,7 @@ impl Signal {
#[derive(Debug)]
struct Channel {
microphone: Arc<broadcast::Sender<f32>>,
speaker: Arc<broadcast::Sender<f32>>,
speaker: Arc<broadcast::Sender<protocol::protocol::Signal>>,
}
#[derive(Debug, Clone, Copy)]
@ -90,12 +90,12 @@ pub struct App {
client_config: Arc<ClientConfig>,
gui_status: Arc<RwLock<GUIStatus>>,
channel: Channel,
signal: Arc<Signal>,
controller: Arc<Controller>,
}
impl App {
fn reset_connection(&mut self) -> Result<(), Error> {
self.signal.reset_connection()?;
self.controller.reset_connection()?;
self.gui_status.write().unwrap().room = State::Passive;
Ok(())
}
@ -122,7 +122,7 @@ impl App {
microphone: record_sender,
speaker: play_sender,
},
signal: Signal {
controller: Controller {
record_control: record_control.0,
play_control: play_control.0,
connection_stop_sender: None.into(),
@ -168,7 +168,7 @@ impl App {
let speaker_sender = self.channel.speaker.clone();
let (connection_stop_sender, connection_stop_receiver) = oneshot::channel();
*self.signal.connection_stop_sender.write().unwrap() = Some(connection_stop_sender);
*self.controller.connection_stop_sender.write().unwrap() = Some(connection_stop_sender);
Task::perform(
async move {
@ -211,7 +211,7 @@ impl App {
self.gui_status.write().unwrap().microphone = State::Loading;
let gui_status = self.gui_status.clone();
let signal = self.signal.clone();
let signal = self.controller.clone();
Task::perform(
async move { signal.record_control.send(State::Active).await },
move |result| {
@ -232,7 +232,7 @@ impl App {
self.gui_status.write().unwrap().microphone = State::Loading;
let gui_status = self.gui_status.clone();
let signal = self.signal.clone();
let signal = self.controller.clone();
Task::perform(
async move { signal.record_control.send(State::Passive).await },
move |result| {
@ -253,7 +253,7 @@ impl App {
self.gui_status.write().unwrap().speaker = State::Loading;
let gui_status = self.gui_status.clone();
let signal = self.signal.clone();
let signal = self.controller.clone();
Task::perform(
async move { signal.play_control.send(State::Active).await },
move |result| {
@ -274,7 +274,7 @@ impl App {
self.gui_status.write().unwrap().speaker = State::Loading;
let gui_status = self.gui_status.clone();
let signal = self.signal.clone();
let signal = self.controller.clone();
Task::perform(
async move { signal.play_control.send(State::Passive).await },
move |result| {

View file

@ -1,3 +1,6 @@
use protocol::protocol::Speaker;
use tokio::sync::broadcast;
pub mod gui;
pub mod stream;
pub mod voice;
@ -5,6 +8,21 @@ pub mod voice;
const MICROPHONE_BUFFER_LENGHT: usize = 1024 * 4;
const SPEAKER_BUFFER_LENGHT: usize = 1024 * 16 * 16;
#[derive(Debug, Clone)]
pub struct SpeakerWithData {
speaker: Speaker,
audio_sender: broadcast::Sender<f32>,
}
impl SpeakerWithData {
pub fn get_speaker_id(&self) -> u8 {
self.speaker.get_id()
}
pub fn subscribe(&self) -> broadcast::Receiver<f32> {
self.audio_sender.subscribe()
}
}
#[derive(Debug)]
pub struct ClientConfig {
certificate_path: String,

View file

@ -1,6 +1,9 @@
use std::{net::SocketAddr, path::Path, sync::Arc};
use protocol::{Error, NETWORK_BUFFER_LENGTH, Signal, SignalType, SignedAudioDatum};
use protocol::{
Error,
protocol::{NETWORK_DATA_LENGTH, Signal},
};
use s2n_quic::{
Client,
client::Connect,
@ -22,7 +25,7 @@ pub struct ConnectReturn {
pub async fn connect(
microphone_receiver: broadcast::Receiver<f32>,
speaker_sender: Arc<broadcast::Sender<f32>>,
speaker_sender: Arc<broadcast::Sender<Signal>>,
client_config: Arc<ClientConfig>,
) -> Result<ConnectReturn, Error> {
let client = Client::builder()
@ -58,10 +61,8 @@ pub async fn connect(
.map_err(|err_val| Error::ConnectionSetup(err_val.to_string()))?;
let (receive_stream, send_stream) = stream.split();
let ready_signal = broadcast::channel(3);
let send_signals_task = tokio::spawn(send_signals(send_stream, microphone_receiver));
let speaker_clone = speaker_sender.clone();
let receive_signals_task = tokio::spawn(receive_signals(receive_stream, speaker_sender));
Ok(ConnectReturn {
@ -111,25 +112,15 @@ async fn send_signals(
async fn receive_signals(
mut receive_stream: ReceiveStream,
speaker_sender: Arc<broadcast::Sender<SignedAudioDatum>>,
speaker_sender: Arc<broadcast::Sender<Signal>>,
) {
let mut network_buffer = [0; NETWORK_BUFFER_LENGTH];
let mut network_buffer = [0; NETWORK_DATA_LENGTH];
loop {
match receive_stream.read_exact(&mut network_buffer).await {
Ok(_) => match Signal::unpack_signal(&network_buffer) {
Ok(received_signal) => match received_signal.signal_type {
SignalType::AudioDatum => match received_signal.unpack_audio() {
Ok(signed_audio_datum) => {
let _ = speaker_sender.send(signed_audio_datum);
}
Err(err_val) => {
eprintln!("Error: Unpack Audio | {}", err_val);
println!("Warning: Illegal Operation");
return;
}
},
SignalType::SpeakerLeft => todo!(),
},
Ok(_) => match Signal::unpack(network_buffer) {
Ok(signal) => {
let _ = speaker_sender.send(signal);
}
Err(err_val) => {
eprintln!("Error: Unpack Signal | {}", err_val);
}

View file

@ -1,11 +1,30 @@
use std::sync::Arc;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use protocol::Error;
use protocol::{
Error,
protocol::{DEFAULT_SAMPLE_RATE, Signal, SignalType},
};
use rodio::{OutputStream, OutputStreamBuilder, Sink, buffer::SamplesBuffer, mixer::Mixer};
use tokio::sync::{broadcast, mpsc};
use crate::gui::State;
struct SpeakerSink {
speaker_id: u8,
sink: Sink,
}
impl SpeakerSink {
fn new(speaker_id: u8, mixer: &Mixer) -> Self {
let sink = Sink::connect_new(mixer);
Self { speaker_id, sink }
}
}
pub async fn record(
mut record_control: mpsc::Receiver<State>,
record_sender: Arc<broadcast::Sender<f32>>,
@ -61,66 +80,85 @@ pub async fn record(
})
}
pub async fn play(
mut play_control: mpsc::Receiver<State>,
mut play_receiver: broadcast::Receiver<f32>,
) -> Result<(), Error> {
let host = cpal::default_host();
let output_device = host.default_output_device().unwrap();
let config = output_device.default_output_config().unwrap().into();
println!("Speaker Stream Config = {:#?}", config);
async fn signal_handler(
output_stream: OutputStream,
mut play_receiver: broadcast::Receiver<Signal>,
play_pause: Arc<AtomicBool>,
) {
let mut speaker_list = vec![];
let output = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
for sample in data {
match play_receiver.try_recv() {
Ok(received_sample) => *sample = received_sample,
Err(err_val) => match err_val {
broadcast::error::TryRecvError::Empty => *sample = 0.0,
broadcast::error::TryRecvError::Closed => {
eprintln!("Error: Speaker Receive | Local Channel | Channel Closed");
return;
while let Ok(signal) = play_receiver.recv().await {
match signal.get_signal_type() {
SignalType::AudioDatum => {
let data = if play_pause.load(std::sync::atomic::Ordering::Relaxed) {
[signal.get_audio_datum()]
} else {
[0.0]
};
let source = SamplesBuffer::new(2, DEFAULT_SAMPLE_RATE, data);
match speaker_list.binary_search_by(|speaker_sink: &SpeakerSink| {
speaker_sink.speaker_id.cmp(&signal.get_speaker_id())
}) {
Ok(speaker_sink_index) => {
let speaker_sink = speaker_list.get(speaker_sink_index).expect("Never");
speaker_sink.sink.append(source);
}
broadcast::error::TryRecvError::Lagged(lag_amount) => {
eprintln!(
"Error: Speaker Receive | Local Channel | Lagging by -> {}",
lag_amount
);
play_receiver = play_receiver.resubscribe();
Err(_) => {
let speaker_sink =
SpeakerSink::new(signal.get_speaker_id(), output_stream.mixer());
speaker_sink.sink.append(source);
speaker_list.push(speaker_sink);
speaker_list.sort_by(|x, y| x.speaker_id.cmp(&y.speaker_id));
}
},
}
}
SignalType::SpeakerLeft => {
speaker_list
.retain(|speaker_sink| speaker_sink.speaker_id != signal.get_speaker_id());
}
}
};
}
}
let output_stream = output_device
.build_output_stream(&config, output, voice_error, None)
.unwrap();
pub async fn play(
mut play_control: mpsc::Receiver<State>,
play_receiver: broadcast::Receiver<Signal>,
) {
let output_stream = OutputStreamBuilder::open_default_stream().unwrap();
let play_pause = Arc::new(AtomicBool::new(true));
output_stream
.play()
.map_err(|inner| Error::Play(inner.to_string()))?;
let signal_handler = tokio::spawn(signal_handler(
output_stream,
play_receiver,
play_pause.clone(),
));
tokio::task::block_in_place(|| {
loop {
match play_control.blocking_recv() {
Some(message) => match message {
State::Active => output_stream
.play()
.map_err(|inner| Error::Play(inner.to_string()))?,
State::Passive => output_stream
.pause()
.map_err(|inner| Error::Play(inner.to_string()))?,
Some(requested_state) => match requested_state {
State::Active => {
play_pause.store(true, Ordering::Relaxed);
}
State::Passive => {
play_pause.store(false, Ordering::Relaxed);
}
State::Loading => {}
},
None => {
output_stream
.pause()
.map_err(|inner| Error::Play(inner.to_string()))?;
return Ok(());
signal_handler.abort();
return;
}
}
}
})
});
signal_handler.abort();
}
fn voice_error(err_val: cpal::StreamError) {