From 790eba87928e1894de6ccf3a2ac65b1884bf1571 Mon Sep 17 00:00:00 2001 From: joren Date: Tue, 31 Mar 2026 20:57:03 +0200 Subject: [PATCH] Fix end-of-track handoff by sending NEXT action --- src/qconnect.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/qconnect.rs b/src/qconnect.rs index f346fde..9c2c6b3 100644 --- a/src/qconnect.rs +++ b/src/qconnect.rs @@ -367,6 +367,18 @@ fn quality_to_format_id(quality: u32) -> u32 { } } +/// RNDR_SRVR_RENDERER_ACTION (24): renderer reports a local user action. +/// ActionType: 0=UNKNOWN, 1=PREVIOUS, 2=NEXT, 3=REPEAT_OFF, 4=REPEAT_ONE, 5=REPEAT_ALL, +/// 6=SHUFFLE_OFF, 7=SHUFFLE_ON, 8=SEEK +fn msg_renderer_action(action: u64, seek_position: Option) -> Vec { + let mut payload = Vec::new(); + if let Some(pos) = seek_position { + payload.extend(encode_field_varint(1, pos as u64)); // field 1: seek_position + } + payload.extend(encode_field_varint(2, action)); // field 2: action + build_qconnect_message(24, &payload) +} + /// RNDR_SRVR_VOLUME_CHANGED (25): renderer reports volume. fn msg_volume_changed(volume: u64) -> Vec { let payload = encode_field_varint(1, volume); @@ -882,14 +894,19 @@ async fn run_connection( && has_seen_position_progress && elapsed_since_play > std::time::Duration::from_secs(3) { - // Track ended naturally — keep reporting PLAYING with position=duration - // so the server detects end-of-track and sends next track via SET_STATE if !track_ended { - info!("[TICK] Track ended naturally, reporting position=duration ({}ms)", current_duration_ms); + // Track ended naturally — send final position then request next track + info!("[TICK] Track ended naturally, sending ACTION_TYPE_NEXT"); track_ended = true; current_position_ms = current_duration_ms; + send_state!(ws_tx, msg_id); + + // Tell server to advance to next track (ACTION_TYPE_NEXT = 2) + let action_msg = msg_renderer_action(2, None); + ws_tx.send(Message::Binary(build_payload_frame(msg_id, &action_msg).into())).await?; + msg_id += 1; } - send_state!(ws_tx, msg_id); + // Don't spam — wait for server to send new SET_STATE } else if status.state == PlayerState::Stopped { debug!("[TICK] Player stopped but grace period (elapsed={:?}, progress={}), ignoring", elapsed_since_play, has_seen_position_progress);