fix(qconnect): honor peer-controller seeks when qbz is the active renderer
Fixes #387: seeks issued from the official Qobuz mobile app while qbz
acts as the active renderer were silently dropped — audio kept playing
from the original position while the controller's progress bar locked.
Two independent gates were swallowing the command between the
WebSocket and the audio thread; either alone is enough to break the
flow, so both move together.
1. Upstream filter (crates/qconnect-app/src/app.rs)
The is_echo guard treated every SET_STATE with playing_state=None
AND current_track=None as a pure server echo and skipped emitting
RendererCommandApplied. iOS sends a real seek as exactly that
shape (only current_position_ms set, the rest unchanged), so the
command never reached the bridge handler.
Add current_position_ms.is_none() to the guard. Pure echoes still
match (server echoes carry only next_track); position-only seeks
pass through.
2. Downstream gate (src-tauri/src/qconnect/corebridge.rs)
The seek branch was conditioned on peer_renderer_active — i.e. it
only acted when qbz was the controller observing a peer renderer,
never when qbz was the renderer itself. Removed; the existing
is_echo_reset and abs_diff > 2 checks already filter the redundant-
seek case the gate was originally added to defend against (147bcbd7).
Drop the now-unused is_peer_renderer_active import.
Verified manually: seeks from iPhone (renderer_id=3) targeting qbz
(renderer_id=2) at ~1/4 and ~5/6 of a 150s track now translate to
local audio jumps to the correct positions.
cc
This commit is contained in:
2 files changed
+30
-14
No files matched your search
@@ -15,7 +15,6 @@ use crate::core_bridge::CoreBridge;
|
||||
use super::queue_resolution::{
|
||||
dedupe_track_ids, resolve_core_shuffle_order, resolve_remote_start_index,
|
||||
};
|
||||
use super::session::is_peer_renderer_active;
|
||||
use super::track_loading::ensure_remote_track_loaded;
|
||||
use super::{
|
||||
model_track_to_core_queue_track, normalize_volume_to_fraction,
|
||||
@@ -84,10 +83,6 @@ pub(super) async fn apply_renderer_command_to_corebridge(
|
||||
projection_renderer_state.next_track = next_track.clone();
|
||||
}
|
||||
let resolved_current_track = projection_renderer_state.current_track.as_ref();
|
||||
let peer_renderer_active = {
|
||||
let state = sync_state.lock().await;
|
||||
is_peer_renderer_active(&state.session)
|
||||
};
|
||||
if let Some(projected_track) = resolved_current_track {
|
||||
let queue_state = {
|
||||
let state = sync_state.lock().await;
|
||||
@@ -197,10 +192,19 @@ pub(super) async fn apply_renderer_command_to_corebridge(
|
||||
.unwrap_or(false)
|
||||
&& target_secs <= 1
|
||||
&& current_pos_secs > 2;
|
||||
if peer_renderer_active
|
||||
&& !is_echo_reset
|
||||
&& current_pos_secs.abs_diff(target_secs) > 2
|
||||
{
|
||||
// Issue #387: honor seeks regardless of which device is the
|
||||
// active renderer. The previous gate (`peer_renderer_active`)
|
||||
// skipped seeks entirely when local was the active renderer,
|
||||
// breaking the case where a peer controller (e.g. official
|
||||
// Qobuz mobile app) sends a real seek to qbz acting as the
|
||||
// renderer — the audio thread never moved while the cloud
|
||||
// state advanced, so the controller's progress bar locked.
|
||||
// The is_echo_reset + abs_diff > 2 gates already filter the
|
||||
// cloud-echo case the peer_renderer_active check was added
|
||||
// to defend against in commit 147bcbd7. If hiccups return,
|
||||
// revert this change and reintroduce a more targeted echo
|
||||
// detector (UUID-based) instead of the all-or-nothing gate.
|
||||
if !is_echo_reset && current_pos_secs.abs_diff(target_secs) > 2 {
|
||||
log::info!(
|
||||
"[QConnect] SetState seek: current={}s target={}s",
|
||||
current_pos_secs,
|
||||
|
||||
Reference in new issue
Block a user