diff --git a/Cargo.lock b/Cargo.lock index d53b5e26..6d254f59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.30" +version = "0.3.32" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index 01369797..9511d43e 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.30" +version = "0.3.32" edition = "2024" [dependencies] diff --git a/pmocontrol/src/music_renderer/chromecast_renderer.rs b/pmocontrol/src/music_renderer/chromecast_renderer.rs index 685024c8..e00cddf2 100644 --- a/pmocontrol/src/music_renderer/chromecast_renderer.rs +++ b/pmocontrol/src/music_renderer/chromecast_renderer.rs @@ -569,10 +569,21 @@ impl PlaybackStatus for ChromecastRenderer { ControlPointError::ChromecastError(format!("Failed to get receiver status: {}", e)) })?; + tracing::debug!( + "Chromecast playback_state: {} apps running", + status.applications.len() + ); + // If no app is running, return NoMedia let app = match status.applications.first() { - Some(app) => app, - None => return Ok(PlaybackState::NoMedia), + Some(app) => { + tracing::debug!("Chromecast playback_state: app={}", app.display_name); + app + } + None => { + tracing::debug!("Chromecast playback_state: no apps running, returning NoMedia"); + return Ok(PlaybackState::NoMedia); + } }; // Connect to the app @@ -591,10 +602,25 @@ impl PlaybackStatus for ChromecastRenderer { ControlPointError::ChromecastError(format!("Failed to get media status: {}", e)) })?; + tracing::debug!( + "Chromecast playback_state: {} media entries", + media_status.entries.len() + ); + // If no media entry, return NoMedia let media_entry = match media_status.entries.first() { - Some(entry) => entry, - None => return Ok(PlaybackState::NoMedia), + Some(entry) => { + tracing::debug!( + "Chromecast playback_state: player_state={:?}, current_time={:?}", + entry.player_state, + entry.current_time + ); + entry + } + None => { + tracing::debug!("Chromecast playback_state: no media entries, returning NoMedia"); + return Ok(PlaybackState::NoMedia); + } }; Ok(map_player_state(&media_entry.player_state)) diff --git a/pmocontrol/src/music_renderer/musicrenderer.rs b/pmocontrol/src/music_renderer/musicrenderer.rs index fdaeea79..515e4390 100644 --- a/pmocontrol/src/music_renderer/musicrenderer.rs +++ b/pmocontrol/src/music_renderer/musicrenderer.rs @@ -18,7 +18,6 @@ use crate::errors::ControlPointError; use crate::events::RendererEventBus; use crate::model::RendererEvent; use crate::model::{PlaybackSource, PlaybackState, RendererInfo, RendererProtocol, TrackMetadata}; -use crate::music_renderer::RendererFromMediaRendererInfo; use crate::music_renderer::arylic_tcp::ArylicTcpRenderer; use crate::music_renderer::capabilities::{ PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, @@ -30,13 +29,12 @@ use crate::music_renderer::openhome_renderer::OpenHomeRenderer; use crate::music_renderer::sleep_timer::SleepTimer; use crate::music_renderer::upnp_renderer::UpnpRenderer; use crate::music_renderer::watcher::{ - WatchStrategy, WatchedState, extract_track_metadata, playback_position_equal, - playback_state_equal, + extract_track_metadata, playback_position_equal, playback_state_equal, WatchStrategy, + WatchedState, }; +use crate::music_renderer::RendererFromMediaRendererInfo; use crate::online::DeviceConnectionState; -use crate::queue::{ - EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot, -}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::{DeviceId, DeviceIdentity, DeviceOnline}; use tracing::warn; @@ -639,10 +637,11 @@ impl MusicRenderer { PlaybackState::Stopped => { { let s = self.state.lock().unwrap(); - tracing::trace!( + tracing::debug!( renderer = self.info.friendly_name(), has_played = s.has_played_since_track_start, playback_source = ?s.playback_source, + user_stop_requested = s.user_stop_requested, "STOPPED detected — evaluating auto-advance" ); } @@ -705,6 +704,70 @@ impl MusicRenderer { self.clear_has_played_flag(); } } + PlaybackState::NoMedia => { + // Handle end of track (Chromecast returns NoMedia when track ends) + // This is equivalent to Stopped for auto-advance purposes + let s = self.state.lock().unwrap(); + let playback_source = s.playback_source; + let has_played = s.has_played_since_track_start; + let user_stop = s.user_stop_requested; + drop(s); + + tracing::debug!( + renderer = self.info.friendly_name(), + has_played = has_played, + playback_source = ?playback_source, + user_stop_requested = user_stop, + "NoMedia detected — evaluating auto-advance" + ); + + // Check if user requested stop (via Stop button in UI) + if self.check_and_clear_user_stop_requested() { + debug!( + renderer = self.info.friendly_name(), + "NoMedia after user request; not auto-advancing" + ); + self.set_playback_source(PlaybackSource::None); + self.clear_has_played_flag(); + } else if matches!(playback_source, PlaybackSource::FromQueue) { + // Auto-advance if we have seen a PLAYING state + if self.check_and_clear_has_played_flag() { + debug!( + renderer = self.info.friendly_name(), + "NoMedia after queue-driven playback; advancing to next track" + ); + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + self.play_next_from_queue() + })); + match result { + Ok(Ok(())) => {} + Ok(Err(err)) => { + error!( + renderer = self.info.friendly_name(), + error = %err, + "Auto-advance from NoMedia failed; clearing queue playback state" + ); + self.set_playback_source(PlaybackSource::None); + } + Err(_panic) => { + error!( + renderer = self.info.friendly_name(), + "Auto-advance from NoMedia panicked; clearing queue playback state" + ); + self.set_playback_source(PlaybackSource::None); + } + } + } else { + tracing::debug!( + renderer = self.info.friendly_name(), + "NoMedia but no PLAYING state seen yet; ignoring" + ); + } + } else { + self.set_playback_source(PlaybackSource::None); + self.clear_has_played_flag(); + } + } PlaybackState::Playing => { self.mark_external_if_idle(); // Mark that we have seen a PLAYING state - auto-advance is now allowed @@ -1605,8 +1668,8 @@ pub(crate) fn build_didl_lite_metadata( uri: &str, protocol_info: &str, ) -> String { - use pmodidl::{DIDLLite, Item, Resource}; use pmodidl::ToXmlElement; + use pmodidl::{DIDLLite, Item, Resource}; // Construire l'Item DIDL avec toutes les métadonnées let item = Item { @@ -1842,7 +1905,6 @@ fn parse_rfc3339_to_system_time(s: &str) -> Option { Some(std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs as u64)) } - /// Transport control façade that dispatches to whichever backend can fulfill /// the request, returning a standardized error if the backend lacks support. impl TransportControl for MusicRendererBackend { diff --git a/version.txt b/version.txt index cfa5015f..cd906cd5 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.30 +0.3.32