add some comments

This commit is contained in:
cediackermann 2025-01-27 12:35:41 +01:00
parent 9e3b573406
commit 044d3911dd
No known key found for this signature in database
GPG key ID: 00C575502DDA2430
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 std::net::UdpSocket;
// Custom error type for binding errors
pub struct BindError {
pub message: String,
}
// Type alias for Result with BindError as the error type
type Result<T> = std::result::Result<T, BindError>;
// Trait for handling client messages
pub trait ClientHandler {
// Function to handle incoming messages
fn handle_message(msg: Message) {
match msg {
// Match on the message type and handle accordingly
Message::PlaySound(play_sound) => {
// Delegate to SoundScheduler to handle the play sound message
SoundScheduler::handle_scheduled_sound(play_sound);
}
}
}
}
// Trait for listening to incoming messages
pub trait Listener: ClientHandler {
// Function to start listening on a given port
fn listen(port: u16) -> Result<()>;
}
// Struct representing a network listener
pub struct NetworkListener;
// Implement ClientHandler for NetworkListener
impl ClientHandler for NetworkListener {}
// Implement Listener for NetworkListener
impl Listener for NetworkListener {
fn listen(port: u16) -> Result<()> {
// Create the bind address string
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 = match socket {
Ok(s) => s,
Ok(s) => s, // If successful, use the socket
Err(e) => {
// If there's an error, return a BindError
return Err(BindError {
message: e.to_string(),
});
}
};
// Print the local address the socket is bound to
println!("Listening on {}", socket.local_addr().unwrap());
// Enter an infinite loop to listen for incoming messages
loop {
// Buffer to store incoming data
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");
// Print the amount of data received and the source address
println!("Received {} bytes from {}", amt, src);
// Slice the buffer to the actual amount of data received
let data = &mut data[..amt];
// Deserialize the data into a Message
let msg = bincode::deserialize::<Message>(data).expect("Failed to deserialize");
// Handle the deserialized message
NetworkListener::handle_message(msg);
}
}

View file

@ -8,62 +8,77 @@ use rodio::{Decoder, Source};
use crate::protocol::{Message, PlaySound};
// Define a trait for dispatching tasks
pub trait Dispatcher {
// Method to handle dispatching tasks to multiple addresses
fn handle_dispatch(addrs: Vec<String>, sound_path: String);
}
// Struct for network-based dispatching
pub struct NetworkDispatcher;
impl NetworkDispatcher {
// Load a sound file and return a Decoder
fn load_sound(path: String) -> Decoder<BufReader<File>> {
let file = BufReader::new(File::open(path).unwrap());
Decoder::new(file).unwrap()
let file = BufReader::new(File::open(path).unwrap()); // Open the file
Decoder::new(file).unwrap() // Decode the file
}
}
impl Dispatcher for NetworkDispatcher {
fn handle_dispatch(addrs: Vec<String>, sound_path: String) {
// Get the current system time since UNIX_EPOCH
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
// Load and buffer the sound source
let source = NetworkDispatcher::load_sound(sound_path).buffered();
// Get sound properties
let channels = source.channels();
let sample_rate = source.sample_rate();
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 {
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 || {
// Bind to a local UDP socket
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 {
// Collect sound data for the current chunk
let sound_data = source
.clone()
.skip_duration(offset)
.take_duration(chunk_len)
.collect::<Vec<i16>>();
offset += chunk_len;
offset += chunk_len; // Update the offset
// Create a PlaySound message
let msg = Message::PlaySound(PlaySound {
timestamp: (now + offset).as_micros(),
timestamp: (now + offset).as_micros(), // Timestamp for synchronization
channels: channels,
sample_rate: sample_rate,
sound_data: sound_data,
});
// Serialize the message
let buf = bincode::serialize(&msg).expect("Failed to serialize");
// Send the serialized message via UDP
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 std::thread;
use ui_app::UIApp;
use eframe::egui;
// Declare the modules used in this project
mod client_handler;
mod dispatcher;
mod protocol;
@ -14,37 +16,41 @@ mod sound_scheduler;
mod ui_app;
fn main() -> eframe::Result {
// Define options for the eframe application
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()
};
// Spawn a new thread to handle network listening
thread::spawn(move || {
// Attempt to listen on port 3000
let result = NetworkListener::listen(3000);
match result {
Ok(_) => {}
Err(e) => {
eprintln!("Failed to bind: {}", e.message);
// Try with port 3001
eprintln!("Failed to bind: {}", e.message); // Print error message if binding fails
// Try with port 3001 if port 3000 fails
let result = NetworkListener::listen(3001);
match result {
Ok(_) => {}
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(
"My egui App",
options,
"My egui App", // Application title
options, // Application options
Box::new(|cc| {
// 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 {
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 timestamp: u128,
pub channels: u16,
pub sample_rate: u32,
pub sound_data: Vec<i16>,
pub timestamp: u128, // The timestamp when the sound should be played
pub channels: u16, // The number of audio channels (e.g., 2 for stereo)
pub sample_rate: u32, // The sample rate of the audio (e.g., 44100 for 44.1 kHz)
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};
// Define a struct for the SoundPlayer
pub struct 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>) {
// let data = Cursor::new(sound_data);
// Create a SamplesBuffer from the provided 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();
// Create a new Sink (audio output) using the stream handle
let sink = Sink::try_new(&stream_handle).unwrap();
// Append the sound source to the sink
sink.append(source);
// Block the current thread until the sound finishes playing
sink.sleep_until_end();
}
}

View file

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

View file

@ -1,33 +1,43 @@
use crate::dispatcher::{Dispatcher, NetworkDispatcher};
use eframe::egui;
use rfd::FileDialog;
use std::path::PathBuf;
use crate::dispatcher::{Dispatcher, NetworkDispatcher}; // Importing necessary modules and traits
use eframe::egui; // Importing eframe's egui module for UI
use rfd::FileDialog; // Importing rfd's FileDialog for file picking
use std::path::PathBuf; // Importing PathBuf for handling file paths
// Struct representing the UI application
pub struct UIApp {
picked_file: Option<PathBuf>,
addresses: Vec<String>,
picked_file: Option<PathBuf>, // Optional field to store the picked file path
addresses: Vec<String>, // Vector to store target device addresses
}
// Implementing the Default trait for UIApp
impl Default for UIApp {
fn default() -> Self {
Self {
picked_file: None,
addresses: Vec::new(),
picked_file: None, // Initially no file is picked
addresses: Vec::new(), // Initially no addresses are added
}
}
}
// Implementing the eframe::App trait for UIApp
impl eframe::App for UIApp {
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() {
self.addresses.push("localhost:3000".to_owned());
}
// Creating the central panel for the 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() {
ui.horizontal(|ui| {
ui.label("Enter IP of target device: ");
ui.text_edit_singleline(address);
ui.label("Enter IP of target device: "); // Label for the address input
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 let Some(path) = &self.picked_file {
NetworkDispatcher::handle_dispatch(
@ -36,21 +46,29 @@ impl eframe::App for UIApp {
);
}
}
// Button to remove the address
if ui.button("Remove").clicked() {
to_remove.push(i);
}
});
}
// Removing addresses that were marked for removal
for i in to_remove.iter().rev() {
self.addresses.remove(*i);
}
// Button to add a new address
if ui.button("Add address").clicked() {
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() {
let file = FileDialog::new().pick_file();
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.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 play sound on all target devices
if ui.button("Play sound").clicked() {
if let Some(path) = &self.picked_file {
NetworkDispatcher::handle_dispatch(