Merge pull request 'push-zplyprxpvxxm' (#51) from push-zplyprxpvxxm into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 28m7s

Reviewed-on: #51
This commit was merged in pull request #51.
This commit is contained in:
2026-01-11 08:16:46 +01:00
5 changed files with 52 additions and 47 deletions

2
Cargo.lock generated
View File

@@ -3713,7 +3713,7 @@ dependencies = [
[[package]]
name = "pmocontrol"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"anyhow",
"async-std",

View File

@@ -1,6 +1,6 @@
[package]
name = "pmocontrol"
version = "0.1.0"
version = "0.3.0"
edition = "2024"
[dependencies]

View File

@@ -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<u64>, Option<u64>) {
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),
}
}

View File

@@ -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),

View File

@@ -1 +1 @@
0.20
0.3.0