feat: ✨ base project structure
This commit is contained in:
parent
e2043f1009
commit
1567a9c32a
15 changed files with 5568 additions and 1 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
5277
Cargo.lock
generated
Normal file
5277
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"client", "protocol", "server",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.45.0", features = ["full"] }
|
||||
s2n-quic = "1.58.0"
|
|
@ -1,2 +1,2 @@
|
|||
# tcid
|
||||
# rust_communication
|
||||
|
||||
|
|
14
client/Cargo.toml
Normal file
14
client/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
protocol = { path = "../protocol" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
s2n-quic = { workspace = true }
|
||||
iced = { features = ["tokio"], git = "https://github.com/iced-rs/iced", rev = "d39022432c778a8cda455f40b9c12245db86ce45" }
|
||||
cpal = "0.15.3"
|
42
client/src/gui.rs
Normal file
42
client/src/gui.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use iced::{
|
||||
Alignment::Center,
|
||||
Element, Task, Theme,
|
||||
widget::{button, column, text},
|
||||
};
|
||||
|
||||
use crate::{ClientConfig, stream::connect};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Message {
|
||||
JoinRoom,
|
||||
LeaveRoom,
|
||||
MuteMicrophone,
|
||||
UnmuteMicrophone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Data {
|
||||
client_config: Option<ClientConfig>,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match message {
|
||||
Message::JoinRoom => Task::none(),
|
||||
Message::LeaveRoom => Task::none(),
|
||||
Message::MuteMicrophone => Task::none(),
|
||||
Message::UnmuteMicrophone => Task::none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, Message> {
|
||||
column![button("Join Room"), text("Hello").size(50)]
|
||||
.padding(10)
|
||||
.align_x(Center)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> Theme {
|
||||
Theme::Dark
|
||||
}
|
||||
}
|
8
client/src/lib.rs
Normal file
8
client/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
pub mod gui;
|
||||
pub mod stream;
|
||||
pub mod voice;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ClientConfig {
|
||||
server_address: String,
|
||||
}
|
10
client/src/main.rs
Normal file
10
client/src/main.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use client::gui::Data;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
iced::application(Data::default, Data::update, Data::view)
|
||||
.theme(Data::theme)
|
||||
.run()
|
||||
.unwrap()
|
||||
}
|
65
client/src/stream.rs
Normal file
65
client/src/stream.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use std::{io, net::SocketAddr, path::Path};
|
||||
|
||||
use protocol::BUFFER_LENGTH;
|
||||
use s2n_quic::{Client, client::Connect};
|
||||
use tokio::{
|
||||
io::AsyncReadExt,
|
||||
sync::{broadcast, oneshot},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ClientConfig,
|
||||
voice::{play, record},
|
||||
};
|
||||
|
||||
pub async fn connect(client_config: &ClientConfig) {
|
||||
let client = Client::builder()
|
||||
.with_io("0:0")
|
||||
.unwrap()
|
||||
.with_tls(Path::new("certificates/cert.pem"))
|
||||
.unwrap()
|
||||
.start()
|
||||
.unwrap();
|
||||
|
||||
println!("Client Address = {}", client.local_addr().unwrap());
|
||||
let connect = Connect::new(client_config.server_address.parse::<SocketAddr>().unwrap())
|
||||
.with_server_name("localhost");
|
||||
let mut connection = match client.connect(connect).await {
|
||||
Ok(connection) => connection,
|
||||
Err(err_val) => {
|
||||
eprintln!("Error: Client Connection | {}", err_val);
|
||||
return;
|
||||
}
|
||||
};
|
||||
connection.keep_alive(true).unwrap();
|
||||
|
||||
let stream = connection.open_bidirectional_stream().await.unwrap();
|
||||
let (mut receive_stream, mut send_stream) = stream.split();
|
||||
|
||||
let (microphone_sender, mut microphone_receiver) = broadcast::channel::<f32>(BUFFER_LENGTH);
|
||||
let (speaker_sender, speaker_receiver) = broadcast::channel::<f32>(BUFFER_LENGTH);
|
||||
|
||||
let (microphone_stop_signal_sender, microphone_stop_signal_receiver) =
|
||||
oneshot::channel::<bool>();
|
||||
let (spearker_stop_signal_sender, speaker_stop_signal_receiver) = oneshot::channel::<bool>();
|
||||
|
||||
tokio::spawn(play(speaker_receiver, speaker_stop_signal_receiver));
|
||||
tokio::spawn(record(microphone_sender, microphone_stop_signal_receiver));
|
||||
tokio::spawn(async move {
|
||||
while let Ok(data) = receive_stream.read_f32_le().await {
|
||||
speaker_sender.send(data).unwrap();
|
||||
}
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
while let Ok(data) = microphone_receiver.recv().await {
|
||||
send_stream
|
||||
.send(data.to_le_bytes().to_vec().into())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
let mut read_buffer = String::default();
|
||||
io::stdin().read_line(&mut read_buffer).unwrap();
|
||||
microphone_stop_signal_sender.send(true).unwrap();
|
||||
spearker_stop_signal_sender.send(true).unwrap();
|
||||
}
|
72
client/src/voice.rs
Normal file
72
client/src/voice.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use tokio::sync::{broadcast, oneshot};
|
||||
|
||||
pub async fn record(
|
||||
microphone_sender: broadcast::Sender<f32>,
|
||||
stop_signal_receiver: oneshot::Receiver<bool>,
|
||||
) {
|
||||
let host = cpal::default_host();
|
||||
let input_device = host.default_input_device().unwrap();
|
||||
let config = input_device.default_input_config().unwrap().into();
|
||||
|
||||
let input = move |data: &[f32], _: &cpal::InputCallbackInfo| {
|
||||
for &sample in data {
|
||||
if microphone_sender.receiver_count() > 0 {
|
||||
if let Err(err_val) = microphone_sender.send(sample) {
|
||||
eprintln!("Error: Microphone Send | {}", err_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_stream = input_device
|
||||
.build_input_stream(&config, input, voice_error, None)
|
||||
.unwrap();
|
||||
input_stream.play().unwrap();
|
||||
println!("Recording Started");
|
||||
|
||||
tokio::task::block_in_place(|| {
|
||||
let _ = stop_signal_receiver.blocking_recv();
|
||||
});
|
||||
|
||||
input_stream.pause().unwrap();
|
||||
}
|
||||
|
||||
pub async fn play(
|
||||
mut speaker_receiver: broadcast::Receiver<f32>,
|
||||
stop_signal_receiver: oneshot::Receiver<bool>,
|
||||
) {
|
||||
let host = cpal::default_host();
|
||||
let output_device = host.default_output_device().unwrap();
|
||||
let config = output_device.default_output_config().unwrap().into();
|
||||
|
||||
let output = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
|
||||
for sample in data {
|
||||
if speaker_receiver.len() > 0 {
|
||||
match speaker_receiver.blocking_recv() {
|
||||
Ok(received_sample) => *sample = received_sample,
|
||||
Err(err_val) => eprintln!("Error: Speaker Receive | {}", err_val),
|
||||
}
|
||||
} else {
|
||||
*sample = 0.0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let output_stream = output_device
|
||||
.build_output_stream(&config, output, voice_error, None)
|
||||
.unwrap();
|
||||
|
||||
output_stream.play().unwrap();
|
||||
println!("Playing Started");
|
||||
|
||||
tokio::task::block_in_place(|| {
|
||||
let _ = stop_signal_receiver.blocking_recv();
|
||||
});
|
||||
|
||||
output_stream.pause().unwrap();
|
||||
}
|
||||
|
||||
fn voice_error(err_val: cpal::StreamError) {
|
||||
eprintln!("Error: Voice Error | {}", err_val);
|
||||
}
|
8
protocol/Cargo.toml
Normal file
8
protocol/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "protocol"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
chrono = { workspace = true }
|
39
protocol/src/lib.rs
Normal file
39
protocol/src/lib.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use std::{collections::VecDeque, fs::File, io::Read};
|
||||
|
||||
pub const BUFFER_LENGTH: usize = 1024;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TOML {
|
||||
header: String,
|
||||
fields: VecDeque<String>,
|
||||
}
|
||||
|
||||
fn naive_toml_parser(file_location: &str) -> TOML {
|
||||
let mut toml_file = File::open(file_location).unwrap();
|
||||
let mut toml_ingredients = String::default();
|
||||
toml_file.read_to_string(&mut toml_ingredients).unwrap();
|
||||
let mut toml_ingredients = toml_ingredients.lines().collect::<VecDeque<&str>>();
|
||||
|
||||
let header = toml_ingredients
|
||||
.pop_front()
|
||||
.unwrap()
|
||||
.replace('[', "")
|
||||
.replace(']', "")
|
||||
.trim_end()
|
||||
.to_string();
|
||||
|
||||
let fields = toml_ingredients
|
||||
.iter()
|
||||
.map(|ingredient| {
|
||||
ingredient
|
||||
.split_once('=')
|
||||
.unwrap()
|
||||
.1
|
||||
.replace('"', "")
|
||||
.trim()
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
TOML { header, fields }
|
||||
}
|
11
server/Cargo.toml
Normal file
11
server/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
protocol = { path = "../protocol" }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
tokio = { workspace = true }
|
4
server/src/lib.rs
Normal file
4
server/src/lib.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
#[derive(Debug)]
|
||||
struct ServerConfig {
|
||||
server_address: String,
|
||||
}
|
4
server/src/main.rs
Normal file
4
server/src/main.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue