fix: omit queue_item_ids from renderer state reports to fix seekbar sync
The server rejects renderer state reports with "Current track not found in queue nor autoplay" after QBZ-initiated queue loads, because it assigns non-standard queue_item_ids (first track = track_id, rest = 1..N). Omit current_queue_item_id and next_queue_item_id from all outbound renderer reports. The server already knows the current track from SET_STATE commands and adds the correct queue_item_id when forwarding to controllers. Also add info-level diagnostic logging to both report paths (periodic frontend reports and command-response reports) and the protobuf encoder for verifying wire content.
This commit is contained in:
4 files changed
+48
-10
No files matched your search
@@ -15,6 +15,7 @@ serde = { workspace = true }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
log = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["test-util"] }
|
tokio = { workspace = true, features = ["test-util"] }
|
||||||
@@ -416,10 +416,25 @@ where
|
|||||||
) -> Result<(), QconnectAppError> {
|
) -> Result<(), QconnectAppError> {
|
||||||
match command {
|
match command {
|
||||||
RendererCommand::SetState { .. } => {
|
RendererCommand::SetState { .. } => {
|
||||||
|
log::info!(
|
||||||
|
"[QConnect/Report] SetState report: playing={:?} pos={:?} track={:?} next={:?} qv={}.{}",
|
||||||
|
renderer.playing_state,
|
||||||
|
renderer.current_position_ms,
|
||||||
|
renderer.current_track.as_ref().map(|t| (t.track_id, t.queue_item_id)),
|
||||||
|
renderer.next_track.as_ref().map(|t| (t.track_id, t.queue_item_id)),
|
||||||
|
queue_version_ref.major,
|
||||||
|
queue_version_ref.minor
|
||||||
|
);
|
||||||
let report = RendererReport::new(
|
let report = RendererReport::new(
|
||||||
RendererReportType::RndrSrvrStateUpdated,
|
RendererReportType::RndrSrvrStateUpdated,
|
||||||
self.next_action_uuid(),
|
self.next_action_uuid(),
|
||||||
queue_version_ref,
|
queue_version_ref,
|
||||||
|
// NOTE: We intentionally OMIT current_queue_item_id and next_queue_item_id.
|
||||||
|
// The server validates these IDs against its queue state and rejects reports
|
||||||
|
// when IDs don't match ("Current track not found in queue nor autoplay").
|
||||||
|
// This happens after QBZ-initiated queue loads where the server assigns
|
||||||
|
// non-standard queue_item_ids (first track = track_id, rest = 1..N).
|
||||||
|
// The server already knows the current track from SET_STATE commands.
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"playing_state": renderer.playing_state,
|
"playing_state": renderer.playing_state,
|
||||||
"buffer_state": infer_buffer_state(renderer.playing_state),
|
"buffer_state": infer_buffer_state(renderer.playing_state),
|
||||||
@@ -429,8 +444,8 @@ where
|
|||||||
"major": queue_version_ref.major,
|
"major": queue_version_ref.major,
|
||||||
"minor": queue_version_ref.minor
|
"minor": queue_version_ref.minor
|
||||||
},
|
},
|
||||||
"current_queue_item_id": renderer.current_track.as_ref().map(|item| item.queue_item_id),
|
"current_queue_item_id": Option::<i32>::None,
|
||||||
"next_queue_item_id": renderer.next_track.as_ref().map(|item| item.queue_item_id)
|
"next_queue_item_id": Option::<i32>::None
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
self.send_renderer_report(report).await?;
|
self.send_renderer_report(report).await?;
|
||||||
|
|||||||
@@ -424,25 +424,32 @@ fn map_renderer_report(report: &RendererReport) -> Result<QConnectMessage, Proto
|
|||||||
.unwrap_or(report.queue_version_ref);
|
.unwrap_or(report.queue_version_ref);
|
||||||
let current_position = optional_i32(&report.payload, "current_position")?;
|
let current_position = optional_i32(&report.payload, "current_position")?;
|
||||||
let duration = optional_i32(&report.payload, "duration")?;
|
let duration = optional_i32(&report.payload, "duration")?;
|
||||||
|
let current_qid = optional_i32(&report.payload, "current_queue_item_id")?;
|
||||||
|
let next_qid = optional_i32(&report.payload, "next_queue_item_id")?;
|
||||||
|
let playing_state = optional_i32(&report.payload, "playing_state")?;
|
||||||
let playback_position = current_position.map(|value| PlaybackPositionMessage {
|
let playback_position = current_position.map(|value| PlaybackPositionMessage {
|
||||||
timestamp: Some(now_ms()),
|
timestamp: Some(now_ms()),
|
||||||
value: Some(value),
|
value: Some(value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"[QConnect/Proto] Encoding StateUpdated: playing={:?} pos={:?} dur={:?} qid={:?} next_qid={:?} qv={}.{}",
|
||||||
|
playing_state, current_position, duration,
|
||||||
|
current_qid, next_qid,
|
||||||
|
queue_version.major, queue_version.minor
|
||||||
|
);
|
||||||
|
|
||||||
Ok(QConnectMessage {
|
Ok(QConnectMessage {
|
||||||
message_type: Some(QConnectMessageType::MessageTypeRndrSrvrStateUpdated as i32),
|
message_type: Some(QConnectMessageType::MessageTypeRndrSrvrStateUpdated as i32),
|
||||||
rndr_srvr_state_updated: Some(RendererStateUpdatedMessage {
|
rndr_srvr_state_updated: Some(RendererStateUpdatedMessage {
|
||||||
state: Some(RendererStateMessage {
|
state: Some(RendererStateMessage {
|
||||||
playing_state: optional_i32(&report.payload, "playing_state")?,
|
playing_state,
|
||||||
buffer_state: optional_i32(&report.payload, "buffer_state")?,
|
buffer_state: optional_i32(&report.payload, "buffer_state")?,
|
||||||
current_position: playback_position,
|
current_position: playback_position,
|
||||||
duration,
|
duration,
|
||||||
queue_version: Some(to_proto_queue_version(queue_version)?),
|
queue_version: Some(to_proto_queue_version(queue_version)?),
|
||||||
current_queue_item_id: optional_i32(
|
current_queue_item_id: current_qid,
|
||||||
&report.payload,
|
next_queue_item_id: next_qid,
|
||||||
"current_queue_item_id",
|
|
||||||
)?,
|
|
||||||
next_queue_item_id: optional_i32(&report.payload, "next_queue_item_id")?,
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -1723,6 +1723,21 @@ pub async fn v2_qconnect_report_playback_state(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let queue_version = service.get_queue_version().await;
|
let queue_version = service.get_queue_version().await;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"[QConnect/Report] Periodic state report: playing={} pos={:?} dur={:?} qid={:?} next_qid={:?} track_id={:?} qv={}.{}",
|
||||||
|
playing_state, current_position, duration,
|
||||||
|
resolved_current_qid, resolved_next_qid, current_track_id,
|
||||||
|
queue_version.major, queue_version.minor
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: We intentionally OMIT current_queue_item_id and next_queue_item_id
|
||||||
|
// from periodic state reports. The server validates these IDs against its
|
||||||
|
// queue state and rejects reports when IDs don't match (returns "Current track
|
||||||
|
// not found in queue nor autoplay"). This happens consistently after QBZ-initiated
|
||||||
|
// queue loads where the server assigns non-standard queue_item_ids.
|
||||||
|
// The server already knows the current track from SET_STATE commands and adds
|
||||||
|
// the correct queue_item_id when forwarding to controllers.
|
||||||
let report = RendererReport::new(
|
let report = RendererReport::new(
|
||||||
RendererReportType::RndrSrvrStateUpdated,
|
RendererReportType::RndrSrvrStateUpdated,
|
||||||
Uuid::new_v4().to_string(),
|
Uuid::new_v4().to_string(),
|
||||||
@@ -1732,8 +1747,8 @@ pub async fn v2_qconnect_report_playback_state(
|
|||||||
"buffer_state": BUFFER_STATE_OK,
|
"buffer_state": BUFFER_STATE_OK,
|
||||||
"current_position": current_position,
|
"current_position": current_position,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
"current_queue_item_id": resolved_current_qid,
|
"current_queue_item_id": null,
|
||||||
"next_queue_item_id": resolved_next_qid,
|
"next_queue_item_id": null,
|
||||||
"queue_version": {
|
"queue_version": {
|
||||||
"major": queue_version.major,
|
"major": queue_version.major,
|
||||||
"minor": queue_version.minor
|
"minor": queue_version.minor
|
||||||
|
|||||||
Reference in new issue
Block a user