Merge pull request 'push-ynwrzlvxyzym' (#71) from push-ynwrzlvxyzym into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m18s
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m18s
Reviewed-on: #71
This commit was merged in pull request #71.
This commit is contained in:
64
Blackboard/Report/Oubli_Main_Radio.md
Normal file
64
Blackboard/Report/Oubli_Main_Radio.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Rapport : Correction de l'affichage des groupes de radios
|
||||||
|
|
||||||
|
## Résumé
|
||||||
|
|
||||||
|
Correction des deux bugs identifiés dans l'organisation des groupes de stations Radio France :
|
||||||
|
1. La radio principale était absente des groupes multi-stations (écrasée par les webradios)
|
||||||
|
2. Le groupe ICI (radios locales) était inaccessible à cause d'une station virtuelle "ici" mal gérée
|
||||||
|
|
||||||
|
## Solution implémentée
|
||||||
|
|
||||||
|
Refactoring complet pour supprimer la notion de "station virtuelle" et utiliser des champs optionnels `group_name` et `group_slug` dans `StationGroup`.
|
||||||
|
|
||||||
|
### Modifications
|
||||||
|
|
||||||
|
**Fichiers modifiés :**
|
||||||
|
- `pmoradiofrance/src/playlist.rs`
|
||||||
|
- `pmoradiofrance/src/source.rs`
|
||||||
|
- `pmoradiofrance/src/client.rs`
|
||||||
|
|
||||||
|
### Changements structurels
|
||||||
|
|
||||||
|
1. **Ajout de champs optionnels à `StationGroup`** :
|
||||||
|
- `group_name: Option<String>` : nom personnalisé du groupe (pour ICI : "Radios ICI")
|
||||||
|
- `group_slug: Option<String>` : slug personnalisé du groupe (pour ICI : "ici")
|
||||||
|
|
||||||
|
2. **Suppression de la station virtuelle "ici"** :
|
||||||
|
- Plus de création d'une `Station { slug: "ici", name: "Radios ICI" }`
|
||||||
|
- Le groupe ICI contient maintenant uniquement les vraies stations `francebleu_*`
|
||||||
|
|
||||||
|
3. **Méthodes helper dans `StationGroup`** :
|
||||||
|
- `name()` : retourne `group_name` ou le nom de `stations[0]`
|
||||||
|
- `slug()` : retourne `group_slug` ou le slug de `stations[0]`
|
||||||
|
|
||||||
|
4. **Simplification de la logique** :
|
||||||
|
- `to_stub()` et `to_didl()` de `StationGroup` utilisent `self.name()` et `self.slug()`
|
||||||
|
- Plus de traitement spécial pour détecter ou sauter la station virtuelle "ici"
|
||||||
|
- `compute_parent_id()` de `Station` utilise le slug fixe "ici" pour les stations `francebleu_*`
|
||||||
|
|
||||||
|
5. **Tri alphabétique des stations** :
|
||||||
|
- Dans chaque groupe : station principale en position 0, puis webradios triées par nom
|
||||||
|
- Groupe ICI : toutes les stations triées par nom (pas de station principale)
|
||||||
|
- Les stations se retrouvent toujours à la même position dans la liste
|
||||||
|
|
||||||
|
6. **Chargement parallèle des métadonnées** :
|
||||||
|
- Utilisation de `futures::stream::buffer_unordered(5)` pour charger jusqu'à 5 stations en parallèle
|
||||||
|
- Décalage de 50ms entre chaque requête pour éviter de surcharger l'API Radio France
|
||||||
|
- Amélioration significative du temps de chargement des groupes multi-stations
|
||||||
|
|
||||||
|
7. **Filtrage de francebleu générique** :
|
||||||
|
- Application de la règle métier : `francebleu` (sans suffixe) n'est pas une vraie station
|
||||||
|
- Filtrage dans `discover_all_stations()` pour éviter qu'elle soit ajoutée à la liste
|
||||||
|
- Utilisation de `group.slug()` au lieu de `stations[0].slug` dans `source.rs` pour gérer correctement le groupe ICI
|
||||||
|
|
||||||
|
### Résultat
|
||||||
|
|
||||||
|
- **Groupes avec radio principale** (FIP, France Inter, etc.) : la radio principale apparaît à l'index 0, suivie des webradios triées alphabétiquement
|
||||||
|
- **Groupe ICI** : accessible et affiche toutes les radios locales triées alphabétiquement
|
||||||
|
- Code plus simple et sans logique spéciale dispersée
|
||||||
|
- Interface prévisible : les stations sont toujours au même endroit
|
||||||
|
- **Chargement plus rapide** : les métadonnées sont récupérées en parallèle au lieu de séquentiellement
|
||||||
|
|
||||||
|
## Statut
|
||||||
|
|
||||||
|
Modifications terminées. Compilation à vérifier par l'utilisateur.
|
||||||
15
Blackboard/Todo/Oubli_Main_Radio.md
Normal file
15
Blackboard/Todo/Oubli_Main_Radio.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
**Tu réaliseras ce travail en appliquant scrupuleusement les règles définies dans [@Rules.md](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/Rules.md)**
|
||||||
|
|
||||||
|
Ce travail se réalisera dans la crate [@pmoradiofrance](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmoradiofrance).
|
||||||
|
|
||||||
|
|
||||||
|
- L'ensemble des éditions devraient se réaliser dans le fichier [@playlist.rs](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmoradiofrance/src/playlist.rs).
|
||||||
|
- Il est fort probable que tu n'aies besoin de lire aucun autre fichier.
|
||||||
|
|
||||||
|
Le problème, les groupes de radio sont composés d'une radio principale, parfois, et de web radio parfois.
|
||||||
|
|
||||||
|
Quand on a une radio principale unique, comme France Culture, tout se passe bien.
|
||||||
|
|
||||||
|
Quand on a un groupe de radio avec une radio principale et des web radios, la radio principale devrait se trouver à l'index 0 du container et les radios secondaires aux index suivants. Mais la radio principale est absente. ÷Sans doute qu'elle a été écrasée par des webradio.
|
||||||
|
|
||||||
|
Et quant au seul cas de groupe de radio qui n'ont pas de radio principale, ICI, Les radios locales de Radio France, Rien ne marche du tout, on n'arrive même pas à accéder au container. C'est peut-être un reliquat du temps où tu traitais ICI à part.
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.8.7",
|
"axum 0.8.7",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
//! Ce module définit les handlers HTTP pour accéder aux stations Radio France,
|
//! Ce module définit les handlers HTTP pour accéder aux stations Radio France,
|
||||||
//! leurs métadonnées live et les flux de streaming.
|
//! leurs métadonnées live et les flux de streaming.
|
||||||
|
|
||||||
use crate::models::LiveResponse;
|
|
||||||
use crate::playlist::StationGroups;
|
use crate::playlist::StationGroups;
|
||||||
use crate::pmoserver_ext::RadioFranceState;
|
use crate::pmoserver_ext::RadioFranceState;
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -16,6 +15,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
// ============ Gestion des erreurs ============
|
// ============ Gestion des erreurs ============
|
||||||
|
|
||||||
@@ -97,6 +97,11 @@ async fn proxy_stream(
|
|||||||
Err(e) => return AppError(format!("Stream not found: {}", e)).into_response(),
|
Err(e) => return AppError(format!("Stream not found: {}", e)).into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Proxyfying RadioFrance stream for {} ==> {}",
|
||||||
|
&slug, &stream_url
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to the Radio France stream
|
// Connect to the Radio France stream
|
||||||
let response = match reqwest::get(&stream_url).await {
|
let response = match reqwest::get(&stream_url).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
|
|||||||
@@ -134,13 +134,13 @@ impl RadioFranceClient {
|
|||||||
// Note: Skip francebleu because its "webradios" are actually local radios
|
// Note: Skip francebleu because its "webradios" are actually local radios
|
||||||
// which we get from the API with proper "ICI" names
|
// which we get from the API with proper "ICI" names
|
||||||
for main_station in &main_stations {
|
for main_station in &main_stations {
|
||||||
stations.push(main_station.clone());
|
// RÈGLE MÉTIER: Filtrer francebleu (portail générique, pas une vraie station)
|
||||||
|
|
||||||
// Skip francebleu - its local radios are discovered via API below
|
|
||||||
if main_station.slug == "francebleu" {
|
if main_station.slug == "francebleu" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stations.push(main_station.clone());
|
||||||
|
|
||||||
// Try to discover webradios (may return empty for some stations)
|
// Try to discover webradios (may return empty for some stations)
|
||||||
if let Ok(webradios) = self.discover_station_webradios(&main_station.slug).await {
|
if let Ok(webradios) = self.discover_station_webradios(&main_station.slug).await {
|
||||||
stations.extend(webradios);
|
stations.extend(webradios);
|
||||||
@@ -434,10 +434,10 @@ impl RadioFranceClient {
|
|||||||
return ("mouv", Some(slug));
|
return ("mouv", Some(slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
// France Bleu local radios use their slug directly, no webradio param
|
// France Bleu local radios are webradios of francebleu
|
||||||
// e.g., francebleu_alsace → francebleu_alsace/api/live
|
// e.g., francebleu_alsace → francebleu/api/live?webradio=francebleu-alsace
|
||||||
if slug.starts_with("francebleu_") {
|
if slug.starts_with("francebleu_") {
|
||||||
return (slug, None);
|
return ("francebleu", Some(slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main stations
|
// Main stations
|
||||||
|
|||||||
@@ -158,20 +158,21 @@ pub struct Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Media {
|
impl Media {
|
||||||
/// Find the best HiFi stream (AAC 192 kbps or HLS)
|
/// Find the best HiFi stream (AAC ou MP3, bitrate maximum, jamais HLS)
|
||||||
pub fn best_hifi_stream(&self) -> Option<&StreamSource> {
|
pub fn best_hifi_stream(&self) -> Option<&StreamSource> {
|
||||||
// Priority: AAC 192 kbps > HLS
|
// Priority: AAC 192 kbps > AAC autre bitrate > MP3 bitrate max > autre
|
||||||
|
// JAMAIS HLS (incompatible avec beaucoup de lecteurs)
|
||||||
self.sources
|
self.sources
|
||||||
.iter()
|
.iter()
|
||||||
.find(|s| {
|
.filter(|s| s.broadcast_type == BroadcastType::Live && s.format != StreamFormat::Hls)
|
||||||
s.format == StreamFormat::Aac
|
.max_by_key(|s| {
|
||||||
&& s.broadcast_type == BroadcastType::Live
|
// Priorité: format puis bitrate
|
||||||
&& s.bitrate == 192
|
let format_priority = match s.format {
|
||||||
})
|
StreamFormat::Aac => 1000,
|
||||||
.or_else(|| {
|
StreamFormat::Mp3 => 500,
|
||||||
self.sources.iter().find(|s| {
|
StreamFormat::Hls => 0, // Filtré de toute façon
|
||||||
s.format == StreamFormat::Hls && s.broadcast_type == BroadcastType::Live
|
};
|
||||||
})
|
format_priority + s.bitrate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,31 +60,33 @@ impl StationGroups {
|
|||||||
let mut groups: Vec<StationGroup> = groups_map
|
let mut groups: Vec<StationGroup> = groups_map
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(group_key, mut stations)| {
|
.map(|(group_key, mut stations)| {
|
||||||
// Trier : station principale (sans _) en premier
|
// Trier : station principale en premier, puis webradios par ordre alphabétique
|
||||||
stations.sort_by_key(|s| {
|
stations.sort_by(|a, b| {
|
||||||
if s.slug == group_key {
|
let a_is_main = a.slug == group_key;
|
||||||
0 // Station principale en premier
|
let b_is_main = b.slug == group_key;
|
||||||
} else {
|
|
||||||
1
|
match (a_is_main, b_is_main) {
|
||||||
|
(true, false) => std::cmp::Ordering::Less, // Main avant webradios
|
||||||
|
(false, true) => std::cmp::Ordering::Greater, // Webradios après main
|
||||||
|
_ => a.name.cmp(&b.name), // Sinon tri alphabétique
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
StationGroup { stations }
|
StationGroup {
|
||||||
|
stations,
|
||||||
|
group_name: None,
|
||||||
|
group_slug: None,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Ajouter le groupe ICI si on a des radios locales
|
// Ajouter le groupe ICI si on a des radios locales
|
||||||
if !ici_stations.is_empty() {
|
if !ici_stations.is_empty() {
|
||||||
ici_stations.sort_by(|a, b| a.name.cmp(&b.name));
|
ici_stations.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
// Créer une station virtuelle "ici" comme station principale
|
|
||||||
let ici_main = Station {
|
|
||||||
slug: "ici".to_string(),
|
|
||||||
name: "Radios ICI".to_string(),
|
|
||||||
};
|
|
||||||
let mut ici_group_stations = vec![ici_main];
|
|
||||||
ici_group_stations.extend(ici_stations);
|
|
||||||
groups.push(StationGroup {
|
groups.push(StationGroup {
|
||||||
stations: ici_group_stations,
|
stations: ici_stations,
|
||||||
|
group_name: Some("Radios ICI".to_string()),
|
||||||
|
group_slug: Some("ici".to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +133,27 @@ impl StationGroups {
|
|||||||
|
|
||||||
/// Groupe de stations - Niveau 1
|
/// Groupe de stations - Niveau 1
|
||||||
///
|
///
|
||||||
/// Index 0 = station principale du groupe (ex: FIP pour le groupe FIP)
|
/// Index 0 = première station du groupe (donne le nom/slug par défaut)
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct StationGroup {
|
pub struct StationGroup {
|
||||||
pub stations: Vec<Station>,
|
pub stations: Vec<Station>,
|
||||||
|
/// Nom personnalisé du groupe (optionnel, sinon utilise le nom de stations[0])
|
||||||
|
pub group_name: Option<String>,
|
||||||
|
/// Slug personnalisé du groupe (optionnel, sinon utilise le slug de stations[0])
|
||||||
|
pub group_slug: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StationGroup {
|
impl StationGroup {
|
||||||
|
/// Retourne le nom du groupe (personnalisé ou nom de la première station)
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.group_name.as_deref().unwrap_or(&self.stations[0].name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retourne le slug du groupe (personnalisé ou slug de la première station)
|
||||||
|
pub fn slug(&self) -> &str {
|
||||||
|
self.group_slug.as_deref().unwrap_or(&self.stations[0].slug)
|
||||||
|
}
|
||||||
|
|
||||||
/// Niveau 1: to_stub() retourne comment ce groupe apparaît dans la liste de StationGroups
|
/// Niveau 1: to_stub() retourne comment ce groupe apparaît dans la liste de StationGroups
|
||||||
///
|
///
|
||||||
/// - Si 1 station: retourne une playlist avec métadonnées (pour avoir titre/artiste à jour)
|
/// - Si 1 station: retourne une playlist avec métadonnées (pour avoir titre/artiste à jour)
|
||||||
@@ -154,19 +170,18 @@ impl StationGroup {
|
|||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
// Groupe multi-stations : juste le nom, pas de métadonnées
|
// Groupe multi-stations : juste le nom, pas de métadonnées
|
||||||
let main_station = &self.stations[0];
|
|
||||||
let album_art = Some(format!(
|
let album_art = Some(format!(
|
||||||
"{}/api/radiofrance/default-logo",
|
"{}/api/radiofrance/default-logo",
|
||||||
server_base_url.trim_end_matches('/')
|
server_base_url.trim_end_matches('/')
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok(Container {
|
Ok(Container {
|
||||||
id: format!("radiofrance:group:{}", main_station.slug),
|
id: format!("radiofrance:group:{}", self.slug()),
|
||||||
parent_id: "radiofrance".to_string(),
|
parent_id: "radiofrance".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(self.stations.len().saturating_sub(1).to_string()),
|
child_count: Some(self.stations.len().to_string()),
|
||||||
searchable: Some("0".to_string()),
|
searchable: Some("0".to_string()),
|
||||||
title: main_station.name.clone(),
|
title: self.name().to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
artist: None,
|
artist: None,
|
||||||
album_art,
|
album_art,
|
||||||
@@ -179,7 +194,7 @@ impl StationGroup {
|
|||||||
/// Niveau 1: to_didl() retourne le container du groupe avec TOUT son contenu
|
/// Niveau 1: to_didl() retourne le container du groupe avec TOUT son contenu
|
||||||
///
|
///
|
||||||
/// - Si 1 station: retourne la playlist complète avec l'item stream
|
/// - Si 1 station: retourne la playlist complète avec l'item stream
|
||||||
/// - Si plusieurs: retourne le container avec toutes les playlists des webradios en stub
|
/// - Si plusieurs: retourne le container avec toutes les playlists en stub
|
||||||
pub async fn to_didl(
|
pub async fn to_didl(
|
||||||
&self,
|
&self,
|
||||||
metadata_cache: &MetadataCache,
|
metadata_cache: &MetadataCache,
|
||||||
@@ -191,15 +206,34 @@ impl StationGroup {
|
|||||||
.to_didl(metadata_cache, server_base_url)
|
.to_didl(metadata_cache, server_base_url)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
// Groupe multi-stations : retourner un container avec les playlists en stub
|
// Groupe multi-stations : retourner un container avec toutes les playlists en stub
|
||||||
let main_station = &self.stations[0];
|
// Chargement en parallèle avec décalage pour éviter de surcharger l'API
|
||||||
let mut containers = Vec::new();
|
use futures::stream::{self, StreamExt};
|
||||||
|
|
||||||
for station in &self.stations[1..] {
|
let futures: Vec<_> = self
|
||||||
// Appeler to_stub() sur chaque station
|
.stations
|
||||||
let playlist_stub = station.to_stub(metadata_cache, server_base_url).await?;
|
.iter()
|
||||||
containers.push(playlist_stub);
|
.enumerate()
|
||||||
}
|
.map(|(i, station)| async move {
|
||||||
|
// Décalage de 50ms entre chaque requête
|
||||||
|
if i > 0 {
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
station.to_stub(metadata_cache, server_base_url).await
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let results: Vec<Result<Container>> = stream::iter(futures)
|
||||||
|
.buffer_unordered(5) // Maximum 5 requêtes en parallèle
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// to_stub() ne devrait plus échouer (utilise des valeurs par défaut)
|
||||||
|
// mais on gère quand même les erreurs par sécurité
|
||||||
|
let containers: Vec<Container> = results
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|result| result.ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let album_art = Some(format!(
|
let album_art = Some(format!(
|
||||||
"{}/api/radiofrance/default-logo",
|
"{}/api/radiofrance/default-logo",
|
||||||
@@ -207,12 +241,12 @@ impl StationGroup {
|
|||||||
));
|
));
|
||||||
|
|
||||||
Ok(Container {
|
Ok(Container {
|
||||||
id: format!("radiofrance:group:{}", main_station.slug),
|
id: format!("radiofrance:group:{}", self.slug()),
|
||||||
parent_id: "radiofrance".to_string(),
|
parent_id: "radiofrance".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(containers.len().to_string()),
|
child_count: Some(containers.len().to_string()),
|
||||||
searchable: Some("0".to_string()),
|
searchable: Some("0".to_string()),
|
||||||
title: main_station.name.clone(),
|
title: self.name().to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
artist: None,
|
artist: None,
|
||||||
album_art,
|
album_art,
|
||||||
@@ -230,29 +264,55 @@ impl StationGroup {
|
|||||||
impl Station {
|
impl Station {
|
||||||
/// Niveau 2: to_stub() retourne comment cette station apparaît dans la liste d'un groupe
|
/// Niveau 2: to_stub() retourne comment cette station apparaît dans la liste d'un groupe
|
||||||
///
|
///
|
||||||
/// Retourne un container de playlist vide avec métadonnées live
|
/// Retourne un container de playlist vide avec métadonnées live ou par défaut
|
||||||
pub async fn to_stub(
|
pub async fn to_stub(
|
||||||
&self,
|
&self,
|
||||||
metadata_cache: &MetadataCache,
|
metadata_cache: &MetadataCache,
|
||||||
_server_base_url: &str,
|
server_base_url: &str,
|
||||||
) -> Result<Container> {
|
) -> Result<Container> {
|
||||||
// Récupérer les métadonnées du cache
|
|
||||||
let cached_metadata = metadata_cache.get(&self.slug).await?;
|
|
||||||
|
|
||||||
// Construire juste le container de playlist (sans l'item stream)
|
|
||||||
let playlist_id = format!("radiofrance:{}", self.slug);
|
let playlist_id = format!("radiofrance:{}", self.slug);
|
||||||
let parent_id = self.compute_parent_id();
|
let parent_id = self.compute_parent_id();
|
||||||
|
|
||||||
|
// Essayer de récupérer les métadonnées du cache
|
||||||
|
let (title, artist, album_art) = match metadata_cache.get(&self.slug).await {
|
||||||
|
Ok(cached) => (
|
||||||
|
cached.title.clone(),
|
||||||
|
cached.artist.clone(),
|
||||||
|
cached.album_art.clone(),
|
||||||
|
),
|
||||||
|
Err(e) => {
|
||||||
|
// Graceful degradation: utiliser des métadonnées par défaut
|
||||||
|
#[cfg(feature = "logging")]
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to get metadata for {}, using defaults: {}",
|
||||||
|
self.slug,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
|
||||||
|
let default_art = Some(format!(
|
||||||
|
"{}/api/radiofrance/default-logo",
|
||||||
|
server_base_url.trim_end_matches('/')
|
||||||
|
));
|
||||||
|
|
||||||
|
(
|
||||||
|
self.name.clone(),
|
||||||
|
Some("Radio France".to_string()),
|
||||||
|
default_art,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construire juste le container de playlist (sans l'item stream)
|
||||||
Ok(Container {
|
Ok(Container {
|
||||||
id: playlist_id,
|
id: playlist_id,
|
||||||
parent_id,
|
parent_id,
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some("1".to_string()),
|
child_count: Some("1".to_string()),
|
||||||
searchable: Some("0".to_string()),
|
searchable: Some("0".to_string()),
|
||||||
title: cached_metadata.title.clone(),
|
title,
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
artist: cached_metadata.artist.clone(),
|
artist,
|
||||||
album_art: cached_metadata.album_art.clone(),
|
album_art,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
@@ -278,8 +338,9 @@ impl Station {
|
|||||||
|
|
||||||
/// Calcule le parent_id selon la position de la station
|
/// Calcule le parent_id selon la position de la station
|
||||||
fn compute_parent_id(&self) -> String {
|
fn compute_parent_id(&self) -> String {
|
||||||
if self.slug == "ici" {
|
if self.slug.starts_with("francebleu_") {
|
||||||
"radiofrance".to_string()
|
// Radio locale ICI (ex-France Bleu) : parent = groupe ICI
|
||||||
|
"radiofrance:group:ici".to_string()
|
||||||
} else if let Some(pos) = self.slug.find('_') {
|
} else if let Some(pos) = self.slug.find('_') {
|
||||||
// Webradio : parent = groupe
|
// Webradio : parent = groupe
|
||||||
format!("radiofrance:group:{}", &self.slug[..pos])
|
format!("radiofrance:group:{}", &self.slug[..pos])
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ impl MusicSource for RadioFranceSource {
|
|||||||
let group = groups
|
let group = groups
|
||||||
.groups
|
.groups
|
||||||
.iter()
|
.iter()
|
||||||
.find(|g| g.stations[0].slug == slug)
|
.find(|g| g.slug() == slug)
|
||||||
.ok_or_else(|| MusicSourceError::ObjectNotFound(id.to_string()))?;
|
.ok_or_else(|| MusicSourceError::ObjectNotFound(id.to_string()))?;
|
||||||
|
|
||||||
let container = group
|
let container = group
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3.17
|
0.3.18
|
||||||
|
|||||||
Reference in New Issue
Block a user