Changement du mécanisme d'attention sur les channels Radio Paradise.

This commit is contained in:
2025-11-29 14:19:16 +01:00
parent cf3f0afde4
commit 0a03f72467
44 changed files with 564 additions and 231 deletions

View File

@@ -10,10 +10,10 @@
//! - **Search** : Recherche dans les sources qui le supportent
//! - **Update ID** : Suivi des changements pour les notifications UPnP
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 pmoutils::ToXmlElement;
use std::collections::HashSet;
use std::sync::Arc;
@@ -123,20 +123,14 @@ impl ContentHandler {
container.child_count = None; // compatibilité CP
let didl = to_didl_lite(&[container], &[])?;
let update_id = source.update_id().await.max(1);
tracing::debug!(
"BrowseMetadata root (flatten) didl_len={}B",
didl.len()
);
tracing::debug!("BrowseMetadata root (flatten) didl_len={}B", didl.len());
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], &[])?;
tracing::debug!(
"BrowseMetadata root (aggregate) didl_len={}B",
didl.len()
);
tracing::debug!("BrowseMetadata root (aggregate) didl_len={}B", didl.len());
Ok((didl, 1, 1, 1))
} else {
// Essayer de trouver l'objet dans les sources
@@ -300,7 +294,13 @@ impl ContentHandler {
match source.browse(object_id).await {
Ok(result) => {
return self
.browse_result_to_didl(object_id, result, source, starting_index, requested_count)
.browse_result_to_didl(
object_id,
result,
source,
starting_index,
requested_count,
)
.await;
}
Err(MusicSourceError::ObjectNotFound(_)) => continue,

View File

@@ -76,14 +76,14 @@ use pmoupnp::define_service;
pub mod actions;
pub mod handlers;
pub mod variables;
pub mod state;
pub mod variables;
use actions::{BROWSE, GETSEARCHCAPABILITIES, GETSORTCAPABILITIES, GETSYSTEMUPDATEID, SEARCH};
use variables::{
A_ARG_TYPE_BROWSEFLAG, A_ARG_TYPE_COUNT, A_ARG_TYPE_FILTER, A_ARG_TYPE_INDEX,
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_RESULT, A_ARG_TYPE_SEARCHCRITERIA, A_ARG_TYPE_SORTCRITERIA,
A_ARG_TYPE_UPDATEID, SEARCHCAPABILITIES, SORTCAPABILITIES, SYSTEMUPDATEID, CONTAINERUPDATEIDS,
A_ARG_TYPE_UPDATEID, CONTAINERUPDATEIDS, SEARCHCAPABILITIES, SORTCAPABILITIES, SYSTEMUPDATEID,
};
// Service ContentDirectory:1 conforme à la spécification UPnP AV pour MediaServer

View File

@@ -1,6 +1,9 @@
use once_cell::sync::OnceCell;
use pmoupnp::{services::ServiceInstance, variable_types::StateValue};
use std::sync::{atomic::{AtomicU32, Ordering}, Arc, Weak, Mutex};
use std::sync::{
Arc, Mutex, Weak,
atomic::{AtomicU32, Ordering},
};
static CONTENTDIR_INSTANCE: OnceCell<Weak<ServiceInstance>> = OnceCell::new();
static SYSTEM_UPDATE_ID: AtomicU32 = AtomicU32::new(1);
@@ -17,7 +20,9 @@ pub fn register_instance(instance: &Arc<ServiceInstance>) {
/// Notifie une mise à jour en incrémentant SystemUpdateID et ContainerUpdateIDs.
/// `container_ids` doit contenir les IDs des conteneurs impactés.
pub fn notify_containers_updated(container_ids: &[&str]) {
let new_id = SYSTEM_UPDATE_ID.fetch_add(1, Ordering::Relaxed).saturating_add(1);
let new_id = SYSTEM_UPDATE_ID
.fetch_add(1, Ordering::Relaxed)
.saturating_add(1);
set_system_update_id(new_id);
if !container_ids.is_empty() {

View File

@@ -1,5 +1,4 @@
///! Extension trait pour initialiser le PMO Music MediaServer UPnP
use pmoupnp::devices::DeviceInstance;
use pmoupnp::variable_types::StateValue;
use std::sync::Arc;

View File

@@ -6,7 +6,6 @@
use anyhow::{Context, Result};
use async_trait::async_trait;
use axum::{
Json, Router,
body::Body,
extract::{Path, State},
http::{
@@ -15,11 +14,17 @@ use axum::{
},
response::{IntoResponse, Response},
routing::get,
Json, Router,
};
use pmoaudiocache::{get_audio_cache, register_audio_cache, AudioCacheExt, Cache as AudioCache};
use pmocovers::{get_cover_cache, register_cover_cache, Cache as CoverCache, CoverCacheExt};
use pmoparadise::{
channels::{ChannelDescriptor, ALL_CHANNELS},
stream_channel::register_global_channel_manager,
ParadiseChannelManager, ParadiseHistoryBuilder,
};
use pmoaudiocache::{AudioCacheExt, Cache as AudioCache, get_audio_cache, register_audio_cache};
use pmocovers::{Cache as CoverCache, CoverCacheExt, get_cover_cache, register_cover_cache};
use pmoparadise::{ParadiseChannelManager, ParadiseHistoryBuilder, channels::ALL_CHANNELS};
use pmoplaylist::register_audio_cache as register_playlist_audio_cache;
use pmoplaylist::{self, PlaylistEventKind};
use std::sync::Arc;
use tokio_util::io::ReaderStream;
use tracing::{error, info};
@@ -64,7 +69,9 @@ impl ParadiseStreamingExt for pmoserver::Server {
async fn init_paradise_streaming(&mut self) -> Result<Arc<ParadiseChannelManager>> {
info!("🎵 Initializing Radio Paradise streaming channels...");
// Sentinel log pour vérifier qu'on exécute bien cette version du binaire
tracing::warn!("🔍 Rien de neuf: entering init_paradise_streaming with caches+history setup");
tracing::warn!(
"🔍 Rien de neuf: entering init_paradise_streaming with caches+history setup"
);
// Récupérer ou initialiser les caches singletons
info!("📦 Getting cache singletons...");
@@ -143,6 +150,9 @@ impl ParadiseStreamingExt for pmoserver::Server {
}
};
register_global_channel_manager(manager.clone());
spawn_playlist_event_handler(manager.clone());
let state = Arc::new(ParadiseStreamingState {
manager: manager.clone(),
});
@@ -317,3 +327,56 @@ async fn stream_history_ogg(
.body(Body::from_stream(ReaderStream::new(stream)))
.unwrap())
}
fn spawn_playlist_event_handler(manager: Arc<ParadiseChannelManager>) {
tokio::spawn(async move {
let mut rx = pmoplaylist::subscribe_events();
while let Ok(envelope) = rx.recv().await {
if let PlaylistEventKind::TrackPlayed { cache_pk, .. } = envelope.event.kind {
if let Some(descriptor) = channel_from_live_playlist(&envelope.event.playlist_id) {
if let Err(e) = manager.prefetch_until_horizon(descriptor.id).await {
tracing::warn!(
"Failed to prefetch for channel {}: {}",
descriptor.display_name,
e
);
}
if let Err(e) = append_track_to_history(descriptor, &cache_pk).await {
tracing::warn!(
"Failed to update history for channel {}: {}",
descriptor.display_name,
e
);
}
}
}
}
});
}
fn channel_from_live_playlist(playlist_id: &str) -> Option<&'static ChannelDescriptor> {
const PREFIX: &str = "radio-paradise-live-";
let slug = playlist_id.strip_prefix(PREFIX)?;
ALL_CHANNELS
.iter()
.find(|descriptor| descriptor.slug == slug)
}
async fn append_track_to_history(descriptor: &ChannelDescriptor, cache_pk: &str) -> Result<()> {
let playlist_id = format!("radio-paradise-history-{}", descriptor.slug);
let manager = pmoplaylist::PlaylistManager();
let handle = manager
.get_persistent_write_handle(playlist_id.clone())
.await
.with_context(|| format!("Failed to get history playlist {}", playlist_id))?;
if handle.contains_pk(cache_pk).await? {
return Ok(());
}
handle
.push(cache_pk.to_string())
.await
.with_context(|| format!("Failed to append {} to {}", cache_pk, playlist_id))?;
Ok(())
}

View File

@@ -173,8 +173,8 @@ impl SourcesExt for Server {
#[cfg(feature = "paradise")]
async fn register_paradise(&mut self) -> Result<()> {
use pmoparadise::{RadioParadiseExt, RadioParadiseSource};
use crate::contentdirectory::state;
use pmoparadise::{RadioParadiseExt, RadioParadiseSource};
tracing::info!("Initializing Radio Paradise source...");