Commit Graph
1526 Commits
Author SHA1 Message Date
vicrodh abb529e22b feat(discover): Most Played Albums — play-count store, hook, section id
Data foundation for the new Most Played Albums rail. New qbz-app store album_play_history (SQLite, mirrors play_history): one album_play_event row per track-start + an album_meta side table; top_albums(n)/all_albums() rank by COUNT(*) GROUP BY album_id, tie-broken by most-recent play. Hooked in playback::record_recent next to the recently/artist stores, same album identity. Registers the mostPlayedAlbums section id in discover_prefs (Home + For You, default off). The Qobuz API exposes no most-played endpoint (verified vs the inferred OpenAPI), so the ranking is local. 15 unit tests green (store 5 + prefs 10).

cc
2026-07-13 23:30:28 -06:00
vicrodh bf29892aa4 fix(dsd): DsdErrorReport holds SharedState, not Arc<SharedState>
The merged #599 typed DsdErrorReport.state as Arc<SharedState>, but the audio thread passes thread_state — a SharedState, which is itself a #[derive(Clone)] handle of Arc<Atomic..> fields (a clone already shares the state). The extra Arc broke the qbz-player build in the combined pre-release (E0308 at the four DoP / native-DSD wrap sites), failing the test-crates CI. Switches the field + new() to SharedState and drops the now-redundant Arc::new in the two DSD tests. Verified with scripts/cargo-test.sh (workspace, no UI): all crates green.

cc
2026-07-13 21:15:22 -06:00
vicrodh 8c50c16faa fix(offline): clarify that Clear cache keeps downloaded albums
After #606 the offline Clear-cache purges only the transient cache dirs (tracks/tracks-cmaf/artwork), never the organized downloaded albums — those are removed per-item from the offline manager. The old copy ('Remove every downloaded track. This cannot be undone.') was misleading. Relabels the row 'Clear cache', rewrites the description to say downloads are kept and point to the manager, and softens the toasts to 'Cache cleared' / 'Couldn't clear the cache'. 8 locales.

cc
2026-07-13 21:01:48 -06:00
vicrodh ba7be747a7 fix(immersive): persist volume on drag-end
The immersive VolumeBar only called set-volume (live) and never persist-volume, so a volume set in the immersive view was lost on restart/track-change. Adds a released(float) callback emitted on pointer-up, wired to NowPlayingState.persist-volume — parity with the NPB sliders. Pure .slint; persist-volume already exists in Rust.

cc
2026-07-13 21:01:48 -06:00
vicrodh 41f8578b78 feat(home): sort control for the Library Albums rail
Adds a compact dropdown in the Library Albums (favoriteAlbums) row header, left of the page chevrons: Recently added (default), First added (reverse), Random (shuffle). Reorders the cached base list in Rust with no re-fetch and re-fires artwork by the new index. Also corrects the stale HomeView comment that claimed the recently-played refresh lives in the toolbar (it is a row-header button). 8 locales updated.

