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/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 6f6ddb43..4b2d89e7 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!( @@ -1516,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), } } 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), 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