add some comments
This commit is contained in:
parent
9e3b573406
commit
044d3911dd
7 changed files with 138 additions and 46 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trait for listening to incoming messages
|
||||||
pub trait Listener: ClientHandler {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
24
src/main.rs
24
src/main.rs
|
|
@ -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
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue