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)