From 7d55b5c3f0f350223751d19e0a6e874e6cb8e1ec Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 24 Jan 2026 20:19:40 +0100 Subject: [PATCH 1/3] Corrige l'affichage des groupes de radios et supprime la station virtuelle 'ici' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correction des bugs d'affichage des groupes de radios : - La radio principale n'était plus présente dans les groupes multi-stations - Le groupe ICI était inaccessible en raison d'une station virtuelle mal gérée Implémentation d'un refactoring complet : - Suppression de la notion de 'station virtuelle' - Ajout de champs optionnels `group_name` et `group_slug` dans `StationGroup` - Simplification de la logique de tri et de génération des IDs - Les stations sont maintenant triées alphabétiquement dans chaque groupe - Le groupe ICI est désormais accessible et affiche toutes les radios locales triées --- Blackboard/Report/Oubli_Main_Radio.md | 51 ++++++++++++++++++ Blackboard/Todo/Oubli_Main_Radio.md | 15 ++++++ pmoradiofrance/src/playlist.rs | 77 ++++++++++++++++----------- 3 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 Blackboard/Report/Oubli_Main_Radio.md create mode 100644 Blackboard/Todo/Oubli_Main_Radio.md diff --git a/Blackboard/Report/Oubli_Main_Radio.md b/Blackboard/Report/Oubli_Main_Radio.md new file mode 100644 index 00000000..4e6d773f --- /dev/null +++ b/Blackboard/Report/Oubli_Main_Radio.md @@ -0,0 +1,51 @@ +# 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 + +**Fichier modifié :** +- `pmoradiofrance/src/playlist.rs` + +### Changements structurels + +1. **Ajout de champs optionnels à `StationGroup`** : + - `group_name: Option` : nom personnalisé du groupe (pour ICI : "Radios ICI") + - `group_slug: Option` : 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 + +### 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 + +## Statut + +Modifications terminées. Compilation à vérifier par l'utilisateur. diff --git a/Blackboard/Todo/Oubli_Main_Radio.md b/Blackboard/Todo/Oubli_Main_Radio.md new file mode 100644 index 00000000..03174cf3 --- /dev/null +++ b/Blackboard/Todo/Oubli_Main_Radio.md @@ -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. diff --git a/pmoradiofrance/src/playlist.rs b/pmoradiofrance/src/playlist.rs index 5c78fda6..ee26c7e8 100644 --- a/pmoradiofrance/src/playlist.rs +++ b/pmoradiofrance/src/playlist.rs @@ -60,31 +60,33 @@ impl StationGroups { let mut groups: Vec = groups_map .into_iter() .map(|(group_key, mut stations)| { - // Trier : station principale (sans _) en premier - stations.sort_by_key(|s| { - if s.slug == group_key { - 0 // Station principale en premier - } else { - 1 + // Trier : station principale en premier, puis webradios par ordre alphabétique + stations.sort_by(|a, b| { + let a_is_main = a.slug == group_key; + let b_is_main = b.slug == group_key; + + 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(); // Ajouter le groupe ICI si on a des radios locales if !ici_stations.is_empty() { 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 { - 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 /// -/// 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)] pub struct StationGroup { pub stations: Vec, + /// Nom personnalisé du groupe (optionnel, sinon utilise le nom de stations[0]) + pub group_name: Option, + /// Slug personnalisé du groupe (optionnel, sinon utilise le slug de stations[0]) + pub group_slug: Option, } 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) + 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 /// /// - Si 1 station: retourne une playlist avec métadonnées (pour avoir titre/artiste à jour) @@ -154,19 +170,18 @@ impl StationGroup { .await } else { // Groupe multi-stations : juste le nom, pas de métadonnées - let main_station = &self.stations[0]; let album_art = Some(format!( "{}/api/radiofrance/default-logo", server_base_url.trim_end_matches('/') )); Ok(Container { - id: format!("radiofrance:group:{}", main_station.slug), + id: format!("radiofrance:group:{}", self.slug()), parent_id: "radiofrance".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()), - title: main_station.name.clone(), + title: self.name().to_string(), class: "object.container".to_string(), artist: None, album_art, @@ -179,7 +194,7 @@ impl StationGroup { /// 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 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( &self, metadata_cache: &MetadataCache, @@ -191,11 +206,10 @@ impl StationGroup { .to_didl(metadata_cache, server_base_url) .await } else { - // Groupe multi-stations : retourner un container avec les playlists en stub - let main_station = &self.stations[0]; + // Groupe multi-stations : retourner un container avec toutes les playlists en stub let mut containers = Vec::new(); - for station in &self.stations[1..] { + 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); @@ -207,12 +221,12 @@ impl StationGroup { )); Ok(Container { - id: format!("radiofrance:group:{}", main_station.slug), + id: format!("radiofrance:group:{}", self.slug()), parent_id: "radiofrance".to_string(), restricted: Some("1".to_string()), child_count: Some(containers.len().to_string()), searchable: Some("0".to_string()), - title: main_station.name.clone(), + title: self.name().to_string(), class: "object.container".to_string(), artist: None, album_art, @@ -236,13 +250,13 @@ impl Station { metadata_cache: &MetadataCache, _server_base_url: &str, ) -> Result { + 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?; // Construire juste le container de playlist (sans l'item stream) - let playlist_id = format!("radiofrance:{}", self.slug); - let parent_id = self.compute_parent_id(); - Ok(Container { id: playlist_id, parent_id, @@ -278,8 +292,9 @@ impl Station { /// Calcule le parent_id selon la position de la station fn compute_parent_id(&self) -> String { - if self.slug == "ici" { - "radiofrance".to_string() + if self.slug.starts_with("francebleu_") { + // Radio locale ICI (ex-France Bleu) : parent = groupe ICI + "radiofrance:group:ici".to_string() } else if let Some(pos) = self.slug.find('_') { // Webradio : parent = groupe format!("radiofrance:group:{}", &self.slug[..pos]) -- 2.49.1 From 3817fda38e5932ac3ca61bbd2d1ea135ac801780 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 24 Jan 2026 20:59:33 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Am=C3=A9lioration=20du=20chargement=20des?= =?UTF-8?q?=20m=C3=A9tadonn=C3=A9es=20et=20filtrage=20de=20francebleu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 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 --- Blackboard/Report/Oubli_Main_Radio.md | 15 +++++- pmoradiofrance/src/client.rs | 12 ++--- pmoradiofrance/src/playlist.rs | 74 ++++++++++++++++++++++----- pmoradiofrance/src/source.rs | 2 +- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/Blackboard/Report/Oubli_Main_Radio.md b/Blackboard/Report/Oubli_Main_Radio.md index 4e6d773f..396ef143 100644 --- a/Blackboard/Report/Oubli_Main_Radio.md +++ b/Blackboard/Report/Oubli_Main_Radio.md @@ -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 diff --git a/pmoradiofrance/src/client.rs b/pmoradiofrance/src/client.rs index f689640f..d6653631 100644 --- a/pmoradiofrance/src/client.rs +++ b/pmoradiofrance/src/client.rs @@ -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 diff --git a/pmoradiofrance/src/playlist.rs b/pmoradiofrance/src/playlist.rs index ee26c7e8..a221ea52 100644 --- a/pmoradiofrance/src/playlist.rs +++ b/pmoradiofrance/src/playlist.rs @@ -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> = 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 = 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 { 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![], }) diff --git a/pmoradiofrance/src/source.rs b/pmoradiofrance/src/source.rs index 4784add2..84bf3d51 100644 --- a/pmoradiofrance/src/source.rs +++ b/pmoradiofrance/src/source.rs @@ -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 -- 2.49.1 From 4b20c966e017e12fbe65c428cc5887e219eb38af Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 24 Jan 2026 21:20:35 +0100 Subject: [PATCH 3/3] Bump version to 0.3.18 and improve stream handling Version bump from 0.3.17 to 0.3.18 - Updated version in Cargo.toml and version.txt - Removed unused LiveResponse import in pmoradiofrance - Added tracing info for stream proxyfication - Improved Media::best_hifi_stream logic to prioritize AAC over MP3 and never use HLS streams - Updated comments to reflect new stream priority logic --- Cargo.lock | 2 +- PMOMusic/Cargo.toml | 2 +- pmoradiofrance/src/api_rest.rs | 7 ++++++- pmoradiofrance/src/models.rs | 23 ++++++++++++----------- version.txt | 2 +- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce9b081a..48838a9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.17" +version = "0.3.18" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index c2229785..ebc7ba86 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.17" +version = "0.3.18" edition = "2024" [dependencies] diff --git a/pmoradiofrance/src/api_rest.rs b/pmoradiofrance/src/api_rest.rs index adf98c1c..59102440 100644 --- a/pmoradiofrance/src/api_rest.rs +++ b/pmoradiofrance/src/api_rest.rs @@ -3,7 +3,6 @@ //! Ce module définit les handlers HTTP pour accéder aux stations Radio France, //! leurs métadonnées live et les flux de streaming. -use crate::models::LiveResponse; use crate::playlist::StationGroups; use crate::pmoserver_ext::RadioFranceState; use axum::{ @@ -16,6 +15,7 @@ use axum::{ }; use futures::StreamExt; use serde_json; +use tracing::info; // ============ Gestion des erreurs ============ @@ -97,6 +97,11 @@ async fn proxy_stream( 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 let response = match reqwest::get(&stream_url).await { Ok(r) => r, diff --git a/pmoradiofrance/src/models.rs b/pmoradiofrance/src/models.rs index 15895291..3e7a82e2 100644 --- a/pmoradiofrance/src/models.rs +++ b/pmoradiofrance/src/models.rs @@ -158,20 +158,21 @@ pub struct 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> { - // 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 .iter() - .find(|s| { - s.format == StreamFormat::Aac - && s.broadcast_type == BroadcastType::Live - && s.bitrate == 192 - }) - .or_else(|| { - self.sources.iter().find(|s| { - s.format == StreamFormat::Hls && s.broadcast_type == BroadcastType::Live - }) + .filter(|s| s.broadcast_type == BroadcastType::Live && s.format != StreamFormat::Hls) + .max_by_key(|s| { + // Priorité: format puis bitrate + let format_priority = match s.format { + StreamFormat::Aac => 1000, + StreamFormat::Mp3 => 500, + StreamFormat::Hls => 0, // Filtré de toute façon + }; + format_priority + s.bitrate }) } diff --git a/version.txt b/version.txt index e5a9958c..8355eafc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.17 +0.3.18 -- 2.49.1