feat(qbzd): mDNS discovery + registration for LAN pairing
Daemon side: registers _qbz._tcp.local mDNS service on startup so desktop apps can discover it on the LAN. Uses hostname as service name (configurable via qbzd.toml). Desktop side: new qbzd_discovery.rs module with Tauri commands: - v2_qbzd_start_discovery: browse _qbz._tcp services - v2_qbzd_stop_discovery: stop browsing - v2_qbzd_get_devices: list discovered daemons Uses same mdns-sd crate already used for AirPlay discovery. cc
This commit is contained in:
5 files changed
+186
No files matched your search
@@ -44,6 +44,10 @@ log = { workspace = true }
|
||||
env_logger = "0.11"
|
||||
sysinfo = "0.34"
|
||||
|
||||
# mDNS discovery
|
||||
mdns-sd = "0.11"
|
||||
hostname = "0.4"
|
||||
|
||||
# Database
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
|
||||
|
||||
@@ -102,6 +102,22 @@ pub async fn run(mut config: DaemonConfig) -> Result<(), String> {
|
||||
// Start playback state polling loop (broadcasts to event bus)
|
||||
spawn_playback_loop(daemon.core.clone(), daemon.event_bus.clone());
|
||||
|
||||
// Register mDNS service for LAN discovery
|
||||
let _mdns_handle = if config.mdns.enabled {
|
||||
match register_mdns(&config) {
|
||||
Ok(handle) => {
|
||||
log::info!("[qbzd] mDNS registered: _qbz._tcp on port {}", config.server.port);
|
||||
Some(handle)
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[qbzd] mDNS registration failed: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"[qbzd] Daemon ready on {}:{}",
|
||||
config.server.bind,
|
||||
@@ -195,6 +211,36 @@ fn load_oauth_token() -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the daemon as a `_qbz._tcp` mDNS service for LAN discovery.
|
||||
fn register_mdns(config: &DaemonConfig) -> Result<mdns_sd::ServiceDaemon, String> {
|
||||
let mdns = mdns_sd::ServiceDaemon::new()
|
||||
.map_err(|e| format!("mDNS daemon error: {}", e))?;
|
||||
|
||||
let service_name = if config.mdns.name.is_empty() {
|
||||
hostname::get()
|
||||
.ok()
|
||||
.and_then(|h| h.into_string().ok())
|
||||
.unwrap_or_else(|| "qbzd".to_string())
|
||||
} else {
|
||||
config.mdns.name.clone()
|
||||
};
|
||||
|
||||
let service_info = mdns_sd::ServiceInfo::new(
|
||||
"_qbz._tcp.local.",
|
||||
&service_name,
|
||||
&format!("{}.", hostname::get().ok().and_then(|h| h.into_string().ok()).unwrap_or_else(|| "localhost".to_string())),
|
||||
"",
|
||||
config.server.port,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| format!("mDNS service info error: {}", e))?;
|
||||
|
||||
mdns.register(service_info)
|
||||
.map_err(|e| format!("mDNS register error: {}", e))?;
|
||||
|
||||
Ok(mdns)
|
||||
}
|
||||
|
||||
/// Spawn the playback state polling loop.
|
||||
/// Reads player state and broadcasts PlaybackSnapshot events.
|
||||
/// Adaptive polling: 250ms playing, 1s paused, 5s idle.
|
||||
|
||||
Reference in new issue
Block a user