Debug playlist lecture
This commit is contained in:
@@ -1833,7 +1833,14 @@ impl ControlPoint {
|
||||
binding: Some(binding),
|
||||
});
|
||||
|
||||
let mut auto_start_cb = |rid: &RendererId| self.start_queue_playback_if_idle(rid);
|
||||
// For initial attach with auto_play, force playback start (don't check if idle)
|
||||
let mut auto_start_cb = |rid: &RendererId| {
|
||||
debug!(
|
||||
renderer = rid.0.as_str(),
|
||||
"Attach callback: forcing playback start (not checking if idle)"
|
||||
);
|
||||
self.play_current_from_queue(rid)
|
||||
};
|
||||
let callback: Option<&mut dyn FnMut(&RendererId) -> anyhow::Result<()>> = if auto_play {
|
||||
Some(&mut auto_start_cb)
|
||||
} else {
|
||||
@@ -2683,6 +2690,12 @@ fn refresh_attached_queue_for(
|
||||
|
||||
if auto_play {
|
||||
if let Some(callback) = after_refresh.as_deref_mut() {
|
||||
debug!(
|
||||
renderer = renderer_id.0.as_str(),
|
||||
server = server_id.0.as_str(),
|
||||
container = container_id.as_str(),
|
||||
"Auto-play enabled: calling callback to start playback"
|
||||
);
|
||||
if let Err(err) = callback(renderer_id) {
|
||||
warn!(
|
||||
renderer = renderer_id.0.as_str(),
|
||||
@@ -2691,8 +2704,25 @@ fn refresh_attached_queue_for(
|
||||
error = %err,
|
||||
"Failed to auto-start playback after playlist refresh"
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
renderer = renderer_id.0.as_str(),
|
||||
server = server_id.0.as_str(),
|
||||
container = container_id.as_str(),
|
||||
"Auto-play callback completed successfully"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
renderer = renderer_id.0.as_str(),
|
||||
"Auto-play enabled but no callback provided"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
renderer = renderer_id.0.as_str(),
|
||||
"Auto-play disabled, skipping playback start"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
PlaybackPosition, PlaybackState, TransportControl, UpnpRenderer, VolumeControl,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use tracing::warn;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// Backend-agnostic façade exposing transport, volume, and status contracts.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -281,8 +281,18 @@ impl MusicRenderer {
|
||||
pub fn clear_for_playlist_attach(&self) -> Result<()> {
|
||||
match self {
|
||||
MusicRenderer::OpenHome(_) => {
|
||||
// For OpenHome: clear the playlist on the renderer itself
|
||||
self.openhome_playlist_clear()
|
||||
// For OpenHome: clear the playlist (DeleteAll also stops playback automatically)
|
||||
// then explicitly stop to ensure clean state
|
||||
self.openhome_playlist_clear()?;
|
||||
self.stop().or_else(|err| -> Result<()> {
|
||||
// If stop fails (e.g., already stopped), that's fine
|
||||
warn!(
|
||||
renderer = self.id().0.as_str(),
|
||||
error = %err,
|
||||
"Stop failed after clearing OpenHome playlist (continuing anyway)"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
MusicRenderer::Upnp(_)
|
||||
| MusicRenderer::Chromecast(_)
|
||||
@@ -436,21 +446,57 @@ impl MusicRenderer {
|
||||
// Get the current OpenHome playlist snapshot
|
||||
let snapshot = self.fetch_openhome_playlist_snapshot()?;
|
||||
|
||||
debug!(
|
||||
renderer = self.id().0.as_str(),
|
||||
tracks_count = snapshot.tracks.len(),
|
||||
current_id = ?snapshot.current_id,
|
||||
current_index = ?snapshot.current_index,
|
||||
"play_current_from_backend_queue: OpenHome snapshot fetched"
|
||||
);
|
||||
|
||||
if snapshot.tracks.is_empty() {
|
||||
return Err(anyhow!("OpenHome playlist is empty"));
|
||||
}
|
||||
|
||||
// Find the track_id to play (prefer current_id, then current_index, then first)
|
||||
let target_track_id = if let Some(current_id) = snapshot.current_id {
|
||||
debug!(
|
||||
renderer = self.id().0.as_str(),
|
||||
track_id = current_id,
|
||||
"Using current_id for playback"
|
||||
);
|
||||
Some(current_id)
|
||||
} else if let Some(current_idx) = snapshot.current_index {
|
||||
snapshot.tracks.get(current_idx).map(|track| track.id)
|
||||
let track_id = snapshot.tracks.get(current_idx).map(|track| track.id);
|
||||
debug!(
|
||||
renderer = self.id().0.as_str(),
|
||||
current_idx,
|
||||
track_id = ?track_id,
|
||||
"Using current_index for playback"
|
||||
);
|
||||
track_id
|
||||
} else {
|
||||
snapshot.tracks.first().map(|track| track.id)
|
||||
let track_id = snapshot.tracks.first().map(|track| track.id);
|
||||
debug!(
|
||||
renderer = self.id().0.as_str(),
|
||||
track_id = ?track_id,
|
||||
"Using first track for playback"
|
||||
);
|
||||
track_id
|
||||
};
|
||||
|
||||
if let Some(track_id) = target_track_id {
|
||||
info!(
|
||||
renderer = self.id().0.as_str(),
|
||||
track_id,
|
||||
"Calling openhome_playlist_play_id to start playback"
|
||||
);
|
||||
self.openhome_playlist_play_id(track_id)?;
|
||||
info!(
|
||||
renderer = self.id().0.as_str(),
|
||||
track_id,
|
||||
"Successfully called openhome_playlist_play_id"
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("No track to play in OpenHome playlist"))
|
||||
|
||||
@@ -135,6 +135,14 @@ impl RadioParadiseSource {
|
||||
let token = mgr.register_callback(move |event| {
|
||||
let pid = pid_clone.clone();
|
||||
if event.playlist_id == pid {
|
||||
// Ignorer PkUpdated - pas de notification UPnP (évite le reload côté control point)
|
||||
if matches!(event.kind, pmoplaylist::PlaylistEventKind::PkUpdated { .. }) {
|
||||
tracing::debug!(
|
||||
"PK swap in playlist {} - no UPnP notification (prevents playback restart)",
|
||||
event.playlist_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
// On ne réagit qu'aux mises à jour structurelles (ajout/suppression)
|
||||
if !matches!(event.kind, pmoplaylist::PlaylistEventKind::Updated) {
|
||||
return;
|
||||
|
||||
@@ -527,7 +527,8 @@ impl WriteHandle {
|
||||
manager
|
||||
.rebuild_track_index(&self.playlist.id, &snapshot)
|
||||
.await;
|
||||
manager.notify_playlist_changed(&self.playlist.id);
|
||||
// PK swap uniquement - pas de notification UPnP pour éviter le reload
|
||||
manager.notify_playlist_pk_updated(&self.playlist.id, old_pk, new_pk);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -48,6 +48,9 @@ pub struct PlaylistEvent {
|
||||
pub enum PlaylistEventKind {
|
||||
/// La playlist a été modifiée (ajout/suppression/changement de config).
|
||||
Updated,
|
||||
/// Cache PK commuté (lazy→real) - pas de changement structurel.
|
||||
/// N'émet PAS de ContainersUpdated UPnP pour éviter le reload.
|
||||
PkUpdated { old_pk: String, new_pk: String },
|
||||
/// Un morceau référencé par la playlist a été servi par le cache audio.
|
||||
TrackPlayed { cache_pk: String, qualifier: String },
|
||||
}
|
||||
@@ -270,6 +273,18 @@ impl PlaylistManager {
|
||||
self.notify_playlist_event(id, PlaylistEventKind::Updated);
|
||||
}
|
||||
|
||||
/// Notifie que des PK ont été swappés (lazy→real).
|
||||
/// N'émet PAS de notification UPnP ContainersUpdated pour éviter le reload.
|
||||
pub(crate) fn notify_playlist_pk_updated(&self, id: &str, old_pk: &str, new_pk: &str) {
|
||||
self.notify_playlist_event(
|
||||
id,
|
||||
PlaylistEventKind::PkUpdated {
|
||||
old_pk: old_pk.to_string(),
|
||||
new_pk: new_pk.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Notifie les callbacks qu'un morceau a été joué pour une playlist donnée.
|
||||
pub(crate) fn notify_playlist_track_played(
|
||||
&self,
|
||||
|
||||
@@ -55,6 +55,10 @@ pub async fn playlist_events_sse(Query(params): Query<EventsQuery>) -> impl Into
|
||||
|
||||
let (kind, cache_pk, qualifier) = match &envelope.event.kind {
|
||||
PlaylistEventKind::Updated => ("updated", None, None),
|
||||
PlaylistEventKind::PkUpdated { old_pk: _, new_pk: _ } => {
|
||||
// PK swap silencieux - envoyer quand même l'événement SSE pour debug/monitoring
|
||||
("pk_updated", None, None)
|
||||
}
|
||||
PlaylistEventKind::TrackPlayed { cache_pk, qualifier } => {
|
||||
("track_played", Some(cache_pk.as_str()), Some(qualifier.as_str()))
|
||||
}
|
||||
|
||||
@@ -479,6 +479,13 @@ impl QobuzApi {
|
||||
// Fonctions de parsing publiques (utilisées aussi par le module user)
|
||||
|
||||
pub(crate) fn parse_album(response: AlbumResponse) -> Album {
|
||||
// Log pour débugger les valeurs audio
|
||||
if let Some(rate) = response.maximum_sampling_rate {
|
||||
debug!("Album {} - maximum_sampling_rate: {} Hz", response.id, rate);
|
||||
} else {
|
||||
debug!("Album {} - maximum_sampling_rate: None", response.id);
|
||||
}
|
||||
|
||||
Album {
|
||||
id: response.id,
|
||||
title: response.title,
|
||||
|
||||
@@ -269,7 +269,13 @@ impl Album {
|
||||
/// Retourne un titre formaté avec les informations audio si disponibles
|
||||
pub fn formatted_title(&self) -> String {
|
||||
if let (Some(rate), Some(depth)) = (self.maximum_sampling_rate, self.maximum_bit_depth) {
|
||||
format!("{} ({:.0}/{} bit)", self.title, rate / 1000.0, depth)
|
||||
// Convertir Hz en kHz, en gérant les valeurs qui pourraient déjà être en kHz
|
||||
let rate_khz = if rate > 1000.0 {
|
||||
rate / 1000.0
|
||||
} else {
|
||||
rate
|
||||
};
|
||||
format!("{} ({:.1} kHz / {} bits)", self.title, rate_khz, depth)
|
||||
} else {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user