Amélioration du chargement des métadonnées et filtrage de francebleu

### Fichiers modifiés :
- `pmoradiofrance/src/playlist.rs`
- `pmoradiofrance/src/source.rs`
- `pmoradiofrance/src/client.rs`

### Changements structurels :

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 :
- **Chargement plus rapide** : les métadonnées sont récupérées en parallèle au lieu de séquentiellement
- Code plus simple et sans logique spéciale dispersée
- Interface prévisible : les stations sont toujours au même endroit
This commit is contained in:
2026-01-24 20:59:33 +01:00
parent 7d55b5c3f0
commit 3817fda38e
4 changed files with 81 additions and 22 deletions

View File

@@ -12,8 +12,10 @@ Refactoring complet pour supprimer la notion de "station virtuelle" et utiliser
### Modifications
**Fichier modifié :**
**Fichiers modifiés :**
- `pmoradiofrance/src/playlist.rs`
- `pmoradiofrance/src/source.rs`
- `pmoradiofrance/src/client.rs`
### Changements structurels
@@ -39,12 +41,23 @@ Refactoring complet pour supprimer la notion de "station virtuelle" et utiliser
- 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

View File

@@ -134,13 +134,13 @@ impl RadioFranceClient {
// Note: Skip francebleu because its "webradios" are actually local radios
// which we get from the API with proper "ICI" names
for main_station in &main_stations {
stations.push(main_station.clone());
// Skip francebleu - its local radios are discovered via API below
// RÈGLE MÉTIER: Filtrer francebleu (portail générique, pas une vraie station)
if main_station.slug == "francebleu" {
continue;
}
stations.push(main_station.clone());
// Try to discover webradios (may return empty for some stations)
if let Ok(webradios) = self.discover_station_webradios(&main_station.slug).await {
stations.extend(webradios);
@@ -434,10 +434,10 @@ impl RadioFranceClient {
return ("mouv", Some(slug));
}
// France Bleu local radios use their slug directly, no webradio param
// e.g., francebleu_alsace → francebleu_alsace/api/live
// France Bleu local radios are webradios of francebleu
// e.g., francebleu_alsace → francebleu/api/live?webradio=francebleu-alsace
if slug.starts_with("francebleu_") {
return (slug, None);
return ("francebleu", Some(slug));
}
// Main stations

View File

@@ -150,7 +150,7 @@ impl StationGroup {
}
/// Retourne le slug du groupe (personnalisé ou slug de la première station)
fn slug(&self) -> &str {
pub fn slug(&self) -> &str {
self.group_slug.as_deref().unwrap_or(&self.stations[0].slug)
}
@@ -207,13 +207,33 @@ impl StationGroup {
.await
} else {
// Groupe multi-stations : retourner un container avec toutes les playlists en stub
let mut containers = Vec::new();
// Chargement en parallèle avec décalage pour éviter de surcharger l'API
use futures::stream::{self, StreamExt};
for station in &self.stations {
// Appeler to_stub() sur chaque station
let playlist_stub = station.to_stub(metadata_cache, server_base_url).await?;
containers.push(playlist_stub);
}
let futures: Vec<_> = self
.stations
.iter()
.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!(
"{}/api/radiofrance/default-logo",
@@ -244,17 +264,43 @@ impl StationGroup {
impl Station {
/// 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(
&self,
metadata_cache: &MetadataCache,
_server_base_url: &str,
server_base_url: &str,
) -> Result<Container> {
let playlist_id = format!("radiofrance:{}", self.slug);
let parent_id = self.compute_parent_id();
// Récupérer les métadonnées du cache
let cached_metadata = metadata_cache.get(&self.slug).await?;
// 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 {
@@ -263,10 +309,10 @@ impl Station {
restricted: Some("1".to_string()),
child_count: Some("1".to_string()),
searchable: Some("0".to_string()),
title: cached_metadata.title.clone(),
title,
class: "object.container.playlistContainer".to_string(),
artist: cached_metadata.artist.clone(),
album_art: cached_metadata.album_art.clone(),
artist,
album_art,
containers: vec![],
items: vec![],
})

View File

@@ -268,7 +268,7 @@ impl MusicSource for RadioFranceSource {
let group = groups
.groups
.iter()
.find(|g| g.stations[0].slug == slug)
.find(|g| g.slug() == slug)
.ok_or_else(|| MusicSourceError::ObjectNotFound(id.to_string()))?;
let container = group