From 36a05a2575d30c9ff641e2ace0536cf55d4c3e27 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 11 Jan 2026 07:53:46 +0100 Subject: [PATCH 1/4] Prevent auto-advance on user-requested stop Mark user-requested stops to prevent auto-advance when sleep timer expires. This ensures that playback stops cleanly without triggering automatic advancement to the next item. --- pmocontrol/src/control_point.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 6f6ddb43..768f7cc1 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -542,6 +542,9 @@ impl ControlPoint { "Sleep timer expired, stopping playback" ); + // Mark this as a user-requested stop to prevent auto-advance + renderer.mark_user_stop_requested(); + // Stop playback if let Err(err) = renderer.stop() { warn!( -- 2.49.1 From 5a30731854baa74ecb92520e4bb2d54d720cdfd0 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 11 Jan 2026 08:04:24 +0100 Subject: [PATCH 2/4] Validate playback position against duration in control point This commit adds validation to ensure that the playback position does not exceed the track duration. When the position is greater than the duration (which can happen during track initialization on some UPNP renderers), the position is set to None to avoid displaying bogus timestamps. This improves the robustness of playback position handling. --- pmocontrol/src/control_point.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 768f7cc1..4b2d89e7 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -1519,10 +1519,22 @@ impl ControlPoint { #[cfg(feature = "pmoserver")] fn convert_runtime_position(position: Option<&PlaybackPositionInfo>) -> (Option, Option) { match position { - Some(info) => ( - parse_hms_to_ms(info.rel_time.as_deref()), - parse_hms_to_ms(info.track_duration.as_deref()), - ), + Some(info) => { + let position_ms = parse_hms_to_ms(info.rel_time.as_deref()); + let duration_ms = parse_hms_to_ms(info.track_duration.as_deref()); + + // Validate that position doesn't exceed duration + // If position > duration, the renderer is reporting invalid data + // (common during track initialization on some UPNP renderers) + match (position_ms, duration_ms) { + (Some(pos), Some(dur)) if pos > dur => { + // Position exceeds duration - invalid state during initialization + // Return None for position to avoid showing bogus timestamps + (None, duration_ms) + } + _ => (position_ms, duration_ms), + } + } None => (None, None), } } -- 2.49.1 From 8fb135541deb5c9e0c49a36f8f78694f56d60ae1 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 11 Jan 2026 08:13:16 +0100 Subject: [PATCH 3/4] Optimize seek queue command handling Refactor the seek queue command to run asynchronously in the background, returning immediately to the client. The UI will now be updated via SSE events when the state changes, improving responsiveness. The previous timeout and error handling logic has been replaced with a more robust async approach that logs different error scenarios appropriately. --- pmocontrol/src/pmoserver_ext.rs | 70 ++++++++++++++------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/pmocontrol/src/pmoserver_ext.rs b/pmocontrol/src/pmoserver_ext.rs index 89ac43e6..c12a2f91 100644 --- a/pmocontrol/src/pmoserver_ext.rs +++ b/pmocontrol/src/pmoserver_ext.rs @@ -672,47 +672,37 @@ async fn seek_queue_index( let control_point = Arc::clone(&state.control_point); let rid_for_task = rid.clone(); let index = payload.index; - let seek_task = - tokio::task::spawn_blocking(move || control_point.play_queue_index(&rid_for_task, index)); - time::timeout(TRANSPORT_COMMAND_TIMEOUT, seek_task) - .await - .map_err(|_| { - warn!( - "Seek queue command for renderer {} exceeded {:?}", - renderer_id, TRANSPORT_COMMAND_TIMEOUT - ); - ( - StatusCode::GATEWAY_TIMEOUT, - Json(ErrorResponse { - error: format!( - "Seek command timed out after {}s", - TRANSPORT_COMMAND_TIMEOUT.as_secs() - ), - }), - ) - })? - .map_err(|e| { - warn!("Task join error during queue seek: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Internal task error: {}", e), - }), - ) - })? - .map_err(|e| { - warn!( - "Failed to seek to index {} for renderer {}: {}", - index, renderer_id, e - ); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: format!("Failed to seek to index {}: {}", index, e), - }), - ) - })?; + // Launch the command in background and return immediately + // The UI will be updated via SSE events when the state changes + tokio::task::spawn(async move { + let rid_for_log = rid_for_task.clone(); + let result = tokio::task::spawn_blocking(move || { + control_point.play_queue_index(&rid_for_task, index) + }) + .await; + + match result { + Ok(Ok(())) => { + debug!( + "Successfully started playback at index {} for renderer {}", + index, rid_for_log.0 + ); + } + Ok(Err(e)) => { + warn!( + "Failed to seek to index {} for renderer {}: {}", + index, rid_for_log.0, e + ); + } + Err(e) => { + warn!( + "Task join error during queue seek for renderer {}: {}", + rid_for_log.0, e + ); + } + } + }); Ok(Json(SuccessResponse { message: format!("Playing item at index {}", index), -- 2.49.1 From f074f4d657ff26bb30305417e16bd0738aecb5fa Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 11 Jan 2026 08:15:56 +0100 Subject: [PATCH 4/4] Bump version to 0.3.0 Update version numbers in Cargo.lock, Cargo.toml, and version.txt to 0.3.0 --- Cargo.lock | 2 +- pmocontrol/Cargo.toml | 2 +- version.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7b98fb8..363d1caf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "pmocontrol" -version = "0.1.0" +version = "0.3.0" dependencies = [ "anyhow", "async-std", diff --git a/pmocontrol/Cargo.toml b/pmocontrol/Cargo.toml index a5df9755..b9bc2543 100644 --- a/pmocontrol/Cargo.toml +++ b/pmocontrol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pmocontrol" -version = "0.1.0" +version = "0.3.0" edition = "2024" [dependencies] diff --git a/version.txt b/version.txt index eb069cd4..0d91a54c 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.20 \ No newline at end of file +0.3.0 -- 2.49.1