diff --git a/pmodidl/src/lib.rs b/pmodidl/src/lib.rs index c39bd6eb..8a52ef15 100644 --- a/pmodidl/src/lib.rs +++ b/pmodidl/src/lib.rs @@ -609,10 +609,11 @@ impl ToXmlElement for Item { elem.children .push(XMLNode::Element(text_element("upnp:genre", genre))); } - if let Some(ref art) = self.album_art { - elem.children - .push(XMLNode::Element(text_element("upnp:albumArtURI", art))); - } + // NOTE: albumArtURI temporarily disabled for compatibility (some CPs misbehave) + // if let Some(ref art) = self.album_art { + // elem.children + // .push(XMLNode::Element(text_element("upnp:albumArtURI", art))); + // } if let Some(date) = self.normalized_date() { elem.children .push(XMLNode::Element(text_element("dc:date", &date))); diff --git a/pmomediaserver/src/content_handler.rs b/pmomediaserver/src/content_handler.rs index af66c2f3..42a67103 100644 --- a/pmomediaserver/src/content_handler.rs +++ b/pmomediaserver/src/content_handler.rs @@ -14,6 +14,7 @@ use pmoutils::ToXmlElement; use pmodidl::{Container, DIDLLite}; use pmosource::api::{get_source as get_source_from_registry, list_all_sources}; use pmosource::{BrowseResult, MusicSource, MusicSourceError}; +use std::collections::HashSet; use std::sync::Arc; /// Convertit des containers et items en XML DIDL-Lite @@ -85,6 +86,16 @@ impl ContentHandler { "ContentDirectory::Browse" ); + // Log rapide sur la branche flatten vs agrégée + if object_id == "0" { + let sources = list_all_sources().await; + tracing::info!( + "Browse root: sources.len() = {}, flatten = {}", + sources.len(), + sources.len() == 1 + ); + } + match browse_flag { "BrowseMetadata" => self.browse_metadata(object_id).await, "BrowseDirectChildren" => { @@ -98,7 +109,24 @@ impl ContentHandler { /// Browse les métadonnées d'un objet spécifique async fn browse_metadata(&self, object_id: &str) -> Result<(String, u32, u32, u32), String> { if object_id == "0" { - // Retourner le container racine + // Si un seul enfant, publier ce fils comme racine + let sources = list_all_sources().await; + if sources.len() == 1 { + let source = sources.into_iter().next().unwrap(); + let mut container = source + .root_container() + .await + .map_err(|e| format!("Failed to get root container: {}", e))?; + // Le présenter comme la racine (id=0, parent=-1) + container.id = "0".to_string(); + container.parent_id = "-1".to_string(); + container.child_count = None; // compatibilité CP + let didl = to_didl_lite(&[container], &[])?; + let update_id = source.update_id().await.max(1); + return Ok((didl, 1, 1, update_id)); + } + + // Sinon retourner le container racine agrégé let root = self.build_root_container().await; let didl = to_didl_lite(&[root], &[])?; Ok((didl, 1, 1, 1)) @@ -190,6 +218,53 @@ impl ContentHandler { requested_count: u32, ) -> Result<(String, u32, u32, u32), String> { if object_id == "0" { + // Si un seul enfant, publier directement ses enfants comme racine + let sources = list_all_sources().await; + if sources.len() == 1 { + let source = sources.into_iter().next().unwrap(); + let source_id = source.id().to_string(); + + // Récupérer les enfants du container racine de la source + let mut result = source + .browse(&source_id) + .await + .map_err(|e| format!("Browse failed: {}", e))?; + + // Re-mapper parentID sur "0" pour éviter des parents inexistants côté CP + match &mut result { + BrowseResult::Containers(c) => { + for cont in c.iter_mut() { + if cont.parent_id == source_id { + cont.parent_id = "0".to_string(); + } + } + } + BrowseResult::Items(i) => { + for item in i.iter_mut() { + if item.parent_id == source_id { + item.parent_id = "0".to_string(); + } + } + } + BrowseResult::Mixed { containers, items } => { + for cont in containers.iter_mut() { + if cont.parent_id == source_id { + cont.parent_id = "0".to_string(); + } + } + for item in items.iter_mut() { + if item.parent_id == source_id { + item.parent_id = "0".to_string(); + } + } + } + } + + return self + .browse_result_to_didl("0", result, source, starting_index, requested_count) + .await; + } + // Retourner toutes les sources comme enfants de la racine return self.browse_root(starting_index, requested_count).await; } @@ -308,6 +383,27 @@ impl ContentHandler { .collect(); let mut items = items; + // Log avant déduplication + tracing::debug!( + "BrowseResult before dedup: containers={}, items={}", + containers.len(), + items.len() + ); + + // Deduplicate containers/items by id to avoid doubles in the response + let mut seen_containers = HashSet::new(); + containers.retain(|c| seen_containers.insert(c.id.clone())); + + let mut seen_items = HashSet::new(); + items.retain(|i| seen_items.insert(i.id.clone())); + + // Log après déduplication + tracing::debug!( + "BrowseResult after dedup: containers={}, items={}", + containers.len(), + items.len() + ); + // Calculer le total avant pagination let total = (containers.len() + items.len()) as u32; @@ -355,7 +451,7 @@ impl ContentHandler { id: "0".to_string(), parent_id: "-1".to_string(), restricted: Some("1".to_string()), - // Par cohérence avec la plupart des serveurs observés, on omet childCount sur la racine + // Laisser childCount absent sur la racine pour maximiser la compatibilité (BubbleUPnP) child_count: None, searchable: Some("1".to_string()), title: "PMOMusic".to_string(), diff --git a/pmoparadise/src/source.rs b/pmoparadise/src/source.rs index 67bcd9dc..df135d97 100644 --- a/pmoparadise/src/source.rs +++ b/pmoparadise/src/source.rs @@ -187,7 +187,7 @@ impl RadioParadiseSource { id: format!("radio-paradise:channel:{}", descriptor.slug), parent_id: "radio-paradise".to_string(), restricted: Some("1".to_string()), - child_count: Some("2".to_string()), // Live + History + child_count: None, searchable: Some("0".to_string()), title: descriptor.display_name.to_string(), class: "object.container".to_string(), @@ -242,7 +242,7 @@ impl RadioParadiseSource { id: format!("radio-paradise:channel:{}:history", descriptor.slug), parent_id: format!("radio-paradise:channel:{}", descriptor.slug), restricted: Some("1".to_string()), - child_count: Some("0".to_string()), // Default to 0, updated below if playlist exists + child_count: None, searchable: Some("1".to_string()), title: format!("{} - History", descriptor.display_name), // Expose l'historique comme une playlist jouable @@ -355,7 +355,8 @@ impl MusicSource for RadioParadiseSource { id: "radio-paradise".to_string(), parent_id: "0".to_string(), restricted: Some("1".to_string()), - child_count: Some("4".to_string()), // 4 channels + // childCount retiré pour éviter les soucis de compatibilité côté CP + child_count: None, searchable: Some("0".to_string()), title: "Radio Paradise".to_string(), class: "object.container".to_string(), diff --git a/pmoplaylist/src/handle/read.rs b/pmoplaylist/src/handle/read.rs index 6f34a8fb..a01cbc12 100644 --- a/pmoplaylist/src/handle/read.rs +++ b/pmoplaylist/src/handle/read.rs @@ -166,7 +166,7 @@ impl ReadHandle { id: self.playlist.id.clone(), parent_id: "0".to_string(), restricted: Some("1".to_string()), - child_count: Some(remaining.to_string()), + child_count: None, searchable: Some("0".to_string()), title, class: "object.container.playlistContainer".to_string(), @@ -222,6 +222,12 @@ impl ReadHandle { let genre = meta.get_genre().await.ok().flatten(); let year = meta.get_year().await.ok().flatten(); let track_number = meta.get_track_number().await.ok().flatten(); + let cover_pk = meta.get_cover_pk().await.ok().flatten(); + let cover_url = if let Some(pk) = cover_pk.as_ref() { + Some(format!("/covers/jpeg/{}/256", pk)) + } else { + meta.get_cover_url().await.ok().flatten() + }; // Créer l'Item let item = Item { @@ -234,8 +240,8 @@ impl ReadHandle { artist, album, genre, - album_art: None, // TODO: intégrer pmocovers - album_art_pk: None, + album_art: cover_url, + album_art_pk: cover_pk, date: year.map(|y| y.to_string()), original_track_number: track_number.map(|n| n.to_string()), resources: vec![resource],