feat: async mDNS discovery; emit device events; auto-build sidecar in npm dev

This commit is contained in:
2026-01-13 16:17:53 +01:00
parent 91e55fa37c
commit ab3a86041a
4 changed files with 674 additions and 64 deletions

View File

@@ -3,17 +3,12 @@ use std::io::{BufRead, BufReader};
use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream, UdpSocket};
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
use mdns_sd::{ServiceDaemon, ServiceEvent};
use serde_json::json;
use tauri::{AppHandle, Manager, State, Emitter};
use tauri::{AppHandle, Manager, State};
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
use tauri_plugin_shell::ShellExt;
use reqwest;
@@ -110,12 +105,7 @@ fn spawn_standalone_cast_proxy(url: String, port: u16) -> Result<Child, String>
let ffmpeg_disp = ffmpeg.to_string_lossy();
let spawn = |codec: &str| -> Result<Child, String> {
let mut cmd = Command::new(&ffmpeg);
#[cfg(windows)]
{
cmd.creation_flags(CREATE_NO_WINDOW);
}
cmd
Command::new(&ffmpeg)
.arg("-nostdin")
.arg("-hide_banner")
.arg("-loglevel")
@@ -559,55 +549,40 @@ pub fn run() {
let controller = player::spawn_player_thread(shared);
app.manage(PlayerRuntime { shared, controller });
// Start mDNS discovery in background without blocking setup.
// This allows the main window to show immediately.
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
// Small delay to ensure window is fully initialized first.
tokio::time::sleep(Duration::from_millis(100)).await;
std::thread::spawn(move || {
let mdns = ServiceDaemon::new().expect("Failed to create daemon");
let receiver = mdns
.browse("_googlecast._tcp.local.")
.expect("Failed to browse");
while let Ok(event) = receiver.recv() {
match event {
ServiceEvent::ServiceResolved(info) => {
let name = info
.get_property_val_str("fn")
.or_else(|| Some(info.get_fullname()))
.unwrap()
.to_string();
let addresses = info.get_addresses();
let ip = addresses
.iter()
.find(|ip| ip.is_ipv4())
.or_else(|| addresses.iter().next());
thread::spawn(move || {
let mdns = ServiceDaemon::new().expect("Failed to create daemon");
let receiver = mdns
.browse("_googlecast._tcp.local.")
.expect("Failed to browse");
while let Ok(event) = receiver.recv() {
match event {
ServiceEvent::ServiceResolved(info) => {
let name = info
.get_property_val_str("fn")
.or_else(|| Some(info.get_fullname()))
.unwrap()
.to_string();
let addresses = info.get_addresses();
let ip = addresses
.iter()
.find(|ip| ip.is_ipv4())
.or_else(|| addresses.iter().next());
if let Some(ip) = ip {
let state = handle.state::<AppState>();
let mut devices = state.known_devices.lock().unwrap();
let ip_str = ip.to_string();
if !devices.contains_key(&name) {
println!("Discovered Cast Device: {} at {}", name, ip_str);
devices.insert(name.clone(), ip_str.clone());
// Emit event to frontend when new device is discovered.
let _ = handle.emit("cast-device-discovered", json!({
"name": name,
"ip": ip_str
}));
}
if let Some(ip) = ip {
let state = handle.state::<AppState>();
let mut devices = state.known_devices.lock().unwrap();
let ip_str = ip.to_string();
if !devices.contains_key(&name) {
//println!("Discovered Cast Device: {} at {}", name, ip_str);
devices.insert(name, ip_str);
}
}
_ => {}
}
_ => {}
}
});
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![