fix(slint): reconcile now-playing card to the live track on seamless change

The poll loop's seamless-transition branch guessed whether a track-id change
while still playing was a gapless hand-off via a peek_upcoming(1) == track_id
heuristic. When that heuristic missed (queue pointer lagging the engine), the
meta was never refreshed: the seek bar kept advancing while the title/art/album
stayed on the previous track (the reported now-playing populate bug).

Replace the heuristic with reconciliation to what is ACTUALLY playing. New
frontend-agnostic primitive QueueManager::sync_current_to_id(id) moves the queue
pointer to the track the engine advanced to (recording history + keeping the
shuffle cursor aligned) and reports whether it moved; QbzCore wraps it and emits
a queue update only on a real move. The poll loop now syncs to the live track_id
and always refreshes the card, doing the record/sidebar/prefetch bookkeeping only
on a genuine advance (a manual play already moved the pointer, so it is idempotent).

cc
This commit is contained in:
vicrodh committed 2026-06-01 13:29:21 -06:00
1 parent 63d9f6b496
commit c763d5b2fe
3 files changed
+73 -24

No files matched your search

+32
View File
@@ -659,6 +659,38 @@ impl QueueManager {
prev_idx.and_then(|idx| state.tracks.get(idx).cloned())
}
/// Move the current pointer to the track whose id matches `id`, WITHOUT
/// starting playback. Used to reconcile the queue pointer to a track the
/// audio engine already advanced to on its own (a gapless hand-off
/// happens inside the player, not through `next`), so the now-playing
/// card never goes stale while the seek bar keeps moving.
///
/// Returns the matched track plus whether the pointer actually moved
/// (`false` = it was already current). Returns `None` when no queue track
/// has that id, leaving the pointer untouched.
pub fn sync_current_to_id(&self, id: u64) -> Option<(QueueTrack, bool)> {
let mut state = self.state.lock().unwrap();
let target = state.tracks.iter().position(|t| t.id == id)?;
let moved = state.current_index != Some(target);
if moved {
// Record the outgoing track so `previous` still walks back.
if let Some(curr_idx) = state.current_index {
state.history.push_back(curr_idx);
while state.history.len() > 50 {
state.history.pop_front();
}
}
state.current_index = Some(target);
// Keep the shuffle cursor aligned with the new position.
if state.shuffle {
if let Some(pos) = state.shuffle_order.iter().position(|&x| x == target) {
state.shuffle_position = pos;
}
}
}
state.tracks.get(target).cloned().map(|t| (t, moved))
}
/// Jump to a track by its position in the `upcoming` list as returned by
/// `get_state`. This is the position the user sees in the Queue sidebar;
/// the method resolves it to the correct canonical index even when