cc
2026-07-13 21:01:48 -06:00
vicrodh dfc74fb6d4 Merge branch 'pr/606' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:27:23 -06:00
vicrodh 12dcedbc0f Merge branch 'pr/611' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:14:09 -06:00
vicrodh 30c57c9f54 Merge branch 'pr/608' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:13:57 -06:00
vicrodh 97ab577778 Merge branch 'pr/598' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:13:57 -06:00
vicrodh 3f974752ae Merge branch 'pr/595' into integration/pr-review-smoke-2026-07-13
# Conflicts:
#	crates/qbz-player/src/player/mod.rs
2026-07-13 14:13:47 -06:00
vicrodh 83c47933cb Merge branch 'pr/594' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:12:49 -06:00
vicrodh 7b0b2ed7da Merge branch 'pr/599' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:12:39 -06:00
vicrodh c2e23dfa95 Merge branch 'pr/593' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:12:39 -06:00
vicrodh 116fe38476 Merge branch 'pr/607' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:08:28 -06:00
vicrodh d4522c3585 Merge branch 'pr/604' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:08:28 -06:00
vicrodh 62dc3f55aa Merge branch 'pr/601' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:08:28 -06:00
vicrodh a88a38c644 Merge branch 'pr/600' into integration/pr-review-smoke-2026-07-13 2026-07-13 14:08:28 -06:00
vicrodh b3d12e6b2c fix(playback): give the now-playing "source" button a per-track context
The "playing from" button read a single global context set once at play time;
refresh_now_playing_meta never republished it, so it went stale across track
changes (previous track's album) and single-track plays fell back to the
current track's album instead of the playlist it came from. Add per-track
context_kind/context_id to QueueTrack (serde-default None, back-compat), stamp
the real container at every play path (album/artist/playlist/label), and
republish the context from the current track on EVERY track change (gapless
included) — never cached. open-context routes all stamped kinds.

cc
2026-07-13 13:11:43 -06:00
vicrodh a174e06119 feat(home): auto-refresh recently-played rails + a row refresh button
Home's recently-played albums/tracks only populated on a full home load, so
tracks played mid-session never appeared until restart. Re-read the LOCAL
store into the rails when a track is recorded while Home is visible, and on
Home re-mount if it went dirty — event-driven, no polling, cheap (small JSON
read + cached artwork). Also add a manual refresh button in each recently-
played row header next to the page chevrons (moved off the top toolbar, where
it crowded the discovery tab bar).

cc
2026-07-13 13:11:43 -06:00
vicrodh 8df8c18ab0 fix(theme): sync std-widgets Palette to the active QBZ theme
Native std-widgets (LineEdit/TextEdit) render from Slint's own Palette, which
we never synced to the QBZ theme, so on a light theme their text collided with
its own background (Settings input fields, effectively invisible). Add
Theme.is-dark (computed in Rust from the surface luminance, so it is correct
for static/auto/custom themes) and drive Palette.color-scheme from it in the
AppWindow: light/dark follow the theme, System stays `unknown` (follows the
OS). Reactive on theme change.

cc
2026-07-13 13:11:12 -06:00
vicrodh a9dff18d25 fix(ui): make CircleAction buttons visible on light themes
CircleAction defaults to hardcoded light-on-dark colors, so every instance
sitting on the normal page background (not over artwork) vanished on light
themes. Set on-surface: true on those clusters — Popular Tracks (artist +
label), the PlaylistView hero row, LocalAlbumView, AwardView,
PurchaseDetailView, MixtapeDetailView, FavoritesView, LocalLibraryView — so
they use theme-appropriate colors. Buttons over artwork/dark headers keep the
light-on-dark defaults.

cc
2026-07-13 13:11:12 -06:00
vicrodh 80088b6a0b fix(renderer): keep the app alive when the wgpu surface goes Outdated (#558)
At a non-default UI scale, SLINT_SCALE_FACTOR changes the physical surface
size and the first frames race the winit resize/scale-factor event that
reconfigures the swapchain, so get_current_texture() returns Outdated. The
vendored femtovg-wgpu backend reconfigured and retried once, then propagated a
second Outdated via `?`, which killed the event loop and left the app unable
to start at any interface size other than default. Treat a persistent Outdated
like the #540 timeout: skip the frame and back off briefly; the next
resize/redraw reconfigures cleanly. wgpu now survives the scale race without
depending on the degradation ladder falling back to GL.

cc
2026-07-13 13:11:12 -06:00
Afonso Jorge Ramos ec69bce17e feat(ui): open album from grid card title click 2026-07-13 16:45:42 +02:00
Afonso Jorge Ramos b4450965df fix(player): surface stream feeder failures to BufferWriter
CMAF and remote download tasks now error the streaming buffer on failure
so readers do not hang waiting for complete/error forever.
2026-07-13 16:16:14 +02:00
Afonso Jorge Ramos c9b9aea118 fix(scrobble): skip tracks shorter than 30 seconds 2026-07-13 16:12:06 +02:00
Afonso Jorge Ramos 68317f5a34 fix(dsd): treat demux I/O errors as sticky failures, not clean EOF
Only Ok(0) ends the stream cleanly; mid-file read errors set io_error.
2026-07-13 16:11:02 +02:00
vicrodh fa161be525 fix(header): gradient height = header container (ends at divider), artist-page parity, action buttons readable with gradient off
The album header gradient was a fixed px that overshot the header/track-list divider on tall (description-bearing) headers; bind its height to the divider's y so it ends exactly at the header container's bottom at any size. Apply the same readable-text + gradient-to-boundary treatment to the artist page. And CircleAction gains an on-surface variant (theme-colored fill/border/icon) driven by the gradient toggle, so when the header gradient is off the action buttons are visible on light themes instead of white-on-white. Below-divider content untouched. Extends #491/#519.

cc
2026-07-13 07:50:46 -06:00
Afonso Jorge Ramos 21c4817fcf fix(player): bump play generation on stop, DSD, and streaming
Non-play_track entry points must supersede in-flight play_track so a
slow fetch cannot clobber stop or a later source.
2026-07-13 13:50:06 +02:00
Afonso Jorge Ramos 61df66c529 fix(offline): purge only allowlisted cache layout directories
Stop recursive-deleting arbitrary siblings of the offline root so a
mis-set cache path cannot wipe unrelated folders on a shared mount.
2026-07-13 13:49:55 +02:00
Afonso Jorge Ramos 542ee8050f fix(player): clear streaming loaded state on streaming setup failures
Buffer timeout, engine creation failure, and incremental source failure
no longer leave has_loaded_audio true with no engine; each records a
user-visible stream error.
2026-07-13 13:49:34 +02:00
Afonso Jorge Ramos e153673acf fix(cmaf): fail init parse when raw_data length exceeds payload
Do not advance the cursor by an unclamped raw_len after a truncated slice.
2026-07-13 13:48:40 +02:00
Afonso Jorge Ramos 39dccd8b01 fix(credentials): tighten permissions of existing secret files on load 2026-07-13 13:48:30 +02:00
Afonso Jorge Ramos c1bb5b3dc2 fix(audio): fail closed when ALSA exclusive rate is not exact
Read back the hardware rate after hw_params for PCM, DoP, and native DSD
and refuse silent nearest-rate mismatches.
2026-07-13 13:47:43 +02:00
Afonso Jorge Ramos e1c34005e0 fix(credentials): write secret files with mode 0o600 on Unix
Salt, machine-id fallback, encrypted credentials, and OAuth token files
must not inherit a permissive umask.
2026-07-13 13:47:18 +02:00
vicrodh d0a7ded531 fix(audio): superseded plays skip the 60s initial-buffer wait; non-streaming path falls back to legacy
Follow-ups to #591/#592. The streaming initial-buffer wait blocked the audio thread up to 60s even when the play had been superseded by a newer track, which fed the stop/restart-loop feel; play_generation moved onto SharedState and threaded through PlayStreaming so a superseded wait exits in ~50ms (the 60s timeout stays for genuine slow networks). And the non-streaming Play path gained the same legacy-CPAL fallback the streaming path got, instead of dead-ending on backend-init failure (macOS keeps surfacing the error). Control flow only; sample path untouched.

cc
2026-07-13 05:01:22 -06:00
vicrodh c3523722f4 feat(local-library): add a Plex sync button to refresh media without restarting
Fetching new Plex music required restarting QBZ. Add a sync button in the Local Library header (gated on plex-available, with a busy spinner) that triggers the existing Plex re-sync (new plex_auth::sync_now over the persisted selected libraries) and refreshes the view. Strings in all 8 locales. Resolves #573.

cc
2026-07-13 04:44:08 -06:00
vicrodh 99cf738a4c fix(scroll): boost Wayland trackpad scroll speed in the vendored Flickable
Trackpad PixelDelta events arrive divided by the HiDPI scale factor, making trackpad scrolling far slower than other apps on scaled Wayland desktops. In FlickableData::scroll_delta, distinguish line-based wheel deltas (always exact multiples of 60) from pixel-precise trackpad deltas and apply a 2.75x factor to the latter only; wheel and fling scrolling are unchanged. Resolves #588.

cc
2026-07-13 04:44:08 -06:00
vicrodh f8a256c62d fix(audio): don't require pactl for PipeWire, and fall back to legacy on streaming init failure
Two stacked defects broke playback under sandboxes (Snap/Flatpak/minimal .deb) in 2.0.1: PipeWireBackend::is_available() probed only `pactl info`, which those images don't ship, so PipeWire reported unavailable even though it was running (native pw-dump enumeration succeeded in the same log). And the streaming stream-init path had no legacy fallback (unlike init_device), so a failure dead-ended into a stuck spinner until the 45s watchdog. is_available() now checks the pipewire-0 socket + pw-dump (both time-bounded, fail-fast) with pactl kept as a last fallback; the StartStreaming path falls back to legacy CPAL on non-macOS. Sample path untouched (no gain/resample/rate change). Addresses #591/#592.

cc
2026-07-13 04:44:08 -06:00
vicrodh 0f88361427 fix(album): readable header text on light themes + gradient reaches the divider
The header sits on the always-dark art atmosphere, but its text used Theme.text-* which is dark on a light theme = unreadable. Route the header title/artist/meta/description through hdr-strong/hdr-body colors that go white over the gradient and fall back to the theme colors when the gradient is off; below-divider text is untouched. The atmosphere also faded out above the divider on description-bearing headers — raise its height to 440px and push the fade start to 74% so it reaches the header/track-list divider. Addresses #491/#519.

cc
2026-07-13 04:28:26 -06:00
vicrodh f5a8afc0f2 fix(playlist): reorder via the existing row drag gesture, not a bespoke grip
The prior reorder reused only the DragGhost but re-implemented the drag gesture with its own grip + absolute-position math, which placed the ghost several px off the cursor. Remove the grip entirely and drive reorder off the SAME row-body drag that already adds a track to a sidebar playlist: it captures the source index on start, the shared DragGhost (positioned by the Rust DragActions path) follows the cursor exactly, the drop slot is read from DragState.pointer-y against the ListView's absolute-position (no row double-count), and releasing WITHIN the list reorders while releasing on a sidebar playlist still adds. Reverted the sidebar drop suppression + removed DragState.reorder.

cc
2026-07-13 02:45:10 -06:00
vicrodh 85e11d284a feat(quality): downgrade arrow + true-quality tooltip on the Now Playing badge
Follow-up to #590: playback now downgrades to the streaming-quality setting but the badge still showed the catalog max with no indicator. The poll loop already received the delivered stream's sample-rate/bit-depth in PlaybackEvent but dropped them; publish them plus a quality-downgraded flag (effective < catalog max, DSD-exempt on depth) and the true effective detail string. AudioStamp shows an amber down-arrow glyph when downgraded and a Source/Output hover tooltip; mirrored to the miniplayer. Strings translated in all 8 locales.

cc
2026-07-13 01:49:18 -06:00
vicrodh 6e58682aa6 fix(playlist): reuse the shared drag ghost for custom-order reorder
The bespoke reorder ghost read a row's absolute-position inside a ListView, which double-counts the row's own offset, so it only sat at the cursor for row 0 and flew off for the rest. Drive the app-wide DragState directly from the reorder gesture so the shared DragGhost (positioned in AppShell at the proven window coords) renders the reorder ghost; a new DragState.reorder flag keeps the sidebar from claiming it as an add-to-playlist drop and keeps the Rust DragActions path out of it. Removed the buggy local ghost. Reorder logic + persistence unchanged. (state.slint also carries the quality-badge fields for the next commit.)

cc
2026-07-13 01:29:09 -06:00
vicrodh 4210ec088a fix(playback): honor the streaming-quality setting
The Settings streaming-quality dropdown persisted the pref but playback used a hardcoded UltraHiRes const, so the setting did nothing. Resolve the pref (via a new streaming_quality_for_key mapping to the Quality format ids) at playback time for the normal, prefetch and gapless paths. Resolves #590.

cc
2026-07-13 00:19:51 -06:00
vicrodh c85686c473 feat(media-controls): inhibit system sleep/idle while playing (Linux)
QBZ let the machine suspend mid-playback. Take an org.freedesktop.login1 Inhibit (sleep:idle, block) fd while the MPRIS status is Playing and drop it on pause/stop, hooked to the same play-state channel MPRIS already uses. Linux-only, all failures log-and-continue. Resolves #522.

cc
2026-07-13 00:19:51 -06:00
vicrodh b3f866d933 fix(audio): treat D-Bus device-reservation name failure as non-fatal
Under Snap's mediated session bus, requesting the org.freedesktop.ReserveDevice name is denied and the error aborted ALSA Direct stream creation, so there was no audio. Fall back to the existing Degraded state (proceed without the reservation) on acquire failure, reusing the same non-fatal path the card-index / no-bus cases already take. Audio sample path untouched. Resolves #534.

cc
2026-07-13 00:19:51 -06:00
vicrodh 3174488d8e fix(renderer): classify Intel UHD 600 (Gemini Lake) as weak -> GL tier
It flickers on the wgpu tier; the reporter confirmed QBZ_RENDERER=gl fixes it. Added the uhd-600 markers to WEAK_GPU_MARKERS so it auto-selects the stable GL renderer. Resolves #578.

cc
2026-07-13 00:19:51 -06:00
vicrodh 256b0d36a0 fix(volume): kill the slider rubberband on release
The live-drag thumb reverted to the round-tripped bound value on release; because that value echoes back asynchronously the thumb snapped to the stale position then to the fresh one (a rubberband bounce). The thumb now tracks drag-fraction, which stays put on release and re-syncs to the external value only when it changes on its own (MPRIS / keyboard) while not dragging.

cc
2026-07-13 00:19:51 -06:00
vicrodh a0cca35810 fix(volume): live drag state on the slider primitives so the thumb follows the pointer
The de-quantization (0.1% scale + f32-bits atomic) removed the stepping in the VALUE, but the thumb still derived from the round-tripped bound value, so it felt snappy/laggy during a drag. Give QbzSlider/QbzVerticalSlider a live drag fraction: while held, the thumb tracks the pointer position directly (continuous, no round-trip); on release it reverts to the bound value. The emitted value is still the nearest step to the pointer (round), now imperceptible at the 0.1% scale.

cc
2026-07-12 23:38:34 -06:00
vicrodh 42105eaf46 fix(volume): make the volume control fully fluid (de-quantize)
Volume snapped between 1% steps from two integer quantizers. UI: the volume sliders now use a 0..1000 scale so the shared int slider's round() lands on 0.1% (imperceptible). Runtime: the player volume atomic widened from integer-percent AtomicU64 to f32-bits AtomicU32 (same idiom as normalization_gain) so a continuous value no longer re-snaps on track restart / device switch / gapless. Bit-perfect untouched: 1.0f32 round-trips its bits, no gain multiply or ramp added to the protected ALSA-direct / DoP / JACK paths.

cc
2026-07-12 20:17:12 -06:00
vicrodh 2103bbd3eb feat(playlist): drag-and-drop reorder for custom-order tracks
Adds a dedicated drag HANDLE (grip) in the reorder gutter with the queue's drag gesture (6px threshold, absolute-position - root.y correction, three end paths incl. the changed-pressed safety net), so the row-body drag-to-sidebar stays intact. PlaylistView keeps component-local reorder state (slot math folds in the ListView viewport-y), a drop indicator and a floating ghost; commit fires PlaylistActions.reorder-track(from,to). Rust reuses the arbitrary-index persist path (repo::reorder for local, persist_custom for Qobuz) the arrows already back. Edge auto-scroll during drag is a follow-up TODO. Resolves #589.

cc
2026-07-12 20:17:12 -06:00