fix(qconnect): badge clear, multi-track enqueue, shuffle WS-authoritative, cursor/history/pause

- badge: gate is-remote on transport_connected so it clears on every session-end
  path (transport drop / watchdog / reconnect-exhausted), not only toggle-off
- multi-track: route a whole album/playlist/favorites enqueue to the peer in one
  QueueAddTracks/QueueInsertTracks (all-or-nothing admission), not single-track only
- shuffle inbound: SetShuffleMode renderer arm is flag-only (set_shuffle_flag) so the
  renderer never invents a local order; the cloud's echoed order is authoritative
- shuffle outbound: send shuffle_seed + shuffle_pivot_queue_item_id (the cloud needs
  the seed to build the order); hoist resolve_qconnect_shuffle_pivot to qconnect-app
- start index: resolve_remote_start_index validates the track_id before trusting a
  qid, so a freshly-pushed album no longer inherits a stale cursor (wrong NowPlayingBar track)
- history: play_index records history only when actually moving, so the current track
  is no longer duplicated in the History tab
- pause-to-peer: resolve current_queue_item from the core-aligned cursor instead of the
  stale renderer snapshot, so pausing an iOS renderer (reports null qid) is accepted

cc
This commit is contained in:
vicrodh committed 2026-06-04 21:59:58 -06:00
1 parent 712447eb00
commit 1f1d7303ef
10 files changed
+499 -49

No files matched your search

+3 -30
View File
@@ -1,6 +1,7 @@
use tauri::State;
use qbz_models::{QueueState, QueueTrack as CoreQueueTrack, RepeatMode};
use qconnect_app::queue_resolution::resolve_qconnect_shuffle_pivot;
use qconnect_app::QueueCommandType;
use qconnect_app::{QConnectQueueState, QConnectRendererState};
@@ -273,43 +274,15 @@ fn qconnect_loop_mode_from_repeat_mode(mode: RepeatMode) -> i32 {
}
}
fn resolve_qconnect_shuffle_pivot(
queue: &QConnectQueueState,
renderer: &QConnectRendererState,
) -> Option<u64> {
let Some(current_track) = renderer.current_track.as_ref() else {
return None;
};
if queue
.queue_items
.iter()
.position(|item| item.queue_item_id == current_track.queue_item_id)
.is_some()
{
return Some(current_track.queue_item_id);
}
if let Some((_, item)) = queue
.queue_items
.iter()
.enumerate()
.find(|(_, item)| item.track_id == current_track.track_id)
{
return Some(item.queue_item_id);
}
None
}
#[cfg(test)]
mod tests {
use super::{
build_qconnect_remove_upcoming_payload, build_qconnect_reorder_payload,
qconnect_loop_mode_from_repeat_mode, resolve_qconnect_shuffle_pivot,
qconnect_loop_mode_from_repeat_mode,
};
use crate::qconnect::QconnectVisibleQueueProjection;
use qbz_models::RepeatMode;
use qconnect_app::queue_resolution::resolve_qconnect_shuffle_pivot;
use qconnect_app::{QConnectQueueState, QConnectRendererState};
use qconnect_core::QueueItem;
use serde_json::json;
+3
View File
@@ -310,6 +310,9 @@ impl QconnectRendererEngine for CoreBridge {
async fn set_shuffle(&self, enabled: bool) {
CoreBridge::set_shuffle(self, enabled).await
}
async fn set_shuffle_flag(&self, enabled: bool) {
CoreBridge::set_shuffle_with_order(self, enabled, None).await
}
async fn get_all_queue_tracks(&self) -> (Vec<QueueTrack>, Option<usize>) {
CoreBridge::get_all_queue_tracks(self).await
}