Merge pull request #2 from silvio2402/addComments

add some comments
This commit is contained in:
Silvio Brändle 2025-01-27 12:36:25 +01:00 committed by GitHub
commit ccb46ce1ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 138 additions and 46 deletions

View file

@ -2,51 +2,73 @@ use crate::protocol::Message;
use crate::sound_scheduler::SoundScheduler; use crate::sound_scheduler::SoundScheduler;
use std::net::UdpSocket; use std::net::UdpSocket;
// Custom error type for binding errors
pub struct BindError { pub struct BindError {
pub message: String, pub message: String,
} }
// Type alias for Result with BindError as the error type
type Result<T> = std::result::Result<T, BindError>; type Result<T> = std::result::Result<T, BindError>;
// Trait for handling client messages
pub trait ClientHandler { pub trait ClientHandler {
// Function to handle incoming messages
fn handle_message(msg: Message) { fn handle_message(msg: Message) {
match msg { match msg {
// Match on the message type and handle accordingly
Message::PlaySound(play_sound) => { Message::PlaySound(play_sound) => {
// Delegate to SoundScheduler to handle the play sound message
SoundScheduler::handle_scheduled_sound(play_sound); SoundScheduler::handle_scheduled_sound(play_sound);
} }
} }
} }
} }
pub trait Listener : ClientHandler { // Trait for listening to incoming messages
pub trait Listener: ClientHandler {
// Function to start listening on a given port
fn listen(port: u16) -> Result<()>; fn listen(port: u16) -> Result<()>;
} }
// Struct representing a network listener
pub struct NetworkListener; pub struct NetworkListener;
// Implement ClientHandler for NetworkListener
impl ClientHandler for NetworkListener {} impl ClientHandler for NetworkListener {}
// Implement Listener for NetworkListener
impl Listener for NetworkListener { impl Listener for NetworkListener {
fn listen(port: u16) -> Result<()> { fn listen(port: u16) -> Result<()> {
// Create the bind address string
let bind_addr = format!("0.0.0.0:{}", port); let bind_addr = format!("0.0.0.0:{}", port);
// Attempt to bind the UDP socket to the address
let socket = UdpSocket::bind(bind_addr); let socket = UdpSocket::bind(bind_addr);
let socket = match socket { let socket = match socket {
Ok(s) => s, Ok(s) => s, // If successful, use the socket
Err(e) => { Err(e) => {
// If there's an error, return a BindError
return Err(BindError { return Err(BindError {
message: e.to_string(), message: e.to_string(),
}); });
} }
}; };
// Print the local address the socket is bound to
println!("Listening on {}", socket.local_addr().unwrap()); println!("Listening on {}", socket.local_addr().unwrap());
// Enter an infinite loop to listen for incoming messages
loop { loop {
// Buffer to store incoming data
let mut data = [0; 1_048_576]; let mut data = [0; 1_048_576];
// Receive data from the socket
let (amt, src) = socket.recv_from(&mut data).expect("Didn't receive data"); let (amt, src) = socket.recv_from(&mut data).expect("Didn't receive data");
// Print the amount of data received and the source address
println!("Received {} bytes from {}", amt, src); println!("Received {} bytes from {}", amt, src);
// Slice the buffer to the actual amount of data received
let data = &mut data[..amt]; let data = &mut data[..amt];
// Deserialize the data into a Message
let msg = bincode::deserialize::<Message>(data).expect("Failed to deserialize"); let msg = bincode::deserialize::<Message>(data).expect("Failed to deserialize");
// Handle the deserialized message
NetworkListener::handle_message(msg); NetworkListener::handle_message(msg);
} }
} }

View file

@ -8,62 +8,77 @@ use rodio::{Decoder, Source};
use crate::protocol::{Message, PlaySound}; use crate::protocol::{Message, PlaySound};
// Define a trait for dispatching tasks
pub trait Dispatcher { pub trait Dispatcher {
// Method to handle dispatching tasks to multiple addresses
fn handle_dispatch(addrs: Vec<String>, sound_path: String); fn handle_dispatch(addrs: Vec<String>, sound_path: String);
} }
// Struct for network-based dispatching
pub struct NetworkDispatcher; pub struct NetworkDispatcher;
impl NetworkDispatcher { impl NetworkDispatcher {
// Load a sound file and return a Decoder
fn load_sound(path: String) -> Decoder<BufReader<File>> { fn load_sound(path: String) -> Decoder<BufReader<File>> {
let file = BufReader::new(File::open(path).unwrap()); let file = BufReader::new(File::open(path).unwrap()); // Open the file
Decoder::new(file).unwrap() Decoder::new(file).unwrap() // Decode the file
} }
} }
impl Dispatcher for NetworkDispatcher { impl Dispatcher for NetworkDispatcher {
fn handle_dispatch(addrs: Vec<String>, sound_path: String) { fn handle_dispatch(addrs: Vec<String>, sound_path: String) {
// Get the current system time since UNIX_EPOCH
let now = SystemTime::now() let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.unwrap(); .unwrap();
// Load and buffer the sound source
let source = NetworkDispatcher::load_sound(sound_path).buffered(); let source = NetworkDispatcher::load_sound(sound_path).buffered();
// Get sound properties
let channels = source.channels(); let channels = source.channels();
let sample_rate = source.sample_rate(); let sample_rate = source.sample_rate();
let duration = source.total_duration().unwrap(); let duration = source.total_duration().unwrap();
let chunk_len = Duration::from_millis(200); let chunk_len = Duration::from_millis(200); // Length of each chunk to send
let mut offset = Duration::from_secs(0); let mut offset = Duration::from_secs(0); // Initial offset
// Iterate over each address
for addr in addrs { for addr in addrs {
if addr.is_empty() { if addr.is_empty() {
continue; continue; // Skip empty addresses
} }
let source = source.clone(); let source = source.clone(); // Clone the source for each thread
// Spawn a new thread for each address
thread::spawn(move || { thread::spawn(move || {
// Bind to a local UDP socket
let sock = UdpSocket::bind("0.0.0.0:0").expect("Failed to bind"); let sock = UdpSocket::bind("0.0.0.0:0").expect("Failed to bind");
sock.connect(addr.clone()).expect("Failed to connect"); sock.connect(addr.clone()).expect("Failed to connect"); // Connect to the address
// Loop until the entire duration is covered
while offset < duration { while offset < duration {
// Collect sound data for the current chunk
let sound_data = source let sound_data = source
.clone() .clone()
.skip_duration(offset) .skip_duration(offset)
.take_duration(chunk_len) .take_duration(chunk_len)
.collect::<Vec<i16>>(); .collect::<Vec<i16>>();
offset += chunk_len; offset += chunk_len; // Update the offset
// Create a PlaySound message
let msg = Message::PlaySound(PlaySound { let msg = Message::PlaySound(PlaySound {
timestamp: (now + offset).as_micros(), timestamp: (now + offset).as_micros(), // Timestamp for synchronization
channels: channels, channels: channels,
sample_rate: sample_rate, sample_rate: sample_rate,
sound_data: sound_data, sound_data: sound_data,
}); });
// Serialize the message
let buf = bincode::serialize(&msg).expect("Failed to serialize"); let buf = bincode::serialize(&msg).expect("Failed to serialize");
// Send the serialized message via UDP
sock.send(&buf).expect("Failed to send"); sock.send(&buf).expect("Failed to send");
} }
}); });

View file

@ -1,11 +1,13 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // Hide console window on Windows in release mode
// Import necessary modules and crates
use client_handler::{Listener, NetworkListener}; use client_handler::{Listener, NetworkListener};
use std::thread; use std::thread;
use ui_app::UIApp; use ui_app::UIApp;
use eframe::egui; use eframe::egui;
// Declare the modules used in this project
mod client_handler; mod client_handler;
mod dispatcher; mod dispatcher;
mod protocol; mod protocol;
@ -14,37 +16,41 @@ mod sound_scheduler;
mod ui_app; mod ui_app;
fn main() -> eframe::Result { fn main() -> eframe::Result {
// Define options for the eframe application
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([700.0, 400.0]), viewport: egui::ViewportBuilder::default().with_inner_size([700.0, 400.0]), // Set initial window size
..Default::default() ..Default::default()
}; };
// Spawn a new thread to handle network listening
thread::spawn(move || { thread::spawn(move || {
// Attempt to listen on port 3000
let result = NetworkListener::listen(3000); let result = NetworkListener::listen(3000);
match result { match result {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
eprintln!("Failed to bind: {}", e.message); eprintln!("Failed to bind: {}", e.message); // Print error message if binding fails
// Try with port 3001 // Try with port 3001 if port 3000 fails
let result = NetworkListener::listen(3001); let result = NetworkListener::listen(3001);
match result { match result {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
eprintln!("Failed to bind again: {}", e.message); eprintln!("Failed to bind again: {}", e.message); // Print error message if binding fails again
} }
} }
} }
} }
}); });
// Run the eframe application
eframe::run_native( eframe::run_native(
"My egui App", "My egui App", // Application title
options, options, // Application options
Box::new(|cc| { Box::new(|cc| {
// This gives us image support: // This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx); // Install image loaders for egui context
Ok(Box::<UIApp>::default()) Ok(Box::<UIApp>::default()) // Initialize the UI application
}), }),
) )
} }

View file

@ -1,14 +1,16 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize}; // Importing necessary traits from serde for serialization and deserialization
#[derive(Serialize, Deserialize, Debug)] // Define an enum called Message which can hold different types of messages
#[derive(Serialize, Deserialize, Debug)] // Automatically generate code for serialization, deserialization, and debugging
pub enum Message { pub enum Message {
PlaySound(PlaySound), PlaySound(PlaySound), // A variant of the Message enum that holds a PlaySound struct
} }
#[derive(Serialize, Deserialize, Debug)] // Define a struct called PlaySound which represents a sound to be played
#[derive(Serialize, Deserialize, Debug)] // Automatically generate code for serialization, deserialization, and debugging
pub struct PlaySound { pub struct PlaySound {
pub timestamp: u128, pub timestamp: u128, // The timestamp when the sound should be played
pub channels: u16, pub channels: u16, // The number of audio channels (e.g., 2 for stereo)
pub sample_rate: u32, pub sample_rate: u32, // The sample rate of the audio (e.g., 44100 for 44.1 kHz)
pub sound_data: Vec<i16>, pub sound_data: Vec<i16>, // The actual sound data as a vector of 16-bit integers
} }

View file

@ -1,15 +1,24 @@
use rodio::{buffer::SamplesBuffer, OutputStream, Sink}; use rodio::{buffer::SamplesBuffer, OutputStream, Sink};
// Define a struct for the SoundPlayer
pub struct SoundPlayer; pub struct SoundPlayer;
impl SoundPlayer { impl SoundPlayer {
// Function to play sound given channels, sample rate, and sound data
pub fn play_sound(channels: u16, sample_rate: u32, sound_data: Vec<i16>) { pub fn play_sound(channels: u16, sample_rate: u32, sound_data: Vec<i16>) {
// let data = Cursor::new(sound_data); // Create a SamplesBuffer from the provided sound data
let source = SamplesBuffer::new(channels, sample_rate, sound_data); let source = SamplesBuffer::new(channels, sample_rate, sound_data);
// Try to get the default output stream and handle
let (_stream, stream_handle) = OutputStream::try_default().unwrap(); let (_stream, stream_handle) = OutputStream::try_default().unwrap();
// Create a new Sink (audio output) using the stream handle
let sink = Sink::try_new(&stream_handle).unwrap(); let sink = Sink::try_new(&stream_handle).unwrap();
// Append the sound source to the sink
sink.append(source); sink.append(source);
// Block the current thread until the sound finishes playing
sink.sleep_until_end(); sink.sleep_until_end();
} }
} }

View file

@ -3,33 +3,51 @@ use crate::sound_player::SoundPlayer;
use std::thread::{self, sleep}; use std::thread::{self, sleep};
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
// Define a struct for the SoundScheduler
pub struct SoundScheduler; pub struct SoundScheduler;
impl SoundScheduler { impl SoundScheduler {
// Function to handle scheduled sound playback
pub fn handle_scheduled_sound(play_msg: PlaySound) { pub fn handle_scheduled_sound(play_msg: PlaySound) {
// Spawn a new thread to handle the sound playback
thread::spawn(move || { thread::spawn(move || {
// Destructure the PlaySound message
let PlaySound { let PlaySound {
channels, channels,
sample_rate, sample_rate,
sound_data, sound_data,
timestamp, timestamp,
} = play_msg; } = play_msg;
// Print the length of the sound data and the scheduled timestamp
println!( println!(
"Scheduled sound: len={:?} at {:?}", "Scheduled sound: len={:?} at {:?}",
sound_data.len(), sound_data.len(),
timestamp timestamp
); );
// Get the current time in microseconds since UNIX_EPOCH
let now_micros = SystemTime::now() let now_micros = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("Could not get time") .expect("Could not get time")
.as_micros(); .as_micros();
// Calculate the wait time in microseconds
let wait = timestamp as i128 - now_micros as i128; let wait = timestamp as i128 - now_micros as i128;
// If the wait time is less than or equal to zero, it's too late to play the sound
if wait <= 0 { if wait <= 0 {
println!("Too late to play sound"); println!("Too late to play sound");
return; return;
} }
// Sleep for the calculated wait time
sleep(Duration::from_micros(wait as u64)); sleep(Duration::from_micros(wait as u64));
// Print a message indicating that the sound is being played
println!("Playing sound..."); println!("Playing sound...");
// Play the sound using the SoundPlayer
SoundPlayer::play_sound(channels, sample_rate, sound_data); SoundPlayer::play_sound(channels, sample_rate, sound_data);
}); });
} }

View file

@ -1,33 +1,43 @@
use crate::dispatcher::{Dispatcher, NetworkDispatcher}; use crate::dispatcher::{Dispatcher, NetworkDispatcher}; // Importing necessary modules and traits
use eframe::egui; use eframe::egui; // Importing eframe's egui module for UI
use rfd::FileDialog; use rfd::FileDialog; // Importing rfd's FileDialog for file picking
use std::path::PathBuf; use std::path::PathBuf; // Importing PathBuf for handling file paths
// Struct representing the UI application
pub struct UIApp { pub struct UIApp {
picked_file: Option<PathBuf>, picked_file: Option<PathBuf>, // Optional field to store the picked file path
addresses: Vec<String>, addresses: Vec<String>, // Vector to store target device addresses
} }
// Implementing the Default trait for UIApp
impl Default for UIApp { impl Default for UIApp {
fn default() -> Self { fn default() -> Self {
Self { Self {
picked_file: None, picked_file: None, // Initially no file is picked
addresses: Vec::new(), addresses: Vec::new(), // Initially no addresses are added
} }
} }
} }
// Implementing the eframe::App trait for UIApp
impl eframe::App for UIApp { impl eframe::App for UIApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// If no addresses are present, add a default address
if self.addresses.is_empty() { if self.addresses.is_empty() {
self.addresses.push("localhost:3000".to_owned()); self.addresses.push("localhost:3000".to_owned());
} }
// Creating the central panel for the UI
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
let mut to_remove = Vec::new(); let mut to_remove = Vec::new(); // Vector to store indices of addresses to be removed
// Iterating over addresses to create UI elements for each
for (i, address) in self.addresses.iter_mut().enumerate() { for (i, address) in self.addresses.iter_mut().enumerate() {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Enter IP of target device: "); ui.label("Enter IP of target device: "); // Label for the address input
ui.text_edit_singleline(address); ui.text_edit_singleline(address); // Text input for the address
// Button to play sound on the target device
if ui.button("Play sound").clicked() { if ui.button("Play sound").clicked() {
if let Some(path) = &self.picked_file { if let Some(path) = &self.picked_file {
NetworkDispatcher::handle_dispatch( NetworkDispatcher::handle_dispatch(
@ -36,21 +46,29 @@ impl eframe::App for UIApp {
); );
} }
} }
// Button to remove the address
if ui.button("Remove").clicked() { if ui.button("Remove").clicked() {
to_remove.push(i); to_remove.push(i);
} }
}); });
} }
// Removing addresses that were marked for removal
for i in to_remove.iter().rev() { for i in to_remove.iter().rev() {
self.addresses.remove(*i); self.addresses.remove(*i);
} }
// Button to add a new address
if ui.button("Add address").clicked() { if ui.button("Add address").clicked() {
self.addresses.push("localhost:3000".to_owned()); self.addresses.push("localhost:3000".to_owned());
} }
ui.add_space(25.0);
ui.separator();
ui.add_space(25.0);
ui.add_space(25.0); // Adding space in the UI
ui.separator(); // Adding a separator line
ui.add_space(25.0); // Adding more space
// Button to pick a file
if ui.button("Pick file").clicked() { if ui.button("Pick file").clicked() {
let file = FileDialog::new().pick_file(); let file = FileDialog::new().pick_file();
if !file.is_none() { if !file.is_none() {
@ -58,12 +76,14 @@ impl eframe::App for UIApp {
} }
} }
// Displaying the picked file path
ui.label(format!("Picked file: {:?}", self.picked_file)); ui.label(format!("Picked file: {:?}", self.picked_file));
ui.add_space(25.0); ui.add_space(25.0); // Adding space in the UI
ui.separator(); ui.separator(); // Adding a separator line
ui.add_space(25.0); ui.add_space(25.0); // Adding more space
// Button to play sound on all target devices
if ui.button("Play sound").clicked() { if ui.button("Play sound").clicked() {
if let Some(path) = &self.picked_file { if let Some(path) = &self.picked_file {
NetworkDispatcher::handle_dispatch( NetworkDispatcher::handle_dispatch(