Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements.
## Objectifs
1. Simplifier les structures de stations (supprimer StationType)
2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time`
3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine)
4. Implémenter un système d'événements pour la synchronisation GENA
5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL
6. Gérer automatiquement le cache des covers via pmocovers
## Changements architecturaux majeurs
### 1. Nouveau fichier: metadata_cache.rs
**Créé**: `pmoradiofrance/src/metadata_cache.rs`
Contient deux structures principales:
- **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.)
- **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements
**Fonctionnalités**:
- TTL basé sur `end_time` de l'API Radio France
- Gestion automatique du cache de covers via pmocovers
- Système subscribe/notify pour les événements
- Graceful degradation si API Radio France down
- Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques
### 2. Suppression: stateful_client.rs
**Supprimé**: `pmoradiofrance/src/stateful_client.rs`
Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module.
### 3. Simplification: models.rs
**Modifications**:
- Supprimé `StationType` enum
- Simplifié `Station` struct (juste `slug` + `name`)
- Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()`
- Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.)
### 4. Simplification: playlist.rs
**Modifications**:
- Supprimé `StationPlaylist` complètement
- Simplifié `StationGroup` et `StationGroups`
- **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`)
- Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial)
- Préservé les règles de mapping RF → UPnP existantes
### 5. Refactoring: source.rs
**Modifications**:
- Utilise uniquement `MetadataCache` (plus de `stateful_client`)
- Simplifié `browse()` en 3 cas simples
- Abonnement aux événements du cache pour GENA
- Retourne des `Container` (cohérence avec to_didl)
### 6. Adaptation: config_ext.rs
**Modifications**:
- Format simplifié: `Vec<Station>` au lieu de `CachedStationList`
- TTL reste à 7 jours (1 semaine)
### 7. Mise à jour: lib.rs
**Modifications**:
- Ajouté `pub mod metadata_cache;`
- Supprimé export de `stateful_client`
- Ajouté exports: `MetadataCache`, `CachedMetadata`
## Hiérarchie de browse
**Niveau 0**: `radiofrance`
- Retourne UN Container contenant les containers de groupes
- Exemple: Container "FIP", Container "France Culture", Container "ICI"
**Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici`
- Si 1 station: retourne directement la playlist (Container playlistContainer)
- Si plusieurs stations: retourne un container contenant les playlists
**Niveau 2**: `radiofrance:fip`
- Retourne Container playlistContainer avec 1 item
- Métadonnées identiques entre playlist et item
## Règles de mapping préservées
Les règles existantes de transformation RF → UPnP ont été préservées:
- Radio musicale avec song → métadonnées du morceau
- Radio parlée → agrégation émission/producteur
- Éviter duplications du nom de station
- Calcul de duration depuis end_time
## Système d'événements
**Flux**:
1. `MetadataCache` rafraîchit les métadonnées d'un slug
2. Notifie tous les abonnés via `notify(slug)`
3. `RadioFranceSource` reçoit l'événement
4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}`
5. Le Control Point reçoit la notification et peut se mettre à jour
## Fichiers modifiés
### Créés
- `pmoradiofrance/src/metadata_cache.rs`
### Supprimés
- `pmoradiofrance/src/stateful_client.rs`
### Modifiés
- `pmoradiofrance/src/models.rs`
- `pmoradiofrance/src/playlist.rs`
- `pmoradiofrance/src/source.rs`
- `pmoradiofrance/src/config_ext.rs`
- `pmoradiofrance/src/lib.rs`
### Inchangés
- `pmoradiofrance/src/client.rs`
- `pmoradiofrance/src/error.rs`
## Points de vigilance
1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format)
2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry
3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread
4. **Graceful degradation**: Retourne cache expiré si API Radio France down
## Prochaines étapes
1. Tester le cache de métadonnées (TTL, refresh, graceful degradation)
2. Tester le système d'événements
3. Tester le browse sur les 3 niveaux
4. Vérifier les événements GENA
5. Vérifier que les covers sont correctement cachées
138 lines
4.1 KiB
Rust
138 lines
4.1 KiB
Rust
//! Radio France client library for PMOMusic
|
|
//!
|
|
//! This crate provides a Rust client for accessing Radio France's public APIs,
|
|
//! including live metadata, station discovery, and stream URLs.
|
|
//!
|
|
//! # Features
|
|
//!
|
|
//! - **Station Discovery**: Discover all Radio France stations dynamically
|
|
//! (main stations, webradios, and local France Bleu radios)
|
|
//! - **Live Metadata**: Get current show information, producers, visuals
|
|
//! - **Stream URLs**: Get HiFi stream URLs (AAC 192 kbps, HLS)
|
|
//! - **Polling Support**: Intelligent refresh delay based on API recommendations
|
|
//! - **Configuration Extension**: Cache station lists with configurable TTL
|
|
//!
|
|
//! # Supported Stations
|
|
//!
|
|
//! - **Main Stations**: France Inter, France Info, France Culture, France Musique,
|
|
//! FIP, Mouv', France Bleu
|
|
//! - **Webradios**: FIP Rock, FIP Jazz, France Musique Baroque, etc.
|
|
//! - **Local Radios**: ~40 France Bleu local stations
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```no_run
|
|
//! use pmoradiofrance::{RadioFranceClient, ImageSize};
|
|
//!
|
|
//! #[tokio::main]
|
|
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
//! let client = RadioFranceClient::new().await?;
|
|
//!
|
|
//! // Discover all stations
|
|
//! let stations = client.discover_all_stations().await?;
|
|
//! println!("Found {} stations", stations.len());
|
|
//!
|
|
//! // Get live metadata
|
|
//! let live = client.live_metadata("franceculture").await?;
|
|
//! println!("Now: {} - {}",
|
|
//! live.now.first_line.title_or_default(),
|
|
//! live.now.second_line.title_or_default()
|
|
//! );
|
|
//!
|
|
//! // Get HiFi stream URL
|
|
//! let stream_url = client.get_hifi_stream_url("franceculture").await?;
|
|
//! println!("Stream: {}", stream_url);
|
|
//!
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! # Configuration Extension
|
|
//!
|
|
//! When the `pmoconfig` feature is enabled, this crate provides a configuration
|
|
//! extension trait for caching station lists:
|
|
//!
|
|
//! ```no_run
|
|
//! use pmoconfig::get_config;
|
|
//! use pmoradiofrance::{RadioFranceConfigExt, RadioFranceClient};
|
|
//!
|
|
//! # #[tokio::main]
|
|
//! # async fn main() -> anyhow::Result<()> {
|
|
//! let config = get_config();
|
|
//!
|
|
//! // Check cached stations (default TTL: 7 days)
|
|
//! if let Some(stations) = config.get_radiofrance_stations_cached()? {
|
|
//! println!("Using {} cached stations", stations.len());
|
|
//! } else {
|
|
//! // Cache miss - need to discover
|
|
//! let client = RadioFranceClient::new().await?;
|
|
//! let stations = client.discover_all_stations().await?;
|
|
//! config.set_radiofrance_cached_stations(&stations)?;
|
|
//! }
|
|
//! # Ok(())
|
|
//! # }
|
|
//! ```
|
|
//!
|
|
//! # API Rate Limiting
|
|
//!
|
|
//! Radio France's APIs don't have documented rate limits, but the `delayToRefresh`
|
|
//! field in responses indicates the recommended polling interval. Always use
|
|
//! `RadioFranceClient::next_refresh_delay()` to respect this.
|
|
//!
|
|
//! # Audio Quality
|
|
//!
|
|
//! This client focuses on HiFi quality only:
|
|
//! - **AAC 192 kbps**: Primary format (best quality)
|
|
//! - **HLS**: Adaptive streaming fallback
|
|
//!
|
|
//! Lower quality formats (lofi, midfi) are not prioritized but are available
|
|
//! in the `StreamSource` list if needed.
|
|
|
|
pub mod client;
|
|
pub mod error;
|
|
pub mod models;
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
pub mod config_ext;
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
pub mod metadata_cache;
|
|
|
|
#[cfg(feature = "playlist")]
|
|
pub mod playlist;
|
|
|
|
#[cfg(feature = "server")]
|
|
pub mod source;
|
|
|
|
#[cfg(feature = "server")]
|
|
pub mod pmoserver_ext;
|
|
|
|
#[cfg(feature = "server")]
|
|
pub mod pmoserver_impl;
|
|
|
|
#[cfg(feature = "server")]
|
|
pub mod api_rest;
|
|
|
|
// Re-exports
|
|
pub use client::{ClientBuilder, RadioFranceClient};
|
|
pub use error::{Error, Result};
|
|
pub use models::{
|
|
BroadcastType, EmbedImage, ImageSize, Line, LiveResponse, LocalRadio, Media, Release,
|
|
ShowMetadata, Song, Station, StreamFormat, StreamSource, Visuals,
|
|
};
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
pub use config_ext::RadioFranceConfigExt;
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
pub use metadata_cache::{CachedMetadata, MetadataCache, MetadataUpdateCallback};
|
|
|
|
#[cfg(feature = "playlist")]
|
|
pub use playlist::{StationGroup, StationGroups};
|
|
|
|
#[cfg(feature = "server")]
|
|
pub use source::RadioFranceSource;
|
|
|
|
#[cfg(feature = "server")]
|
|
pub use pmoserver_ext::{RadioFranceExt, RadioFranceState};
|