diff --git a/PMOMusic/src/main.rs b/PMOMusic/src/main.rs index 9e44eb03..e2dc910a 100644 --- a/PMOMusic/src/main.rs +++ b/PMOMusic/src/main.rs @@ -71,18 +71,22 @@ async fn main() -> Result<(), Box> { .await .expect("Failed to register MediaRenderer"); - info!( - "โœ… MediaRenderer ready at {}{}", - renderer_instance.base_url(), - renderer_instance.description_route() - ); + tracing::warn!("๐Ÿ” DEBUG: MediaRenderer registered, getting base_url..."); + let base_url = renderer_instance.base_url(); + tracing::warn!("๐Ÿ” DEBUG: Got base_url, getting description_route..."); + let desc_route = renderer_instance.description_route(); + tracing::warn!("๐Ÿ” DEBUG: Got description_route, logging..."); + info!("โœ… MediaRenderer ready at {}{}", base_url, desc_route); + tracing::warn!("๐Ÿ” DEBUG: Log complete!"); + tracing::warn!("๐Ÿ” DEBUG: About to register MediaServer..."); let server_instance = server .write() .await .register_device(MEDIA_SERVER.clone()) .await .expect("Failed to register MediaServer"); + tracing::warn!("๐Ÿ” DEBUG: MediaServer registered successfully"); // Enregistrer l'instance ContentDirectory pour les notifications GENA if let Some(cd_service) = server_instance.get_service("ContentDirectory") { diff --git a/pmocontrol/src/media_server.rs b/pmocontrol/src/media_server.rs index 78611080..ef5eaa61 100644 --- a/pmocontrol/src/media_server.rs +++ b/pmocontrol/src/media_server.rs @@ -645,10 +645,10 @@ fn map_didl_entries(xml: &str) -> Result, ControlPointError> { is_container: true, class: container.class, resources: Vec::new(), - artist: None, + artist: container.artist, album: None, genre: None, - album_art_uri: None, + album_art_uri: container.album_art, date: None, track_number: None, creator: None, diff --git a/pmodidl/src/lib.rs b/pmodidl/src/lib.rs index fd9ad61e..3779add2 100644 --- a/pmodidl/src/lib.rs +++ b/pmodidl/src/lib.rs @@ -665,6 +665,15 @@ impl ToXmlElement for Container { elem.children .push(XMLNode::Element(text_element("upnp:class", &self.class))); + if let Some(ref artist) = self.artist { + elem.children + .push(XMLNode::Element(text_element("upnp:artist", artist))); + } + if let Some(ref art) = self.album_art { + elem.children + .push(XMLNode::Element(text_element("upnp:albumArtURI", art))); + } + for c in &self.containers { elem.children.push(XMLNode::Element(c.to_xml_element())); } diff --git a/pmomediaserver/src/paradise_streaming.rs b/pmomediaserver/src/paradise_streaming.rs index 8e8b6a97..160a9d66 100644 --- a/pmomediaserver/src/paradise_streaming.rs +++ b/pmomediaserver/src/paradise_streaming.rs @@ -75,6 +75,7 @@ impl ParadiseStreamingExt for pmoserver::Server { // Rรฉcupรฉrer ou initialiser les caches singletons info!("๐Ÿ“ฆ Getting cache singletons..."); + tracing::warn!("๐Ÿ” DEBUG: About to check get_cover_cache()"); let cover_cache = match get_cover_cache() { Some(cache) => { info!(" โœ… Using existing cover cache singleton"); @@ -82,14 +83,17 @@ impl ParadiseStreamingExt for pmoserver::Server { } None => { info!(" ๐Ÿ“ฆ Initializing new cover cache singleton"); + tracing::warn!("๐Ÿ” DEBUG: About to call init_cover_cache_configured()"); let cache = self .init_cover_cache_configured() .await .context("Failed to initialize cover cache")?; + tracing::warn!("๐Ÿ” DEBUG: init_cover_cache_configured() completed"); register_cover_cache(cache.clone()); cache } }; + tracing::warn!("๐Ÿ” DEBUG: Cover cache ready, checking audio cache..."); let audio_cache = match get_audio_cache() { Some(cache) => { @@ -100,15 +104,18 @@ impl ParadiseStreamingExt for pmoserver::Server { } None => { info!(" ๐Ÿ“ฆ Initializing new audio cache singleton"); + tracing::warn!("๐Ÿ” DEBUG: About to call init_audio_cache_configured()"); let cache = self .init_audio_cache_configured() .await .context("Failed to initialize audio cache")?; + tracing::warn!("๐Ÿ” DEBUG: init_audio_cache_configured() completed"); register_audio_cache(cache.clone()); register_playlist_audio_cache(cache.clone()); cache } }; + tracing::warn!("๐Ÿ” DEBUG: Audio cache ready, creating ParadiseChannelManager..."); // Crรฉer le builder d'historique let mut history_builder = ParadiseHistoryBuilder::default(); diff --git a/pmoupnp/src/ssdp/server.rs b/pmoupnp/src/ssdp/server.rs index 2ab68d0c..e07d9d0f 100644 --- a/pmoupnp/src/ssdp/server.rs +++ b/pmoupnp/src/ssdp/server.rs @@ -83,7 +83,6 @@ impl SsdpServer { &"0.0.0.0".parse().unwrap(), )?; - socket.set_read_timeout(Some(Duration::from_secs(1)))?; socket.set_multicast_loop_v4(false)?; let socket = Arc::new(socket); @@ -118,8 +117,11 @@ impl SsdpServer { // Envoyer alive pour tous les NTs if let Some(ref socket) = self.socket { - for nt in device.get_notification_types() { - self.send_alive(socket, &device, nt); + let nts = device.get_notification_types(); + for nt in nts.iter() { + Self::send_alive(socket, &device, nt, false); + // Petit dรฉlai pour รฉviter de saturer le buffer UDP sur macOS + std::thread::sleep(Duration::from_millis(5)); } } } @@ -146,7 +148,7 @@ impl SsdpServer { } /// Envoie un NOTIFY alive - fn send_alive(&self, socket: &UdpSocket, device: &SsdpDevice, nt: &str) { + fn send_alive(socket: &UdpSocket, device: &SsdpDevice, nt: &str, is_periodic: bool) { let usn = if nt.starts_with("uuid:") { format!("{}", nt) } else { @@ -172,13 +174,18 @@ impl SsdpServer { match socket.send_to(msg.as_bytes(), addr) { Ok(_) => { - info!("โœ… NOTIFY alive: {} (NT={})", usn, nt); + let label = if is_periodic { " (periodic)" } else { "" }; + info!("โœ… NOTIFY alive{}: {} (NT={})", label, usn, nt); debug!( - "๐Ÿ“ฃ NOTIFY alive payload\n
\n\n```\n{}\n```\n
\n", - msg + "๐Ÿ“ฃ NOTIFY alive{} payload\n
\n\n```\n{}\n```\n
\n", + label, msg ); } - Err(e) => warn!("โŒ Failed to send NOTIFY alive for {}: {}", usn, e), + + Err(e) => { + let label = if is_periodic { "periodic " } else { "" }; + warn!("โŒ Failed to send {}NOTIFY alive for {}: {}", label, usn, e); + } } } @@ -226,53 +233,20 @@ impl SsdpServer { debug!("โฐ SSDP periodic announcement tick"); std::thread::sleep(period); - let devices = devices.read().unwrap(); - for device in devices.values() { + // Clone la liste des devices pour libรฉrer le lock rapidement + let devices_snapshot: Vec = { + let devices = devices.read().unwrap(); + devices.values().cloned().collect() + }; + for device in &devices_snapshot { for nt in device.get_notification_types() { - Self::send_alive_static(&socket, device, nt); + Self::send_alive(&socket, device, nt, true); } } } }); } - /// Version statique de send_alive pour les threads - fn send_alive_static(socket: &UdpSocket, device: &SsdpDevice, nt: &str) { - let usn = if nt.starts_with("uuid:") { - format!("{}", nt) - } else { - format!("uuid:{}::{}", device.uuid, nt) - }; - - let msg = format!( - "NOTIFY * HTTP/1.1\r\n\ - HOST: {}:{}\r\n\ - CACHE-CONTROL: max-age={}\r\n\ - LOCATION: {}\r\n\ - NT: {}\r\n\ - NTS: ssdp:alive\r\n\ - SERVER: {}\r\n\ - USN: {}\r\n\ - \r\n", - SSDP_MULTICAST_ADDR, SSDP_PORT, MAX_AGE, device.location, nt, device.server, usn - ); - - let addr: SocketAddr = format!("{}:{}", SSDP_MULTICAST_ADDR, SSDP_PORT) - .parse() - .unwrap(); - - match socket.send_to(msg.as_bytes(), addr) { - Ok(_) => { - info!("โœ… NOTIFY alive (periodic): {} (NT={})", usn, nt); - debug!( - "๐Ÿ“ฃ NOTIFY alive (periodic) payload\n
\n\n```\n{}\n```\n
\n", - msg - ); - } - Err(e) => warn!("โŒ Failed to send periodic NOTIFY alive for {}: {}", usn, e), - } - } - /// Dรฉmarre l'รฉcoute des M-SEARCH fn start_msearch_listener(&self, socket: Arc) { let devices = Arc::clone(&self.devices); @@ -289,17 +263,17 @@ impl SsdpServer { src, data ); if let Some(st) = Self::parse_st(&data) { - let devices = devices.read().unwrap(); - for device in devices.values() { + // Clone la liste des devices pour libรฉrer le lock rapidement + let devices_snapshot: Vec = { + let devices = devices.read().unwrap(); + devices.values().cloned().collect() + }; + for device in &devices_snapshot { Self::handle_msearch(&socket, &src, &st, device); } } } } - Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { - // Timeout, continuer - continue; - } Err(e) => { warn!("โŒ SSDP read error: {}", e); } diff --git a/pmoupnp/src/upnp_server.rs b/pmoupnp/src/upnp_server.rs index 9ebe2d0f..333b12e5 100644 --- a/pmoupnp/src/upnp_server.rs +++ b/pmoupnp/src/upnp_server.rs @@ -250,19 +250,26 @@ impl UpnpServerExt for Server { } // Enregistrer les URLs dans le serveur web + tracing::warn!("๐Ÿ” DEBUG: About to register_urls for device {}", di.udn()); di.register_urls(self).await?; + tracing::warn!("๐Ÿ” DEBUG: register_urls completed for device {}", di.udn()); // Ajouter au registre pour l'introspection + tracing::warn!("๐Ÿ” DEBUG: Adding to DEVICE_REGISTRY..."); DEVICE_REGISTRY .write() .unwrap() .register(di.clone()) .map_err(|e| DeviceError::UrlRegistrationError(e))?; + tracing::warn!("๐Ÿ” DEBUG: Added to DEVICE_REGISTRY"); // Annoncer via SSDP (si initialisรฉ) + tracing::warn!("๐Ÿ” DEBUG: Checking SSDP..."); if self.ssdp_enabled() { + tracing::warn!("๐Ÿ” DEBUG: SSDP enabled, getting lock..."); let ssdp_opt = SSDP_SERVER.read().unwrap(); if let Some(ref ssdp) = *ssdp_opt { + tracing::warn!("๐Ÿ” DEBUG: SSDP server exists, announcing..."); use crate::config_ext::UpnpConfigExt; let config = pmoconfig::get_config(); let manufacturer = config @@ -271,8 +278,10 @@ impl UpnpServerExt for Server { let ssdp_device = di.to_ssdp_device(&manufacturer, "1.0"); ssdp.add_device(ssdp_device); info!("โœ… SSDP announcement for {}", di.udn()); + tracing::warn!("๐Ÿ” DEBUG: SSDP announcement complete"); } } + tracing::warn!("๐Ÿ” DEBUG: Returning device instance"); Ok(di) } diff --git a/toto b/toto new file mode 100644 index 00000000..1c70b73e --- /dev/null +++ b/toto @@ -0,0 +1,281 @@ +Modified regular file PMOMusic/src/main.rs: + ... + 71 71: .await + 72 72: .expect("Failed to register MediaRenderer"); + 73 73: + 74 : info!( + 75 : "โœ… MediaRenderer ready at {}{}", + 76 : renderer_instance.base_url(), + 77 : renderer_instance.description_route() + 78 : ); + 74: tracing::warn!("๐Ÿ” DEBUG: MediaRenderer registered, getting base_url..."); + 75: let base_url = renderer_instance.base_url(); + 76: tracing::warn!("๐Ÿ” DEBUG: Got base_url, getting description_route..."); + 77: let desc_route = renderer_instance.description_route(); + 78: tracing::warn!("๐Ÿ” DEBUG: Got description_route, logging..."); + 79: info!("โœ… MediaRenderer ready at {}{}", base_url, desc_route); + 80: tracing::warn!("๐Ÿ” DEBUG: Log complete!"); + 79 81: + 82: tracing::warn!("๐Ÿ” DEBUG: About to register MediaServer..."); + 80 83: let server_instance = server + 81 84: .write() + 82 85: .await + 83 86: .register_device(MEDIA_SERVER.clone()) + 84 87: .await + 85 88: .expect("Failed to register MediaServer"); + 89: tracing::warn!("๐Ÿ” DEBUG: MediaServer registered successfully"); + 86 90: + 87 91: // Enregistrer l'instance ContentDirectory pour les notifications GENA + 88 92: if let Some(cd_service) = server_instance.get_service("ContentDirectory") { + ... +Modified regular file pmocontrol/src/media_server.rs: + ... + 645 645: is_container: true, + 646 646: class: container.class, + 647 647: resources: Vec::new(), + 648 648: artist: Nonecontainer.artist, + 649 649: album: None, + 650 650: genre: None, + 651 651: album_art_uri: Nonecontainer.album_art, + 652 652: date: None, + 653 653: track_number: None, + 654 654: creator: None, + ... +Modified regular file pmodidl/src/lib.rs: + ... + 665 665: elem.children + 666 666: .push(XMLNode::Element(text_element("upnp:class", &self.class))); + 667 667: + 668: if let Some(ref artist) = self.artist { + 669: elem.children + 670: .push(XMLNode::Element(text_element("upnp:artist", artist))); + 671: } + 672: if let Some(ref art) = self.album_art { + 673: elem.children + 674: .push(XMLNode::Element(text_element("upnp:albumArtURI", art))); + 675: } + 676: + 668 677: for c in &self.containers { + 669 678: elem.children.push(XMLNode::Element(c.to_xml_element())); + 670 679: } + ... +Modified regular file pmomediaserver/src/paradise_streaming.rs: + ... + 75 75: + 76 76: // Rรฉcupรฉrer ou initialiser les caches singletons + 77 77: info!("๐Ÿ“ฆ Getting cache singletons..."); + 78: tracing::warn!("๐Ÿ” DEBUG: About to check get_cover_cache()"); + 78 79: let cover_cache = match get_cover_cache() { + 79 80: Some(cache) => { + 80 81: info!(" โœ… Using existing cover cache singleton"); + 81 82: cache + 82 83: } + 83 84: None => { + 84 85: info!(" ๐Ÿ“ฆ Initializing new cover cache singleton"); + 86: tracing::warn!("๐Ÿ” DEBUG: About to call init_cover_cache_configured()"); + 85 87: let cache = self + 86 88: .init_cover_cache_configured() + 87 89: .await + 88 90: .context("Failed to initialize cover cache")?; + 91: tracing::warn!("๐Ÿ” DEBUG: init_cover_cache_configured() completed"); + 89 92: register_cover_cache(cache.clone()); + 90 93: cache + 91 94: } + 92 95: }; + 96: tracing::warn!("๐Ÿ” DEBUG: Cover cache ready, checking audio cache..."); + 93 97: + 94 98: let audio_cache = match get_audio_cache() { + 95 99: Some(cache) => { + ... + 100 104: } + 101 105: None => { + 102 106: info!(" ๐Ÿ“ฆ Initializing new audio cache singleton"); + 107: tracing::warn!("๐Ÿ” DEBUG: About to call init_audio_cache_configured()"); + 103 108: let cache = self + 104 109: .init_audio_cache_configured() + 105 110: .await + 106 111: .context("Failed to initialize audio cache")?; + 112: tracing::warn!("๐Ÿ” DEBUG: init_audio_cache_configured() completed"); + 107 113: register_audio_cache(cache.clone()); + 108 114: register_playlist_audio_cache(cache.clone()); + 109 115: cache + 110 116: } + 111 117: }; + 118: tracing::warn!("๐Ÿ” DEBUG: Audio cache ready, creating ParadiseChannelManager..."); + 112 119: + 113 120: // Crรฉer le builder d'historique + 114 121: let mut history_builder = ParadiseHistoryBuilder::default(); + ... +Modified regular file pmoupnp/src/ssdp/server.rs: + ... + 83 83: &"0.0.0.0".parse().unwrap(), + 84 84: )?; + 85 85: + 86 : socket.set_read_timeout(Some(Duration::from_secs(1)))?; + 87 86: socket.set_multicast_loop_v4(false)?; + 88 87: + 89 88: let socket = Arc::new(socket); + ... + 118 117: + 119 118: // Envoyer alive pour tous les NTs + 120 119: if let Some(ref socket) = self.socket { + 121 : for nt in device.get_notification_types() { + 122 : self.send_alive(socket, &device, nt); + 120: let nts = device.get_notification_types(); + 121: for nt in nts.iter() { + 122: Self::send_alive(socket, &device, nt, false); + 123: // Petit dรฉlai pour รฉviter de saturer le buffer UDP sur macOS + 124: std::thread::sleep(Duration::from_millis(5)); + 123 125: } + 124 126: } + 125 127: } + ... + 146 148: } + 147 149: + 148 150: /// Envoie un NOTIFY alive + 149 151: fn send_alive(&self, socket: &UdpSocket, device: &SsdpDevice, nt: &str, is_periodic: bool) { + 150 152: let usn = if nt.starts_with("uuid:") { + 151 153: format!("{}", nt) + 152 154: } else { + ... + 172 174: + 173 175: match socket.send_to(msg.as_bytes(), addr) { + 174 176: Ok(_) => { + 175 177: let label = if is_periodic { " (periodic)" } else { "" }; + 175 178: info!("โœ… NOTIFY alive{}: {} (NT={})", label, usn, nt); + 176 179: debug!( + 177 180: "๐Ÿ“ฃ NOTIFY alive{} payload\n
\n\n```\n{}\n```\n
\n", + 178 181: label, msg + 179 182: ); + 180 183: } + 184: + 181 185: Err(e) => { + 186: let label = if is_periodic { "periodic " } else { "" }; + 181 187: warn!("โŒ Failed to send {}NOTIFY alive for {}: {}", label, usn, e),; + 181 188: } + 182 189: } + 183 190: } + 184 191: + ... + 226 233: debug!("โฐ SSDP periodic announcement tick"); + 227 234: std::thread::sleep(period); + 228 235: + 229 236: // Clone la liste des devices pour libรฉrer le lock rapidement + 237: let devices_snapshot: Vec = { + 229 238: let devices = devices.read().unwrap(); + 230 239: devices.values().cloned().collect() + 240: }; + 230 241: for device in devices.values()&devices_snapshot { + 231 242: for nt in device.get_notification_types() { + 232 243: Self::send_alive_staticsend_alive(&socket, device, nt, true); + 233 244: } + 234 245: } + 235 246: } + 236 247: }); + 237 248: } + 238 249: + 239 : /// Version statique de send_alive pour les threads + 240 : fn send_alive_static(socket: &UdpSocket, device: &SsdpDevice, nt: &str) { + 241 : let usn = if nt.starts_with("uuid:") { + 242 : format!("{}", nt) + 243 : } else { + 244 : format!("uuid:{}::{}", device.uuid, nt) + 245 : }; + 246 : + 247 : let msg = format!( + 248 : "NOTIFY * HTTP/1.1\r\n\ + 249 : HOST: {}:{}\r\n\ + 250 : CACHE-CONTROL: max-age={}\r\n\ + 251 : LOCATION: {}\r\n\ + 252 : NT: {}\r\n\ + 253 : NTS: ssdp:alive\r\n\ + 254 : SERVER: {}\r\n\ + 255 : USN: {}\r\n\ + 256 : \r\n", + 257 : SSDP_MULTICAST_ADDR, SSDP_PORT, MAX_AGE, device.location, nt, device.server, usn + 258 : ); + 259 : + 260 : let addr: SocketAddr = format!("{}:{}", SSDP_MULTICAST_ADDR, SSDP_PORT) + 261 : .parse() + 262 : .unwrap(); + 263 : + 264 : match socket.send_to(msg.as_bytes(), addr) { + 265 : Ok(_) => { + 266 : info!("โœ… NOTIFY alive (periodic): {} (NT={})", usn, nt); + 267 : debug!( + 268 : "๐Ÿ“ฃ NOTIFY alive (periodic) payload\n
\n\n```\n{}\n```\n
\n", + 269 : msg + 270 : ); + 271 : } + 272 : Err(e) => warn!("โŒ Failed to send periodic NOTIFY alive for {}: {}", usn, e), + 273 : } + 274 : } + 275 : + 276 250: /// Dรฉmarre l'รฉcoute des M-SEARCH + 277 251: fn start_msearch_listener(&self, socket: Arc) { + 278 252: let devices = Arc::clone(&self.devices); + ... + 289 263: src, data + 290 264: ); + 291 265: if let Some(st) = Self::parse_st(&data) { + 292 266: // Clone la liste des devices pour libรฉrer le lock rapidement + 267: let devices_snapshot: Vec = { + 292 268: let devices = devices.read().unwrap(); + 293 269: devices.values().cloned().collect() + 270: }; + 293 271: for device in devices.values()&devices_snapshot { + 294 272: Self::handle_msearch(&socket, &src, &st, device); + 295 273: } + 296 274: } + 297 275: } + 298 276: } + 299 : Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + 300 : // Timeout, continuer + 301 : continue; + 302 : } + 303 277: Err(e) => { + 304 278: warn!("โŒ SSDP read error: {}", e); + 305 279: } + ... +Modified regular file pmoupnp/src/upnp_server.rs: + ... + 250 250: } + 251 251: + 252 252: // Enregistrer les URLs dans le serveur web + 253: tracing::warn!("๐Ÿ” DEBUG: About to register_urls for device {}", di.udn()); + 253 254: di.register_urls(self).await?; + 255: tracing::warn!("๐Ÿ” DEBUG: register_urls completed for device {}", di.udn()); + 254 256: + 255 257: // Ajouter au registre pour l'introspection + 258: tracing::warn!("๐Ÿ” DEBUG: Adding to DEVICE_REGISTRY..."); + 256 259: DEVICE_REGISTRY + 257 260: .write() + 258 261: .unwrap() + 259 262: .register(di.clone()) + 260 263: .map_err(|e| DeviceError::UrlRegistrationError(e))?; + 264: tracing::warn!("๐Ÿ” DEBUG: Added to DEVICE_REGISTRY"); + 261 265: + 262 266: // Annoncer via SSDP (si initialisรฉ) + 267: tracing::warn!("๐Ÿ” DEBUG: Checking SSDP..."); + 263 268: if self.ssdp_enabled() { + 269: tracing::warn!("๐Ÿ” DEBUG: SSDP enabled, getting lock..."); + 264 270: let ssdp_opt = SSDP_SERVER.read().unwrap(); + 265 271: if let Some(ref ssdp) = *ssdp_opt { + 272: tracing::warn!("๐Ÿ” DEBUG: SSDP server exists, announcing..."); + 266 273: use crate::config_ext::UpnpConfigExt; + 267 274: let config = pmoconfig::get_config(); + 268 275: let manufacturer = config + ... + 271 278: let ssdp_device = di.to_ssdp_device(&manufacturer, "1.0"); + 272 279: ssdp.add_device(ssdp_device); + 273 280: info!("โœ… SSDP announcement for {}", di.udn()); + 281: tracing::warn!("๐Ÿ” DEBUG: SSDP announcement complete"); + 274 282: } + 275 283: } + 284: tracing::warn!("๐Ÿ” DEBUG: Returning device instance"); + 276 285: + 277 286: Ok(di) + 278 287: } + ... +Added regular file toto: + (empty)