fix(ui): highlight/animate the playing row for offline-cache tracks

Local track rows bind the local-library DB row id, but local_queue_track
publishes the Qobuz catalog id for offline copies (so the shared
resolver finds them), so NowPlayingState.track-id never matched the
row: no equalizer bars, no active highlight, and double-click restarted
the track instead of toggling (album view, Tracks tab, folder detail,
kiosk).

Carry the library row id in source_item_id_hint for qobuz_download and
re-publish it as NowPlayingState.local-track-id; TrackPlayCell,
TrackRow and KioskAlbum now match on either id — one alias, no
per-view hacks.

km
This commit is contained in:
vicrodh committed 2026-07-19 16:08:02 -06:00
1 parent 1adf4e6f9d
commit c6da484481
5 files changed
+30 -3

No files matched your search

@@ -80,8 +80,11 @@ export component TrackPlayCell inherits Rectangle {
// Fired when a non-current track should start playing.
callback play();
// Offline-cache tracks publish the Qobuz catalog id in track-id but the
// local-library row id in local-track-id (playback.rs) — match either.
property <bool> is-active:
NowPlayingState.has-track && NowPlayingState.track-id == root.track-id;
NowPlayingState.has-track && (NowPlayingState.track-id == root.track-id
|| (NowPlayingState.local-track-id != "" && NowPlayingState.local-track-id == root.track-id));
property <bool> is-playing: root.is-active && NowPlayingState.playing;
// This row is the track being fetched/resolved (no audio yet). The global
// loading flag + active-row match means only the one loading row spins,
+2 -1
View File
@@ -141,7 +141,8 @@ export component TrackRow inherits Rectangle {
if (root.multi-select-mode) {
return;
}
if (NowPlayingState.has-track && NowPlayingState.track-id == root.track.id) {
// Same local-track-id alias as TrackPlayCell (offline-cache tracks).
if (NowPlayingState.has-track && (NowPlayingState.track-id == root.track.id || (NowPlayingState.local-track-id != "" && NowPlayingState.local-track-id == root.track.id))) {
NowPlayingState.toggle-play();
} else {
root.media-action("track", root.track.id, "play");
+2 -1
View File
@@ -120,7 +120,8 @@ export component KioskAlbum inherits Rectangle {
for track[i] in AlbumState.tracks: trow-rect := Rectangle {
height: 52px;
border-radius: Radius.sm;
property <bool> current: NowPlayingState.track-id == track.id;
// Same local-track-id alias as TrackPlayCell (offline-cache tracks).
property <bool> current: NowPlayingState.track-id == track.id || (NowPlayingState.local-track-id != "" && NowPlayingState.local-track-id == track.id);
background: trow.has-hover
? Theme.alpha-6
: self.current ? Theme.accent.with-alpha(0.10) : transparent;
+5
View File
@@ -4188,6 +4188,11 @@ export global WindowControlActions {
export global NowPlayingState {
in property <bool> has-track: false;
in property <string> title;
// Secondary alias for the playing track: the LOCAL-LIBRARY ROW ID when the
// track is an offline-cache copy (queue track-id carries the Qobuz catalog
// id there; set by refresh_now_playing_meta in playback.rs). Empty for every
// other source. TrackPlayCell / TrackRow / KioskAlbum match on either id.
in property <string> local-track-id;
in property <string> artist;
in property <string> album;
in property <string> album-id;
+17
View File
@@ -1376,6 +1376,13 @@ pub(crate) fn local_queue_track(track: &qbz_library::LocalTrack) -> QueueTrack {
// the session queue store so playback survives a restart.
source_item_id_hint: if is_plex {
Some(track.file_path.clone())
} else if is_offline {
// Offline copies: carry the local-library row id. The queue `id`
// above is the Qobuz catalog id (so the shared resolver finds the
// track), but every local surface's track row binds the DB row id —
// NowPlayingState re-publishes it as local-track-id so active-row
// comparisons (TrackPlayCell/TrackRow/KioskAlbum) match either.
Some(track.id.to_string())
} else {
None
},
@@ -1748,6 +1755,15 @@ pub(crate) async fn refresh_now_playing_meta(runtime: &Runtime, weak: &slint::We
.source
.clone()
.unwrap_or_else(|| if track.is_local { "local" } else { "qobuz" }.to_string());
// Active-row alias for offline-cache tracks: the local-library row id (the
// queue id is the Qobuz catalog id there, so the row's track-id binding
// never matches). Only trusted for qobuz_download — other builders reuse
// the hint for unrelated things (album id, plex rating key).
let local_track_id = if source == "qobuz_download" {
track.source_item_id_hint.clone().unwrap_or_default()
} else {
String::new()
};
let duration = track.duration_secs;
// Plex-aware: a Plex track carries a raw `/library/...` thumb path that
// must resolve to a tokenized `PlexThumb` (from current creds) so the
@@ -1953,6 +1969,7 @@ pub(crate) async fn refresh_now_playing_meta(runtime: &Runtime, weak: &slint::We
np.set_context_kind(context_kind.into());
np.set_context_id(context_id.into());
np.set_track_id(track_id.into());
np.set_local_track_id(local_track_id.into());
// Mirror the active track + playing flag onto the Purchases globals so a
// purchase track-row highlights/animates when it is the now-playing one.
mirror_now_playing_to_purchases(&w, track_id_num, true);