Merge pull request 'feat: Améliorations et corrections dans la gestion des devices UPnP' (#41) from push-xpzxtzwxzktm into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m39s
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m39s
Reviewed-on: #41
This commit was merged in pull request #41.
This commit is contained in:
@@ -71,18 +71,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to register MediaRenderer");
|
.expect("Failed to register MediaRenderer");
|
||||||
|
|
||||||
info!(
|
tracing::warn!("🔍 DEBUG: MediaRenderer registered, getting base_url...");
|
||||||
"✅ MediaRenderer ready at {}{}",
|
let base_url = renderer_instance.base_url();
|
||||||
renderer_instance.base_url(),
|
tracing::warn!("🔍 DEBUG: Got base_url, getting description_route...");
|
||||||
renderer_instance.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
|
let server_instance = server
|
||||||
.write()
|
.write()
|
||||||
.await
|
.await
|
||||||
.register_device(MEDIA_SERVER.clone())
|
.register_device(MEDIA_SERVER.clone())
|
||||||
.await
|
.await
|
||||||
.expect("Failed to register MediaServer");
|
.expect("Failed to register MediaServer");
|
||||||
|
tracing::warn!("🔍 DEBUG: MediaServer registered successfully");
|
||||||
|
|
||||||
// Enregistrer l'instance ContentDirectory pour les notifications GENA
|
// Enregistrer l'instance ContentDirectory pour les notifications GENA
|
||||||
if let Some(cd_service) = server_instance.get_service("ContentDirectory") {
|
if let Some(cd_service) = server_instance.get_service("ContentDirectory") {
|
||||||
|
|||||||
@@ -645,10 +645,10 @@ fn map_didl_entries(xml: &str) -> Result<Vec<MediaEntry>, ControlPointError> {
|
|||||||
is_container: true,
|
is_container: true,
|
||||||
class: container.class,
|
class: container.class,
|
||||||
resources: Vec::new(),
|
resources: Vec::new(),
|
||||||
artist: None,
|
artist: container.artist,
|
||||||
album: None,
|
album: None,
|
||||||
genre: None,
|
genre: None,
|
||||||
album_art_uri: None,
|
album_art_uri: container.album_art,
|
||||||
date: None,
|
date: None,
|
||||||
track_number: None,
|
track_number: None,
|
||||||
creator: None,
|
creator: None,
|
||||||
|
|||||||
@@ -665,6 +665,15 @@ impl ToXmlElement for Container {
|
|||||||
elem.children
|
elem.children
|
||||||
.push(XMLNode::Element(text_element("upnp:class", &self.class)));
|
.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 {
|
for c in &self.containers {
|
||||||
elem.children.push(XMLNode::Element(c.to_xml_element()));
|
elem.children.push(XMLNode::Element(c.to_xml_element()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ impl ParadiseStreamingExt for pmoserver::Server {
|
|||||||
|
|
||||||
// Récupérer ou initialiser les caches singletons
|
// Récupérer ou initialiser les caches singletons
|
||||||
info!("📦 Getting cache singletons...");
|
info!("📦 Getting cache singletons...");
|
||||||
|
tracing::warn!("🔍 DEBUG: About to check get_cover_cache()");
|
||||||
let cover_cache = match get_cover_cache() {
|
let cover_cache = match get_cover_cache() {
|
||||||
Some(cache) => {
|
Some(cache) => {
|
||||||
info!(" ✅ Using existing cover cache singleton");
|
info!(" ✅ Using existing cover cache singleton");
|
||||||
@@ -82,14 +83,17 @@ impl ParadiseStreamingExt for pmoserver::Server {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!(" 📦 Initializing new cover cache singleton");
|
info!(" 📦 Initializing new cover cache singleton");
|
||||||
|
tracing::warn!("🔍 DEBUG: About to call init_cover_cache_configured()");
|
||||||
let cache = self
|
let cache = self
|
||||||
.init_cover_cache_configured()
|
.init_cover_cache_configured()
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialize cover cache")?;
|
.context("Failed to initialize cover cache")?;
|
||||||
|
tracing::warn!("🔍 DEBUG: init_cover_cache_configured() completed");
|
||||||
register_cover_cache(cache.clone());
|
register_cover_cache(cache.clone());
|
||||||
cache
|
cache
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
tracing::warn!("🔍 DEBUG: Cover cache ready, checking audio cache...");
|
||||||
|
|
||||||
let audio_cache = match get_audio_cache() {
|
let audio_cache = match get_audio_cache() {
|
||||||
Some(cache) => {
|
Some(cache) => {
|
||||||
@@ -100,15 +104,18 @@ impl ParadiseStreamingExt for pmoserver::Server {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
info!(" 📦 Initializing new audio cache singleton");
|
info!(" 📦 Initializing new audio cache singleton");
|
||||||
|
tracing::warn!("🔍 DEBUG: About to call init_audio_cache_configured()");
|
||||||
let cache = self
|
let cache = self
|
||||||
.init_audio_cache_configured()
|
.init_audio_cache_configured()
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialize audio cache")?;
|
.context("Failed to initialize audio cache")?;
|
||||||
|
tracing::warn!("🔍 DEBUG: init_audio_cache_configured() completed");
|
||||||
register_audio_cache(cache.clone());
|
register_audio_cache(cache.clone());
|
||||||
register_playlist_audio_cache(cache.clone());
|
register_playlist_audio_cache(cache.clone());
|
||||||
cache
|
cache
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
tracing::warn!("🔍 DEBUG: Audio cache ready, creating ParadiseChannelManager...");
|
||||||
|
|
||||||
// Créer le builder d'historique
|
// Créer le builder d'historique
|
||||||
let mut history_builder = ParadiseHistoryBuilder::default();
|
let mut history_builder = ParadiseHistoryBuilder::default();
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ impl SsdpServer {
|
|||||||
&"0.0.0.0".parse().unwrap(),
|
&"0.0.0.0".parse().unwrap(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
socket.set_read_timeout(Some(Duration::from_secs(1)))?;
|
|
||||||
socket.set_multicast_loop_v4(false)?;
|
socket.set_multicast_loop_v4(false)?;
|
||||||
|
|
||||||
let socket = Arc::new(socket);
|
let socket = Arc::new(socket);
|
||||||
@@ -118,8 +117,11 @@ impl SsdpServer {
|
|||||||
|
|
||||||
// Envoyer alive pour tous les NTs
|
// Envoyer alive pour tous les NTs
|
||||||
if let Some(ref socket) = self.socket {
|
if let Some(ref socket) = self.socket {
|
||||||
for nt in device.get_notification_types() {
|
let nts = device.get_notification_types();
|
||||||
self.send_alive(socket, &device, nt);
|
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
|
/// 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:") {
|
let usn = if nt.starts_with("uuid:") {
|
||||||
format!("{}", nt)
|
format!("{}", nt)
|
||||||
} else {
|
} else {
|
||||||
@@ -172,13 +174,18 @@ impl SsdpServer {
|
|||||||
|
|
||||||
match socket.send_to(msg.as_bytes(), addr) {
|
match socket.send_to(msg.as_bytes(), addr) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("✅ NOTIFY alive: {} (NT={})", usn, nt);
|
let label = if is_periodic { " (periodic)" } else { "" };
|
||||||
|
info!("✅ NOTIFY alive{}: {} (NT={})", label, usn, nt);
|
||||||
debug!(
|
debug!(
|
||||||
"📣 NOTIFY alive payload\n<details>\n\n```\n{}\n```\n</details>\n",
|
"📣 NOTIFY alive{} payload\n<details>\n\n```\n{}\n```\n</details>\n",
|
||||||
msg
|
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");
|
debug!("⏰ SSDP periodic announcement tick");
|
||||||
std::thread::sleep(period);
|
std::thread::sleep(period);
|
||||||
|
|
||||||
|
// Clone la liste des devices pour libérer le lock rapidement
|
||||||
|
let devices_snapshot: Vec<SsdpDevice> = {
|
||||||
let devices = devices.read().unwrap();
|
let devices = devices.read().unwrap();
|
||||||
for device in devices.values() {
|
devices.values().cloned().collect()
|
||||||
|
};
|
||||||
|
for device in &devices_snapshot {
|
||||||
for nt in device.get_notification_types() {
|
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<details>\n\n```\n{}\n```\n</details>\n",
|
|
||||||
msg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(e) => warn!("❌ Failed to send periodic NOTIFY alive for {}: {}", usn, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Démarre l'écoute des M-SEARCH
|
/// Démarre l'écoute des M-SEARCH
|
||||||
fn start_msearch_listener(&self, socket: Arc<UdpSocket>) {
|
fn start_msearch_listener(&self, socket: Arc<UdpSocket>) {
|
||||||
let devices = Arc::clone(&self.devices);
|
let devices = Arc::clone(&self.devices);
|
||||||
@@ -289,17 +263,17 @@ impl SsdpServer {
|
|||||||
src, data
|
src, data
|
||||||
);
|
);
|
||||||
if let Some(st) = Self::parse_st(&data) {
|
if let Some(st) = Self::parse_st(&data) {
|
||||||
|
// Clone la liste des devices pour libérer le lock rapidement
|
||||||
|
let devices_snapshot: Vec<SsdpDevice> = {
|
||||||
let devices = devices.read().unwrap();
|
let devices = devices.read().unwrap();
|
||||||
for device in devices.values() {
|
devices.values().cloned().collect()
|
||||||
|
};
|
||||||
|
for device in &devices_snapshot {
|
||||||
Self::handle_msearch(&socket, &src, &st, device);
|
Self::handle_msearch(&socket, &src, &st, device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
|
||||||
// Timeout, continuer
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("❌ SSDP read error: {}", e);
|
warn!("❌ SSDP read error: {}", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,19 +250,26 @@ impl UpnpServerExt for Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enregistrer les URLs dans le serveur web
|
// Enregistrer les URLs dans le serveur web
|
||||||
|
tracing::warn!("🔍 DEBUG: About to register_urls for device {}", di.udn());
|
||||||
di.register_urls(self).await?;
|
di.register_urls(self).await?;
|
||||||
|
tracing::warn!("🔍 DEBUG: register_urls completed for device {}", di.udn());
|
||||||
|
|
||||||
// Ajouter au registre pour l'introspection
|
// Ajouter au registre pour l'introspection
|
||||||
|
tracing::warn!("🔍 DEBUG: Adding to DEVICE_REGISTRY...");
|
||||||
DEVICE_REGISTRY
|
DEVICE_REGISTRY
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.register(di.clone())
|
.register(di.clone())
|
||||||
.map_err(|e| DeviceError::UrlRegistrationError(e))?;
|
.map_err(|e| DeviceError::UrlRegistrationError(e))?;
|
||||||
|
tracing::warn!("🔍 DEBUG: Added to DEVICE_REGISTRY");
|
||||||
|
|
||||||
// Annoncer via SSDP (si initialisé)
|
// Annoncer via SSDP (si initialisé)
|
||||||
|
tracing::warn!("🔍 DEBUG: Checking SSDP...");
|
||||||
if self.ssdp_enabled() {
|
if self.ssdp_enabled() {
|
||||||
|
tracing::warn!("🔍 DEBUG: SSDP enabled, getting lock...");
|
||||||
let ssdp_opt = SSDP_SERVER.read().unwrap();
|
let ssdp_opt = SSDP_SERVER.read().unwrap();
|
||||||
if let Some(ref ssdp) = *ssdp_opt {
|
if let Some(ref ssdp) = *ssdp_opt {
|
||||||
|
tracing::warn!("🔍 DEBUG: SSDP server exists, announcing...");
|
||||||
use crate::config_ext::UpnpConfigExt;
|
use crate::config_ext::UpnpConfigExt;
|
||||||
let config = pmoconfig::get_config();
|
let config = pmoconfig::get_config();
|
||||||
let manufacturer = config
|
let manufacturer = config
|
||||||
@@ -271,8 +278,10 @@ impl UpnpServerExt for Server {
|
|||||||
let ssdp_device = di.to_ssdp_device(&manufacturer, "1.0");
|
let ssdp_device = di.to_ssdp_device(&manufacturer, "1.0");
|
||||||
ssdp.add_device(ssdp_device);
|
ssdp.add_device(ssdp_device);
|
||||||
info!("✅ SSDP announcement for {}", di.udn());
|
info!("✅ SSDP announcement for {}", di.udn());
|
||||||
|
tracing::warn!("🔍 DEBUG: SSDP announcement complete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tracing::warn!("🔍 DEBUG: Returning device instance");
|
||||||
|
|
||||||
Ok(di)
|
Ok(di)
|
||||||
}
|
}
|
||||||
|
|||||||
281
toto
Normal file
281
toto
Normal file
@@ -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<details>\n\n```\n{}\n```\n</details>\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<SsdpDevice> = {
|
||||||
|
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<details>\n\n```\n{}\n```\n</details>\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<UdpSocket>) {
|
||||||
|
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<SsdpDevice> = {
|
||||||
|
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)
|
||||||
Reference in New Issue
Block a user