cpal 0.17.3 requires alsa 0.11, which in turn pulls alsa-sys 0.4. The
`links = "alsa"` constraint permits only one alsa-sys in the dependency
graph, so qbz-audio's direct alsa stack is raised in lockstep:
alsa 0.10 -> 0.11 and alsa-sys 0.3 -> 0.4. Both workspace lockfiles
(crates/ and src-tauri/) are updated to cpal 0.17.3 / alsa 0.11.0 /
alsa-sys 0.4.0.
No source changes required: alsa_direct.rs uses only the stable
high-level alsa API (pcm, HwParams, Format, Direction, ValueOr, mixer),
and the snd_lib_error_set_handler FFI in alsa_error_handler.rs is
unchanged in alsa-sys 0.4. Both workspaces pass cargo check.
cc
Fixes#467 (long-session offline death), #468 (favorite on see-all), #466
(PipeWire backend hidden without pactl), #452 (remix version on initial play
from favorites), #449 (sample-rate sync regression on PipeWire), and #453
(macOS queue drag — pending Mac confirmation). Includes the recovered adaptive
prefetch throttle that #467's offline-liveness signal builds on.
cc
c1c151ee added `-webkit-user-select: none` to <body>; on macOS WKWebView this
also blocks dragstart on the draggable .queue-track rows (WebKit couples
selectability with draggability), so queue reordering and its blue insertion
line silently broke. Marking the rows as `-webkit-user-drag: element` restores
drag initiation. WebKitGTK (Linux) is unaffected. Needs confirmation on a Mac.
cc
The stream-recreate decision only forced a rebuild on a decoded-format change
for bit-perfect backends (DAC passthrough, ALSA Direct, CoreAudio exclusive).
The default/PipeWire path relied on Stop dropping the stream to get a fresh one
at the new rate; once that drop was deferred to avoid a track-change click
(e93fcaec), the PipeWire node stayed locked to the first track's rate for all
subsequent tracks — a regression vs 1.2.10. Make any decoded-format change
rebuild the stream on every backend; same-rate tracks still reuse it, so the
click fix is preserved.
Verified on hardware via pw-top: PipeWire without passthrough now switches rate
per track; PipeWire with passthrough and ALSA Direct unchanged (no regression).
cc
The favorites queue builder forwarded the track version, so skipping/seeking to
a track showed its remix subtitle, but buildDisplayTrack (used for the initial
click-to-play via onTrackPlay) dropped it — so the now-playing bar, queue,
MPRIS and Last.fm lost the version on first play. Add version to the
DisplayTrack interface and buildDisplayTrack.
cc
is_pipewire_available() shelled out to `pactl info`, which lives in
pulseaudio-utils and is absent on PipeWire-only systems — hiding the PipeWire
backend even though PipeWire was running. Detect via the
$XDG_RUNTIME_DIR/pipewire-0 socket first (no extra package required), with
pw-cli and pactl as fallbacks.
cc
The heart overlay on AlbumCardLite always renders and calls onFavorite, but
VirtualizedFavoritesAlbumGrid never forwarded onFavorite/isFavorite, so the
button was dead on every discover see-all page. Thread both through the grid
and wire DiscoverBrowseView to the shared albumFavoritesStore (same pattern as
the collapsed DiscoveryView). Fixes favoriting across all six browse pages.
cc
The offline-skip recursion walked the entire remaining queue synchronously,
desyncing the logical queue from the still-playing engine. Fixes:
- The gapless-transition target is the track the engine is already playing;
never run the availability-skip branch for it.
- isTrackAvailable now counts the in-memory/disk cache (new v2_is_track_cached
command), not just downloaded copies.
- Cap consecutive offline skips and play-failure auto-skips at 5 in both
+page.svelte and playbackService.ts.
cc
A single transient 5xx / timeout / reset on the next track's file/url or a CMAF
segment used to be terminal and skipped the track. Add a retry wrapper (3
attempts, exponential backoff + jitter) around get_file_url, the init segment,
and per-segment fetches. Retry only transient errors (network/timeout, 5xx, 429
honoring Retry-After); a real 404 is classified as TrackUnavailable and
propagates immediately to the bounded skip path.
cc
Long sessions died when a single transient blip flipped the app to offline and
the queue then skipped every cached-but-not-downloaded track. Harden the
detector:
- Consecutive-failure hysteresis: declare offline only after 2 failed polls,
not a single blip.
- Replace the HEAD https://1.1.1.1 probe with a concurrent race of cheap
generate_204 endpoints, so a saturated link (long Hi-Res tracks) no longer
times the probe out.
- Positive liveness override: if a segment downloaded within the last 45s we
are online by definition, regardless of the probe.
cc
Recovered from 63ecc324 (authored on the native-port branch, never merged to
pre-release). Backend-only, no Slint. Scales the prefetch cap down on slow
links and during audio underruns, and exposes the per-segment download signal
the offline detector reuses as a positive liveness override (#467).
cc
Extracts two self-contained, framework-agnostic modules out of
src-tauri so they can be reused by other clients without duplicating
logic:
- ImageCacheService / ImageCacheStats move into the qbz-cache crate
(LRU disk cache, MD5-keyed, SQLite index).
- The credential store (keyring + AES-256-GCM encrypted file for Qobuz
credentials and the OAuth token) moves into a new qbz-credentials
crate.
src-tauri/image_cache.rs and src-tauri/credentials/mod.rs become thin
re-exports, keeping their Tauri managed-state wrappers. The Tauri app
is behavior-identical; both modules were already self-contained (no
Tauri and no crate:: references).
cc
Task 2 of the Slint POC readiness audit. AppRuntime now performs
framework-agnostic per-user session activation: activate, activate_at,
activate_offline, deactivate, plus is_session_active, active_user_id,
and a with_session_store accessor.
This is the minimal version per the 2026-05-18 audit correction. It
opens only the session store and does the portable scaffolding (user
paths, directories, last_user_id marker, runtime state machine). It
does not touch Tauri's session_lifecycle, does not initialize the
src-tauri-side per-user stores, and does not run the flat-to-user
migration. The shipping app keeps identical behavior; this is additive.
activate_at takes explicit directories and is the testable core;
activate resolves real per-user paths through UserDataPaths.
cc
Adds a framework-agnostic composition root for non-Tauri UI shells
(Slint, TUI, headless) as Task 1 of the Slint POC readiness audit
(session-20-slint-poc-readiness-audit).
AppRuntime<A> owns an Arc<QbzCore<A>> plus the runtime state machine,
built with the same recipe as the Tauri CoreBridge but with no Tauri
dependency. Scope is composition and accessors only; per-user store
ownership and session activation are deferred to Task 2.
qbz-app now depends on qbz-core, qbz-player, qbz-audio, and qbz-models.
The Tauri app does not consume this module; it is purely additive.
cc
Clicking a track inside an expanded Mixtape/Collection item now plays
it. Qobuz albums and playlists build the queue directly; Plex and
local-library albums delegate to the backend collection-item resolver,
then jump the queue cursor to the clicked track.
cc
Expanded playlist items showed "No results found"; fetch their tracks
via v2_get_playlist. Also carry the remix/edition version field so
titles render consistently.
cc
Add playback/audio/graphics settings to the diagnostics snapshot via
v2_get_playback_preferences. No sensitive data (no username or paths);
gated by the existing diagnostics opt-in toggle.
cc
Switch the session store SQLite connection from synchronous=NORMAL to
synchronous=FULL. With WAL + NORMAL the last writes can sit unsynced in
the WAL and be lost on a hard reboot / power loss, dropping the restored
session.
cc
Qobuz Connect renderer mode and casting are mutually exclusive output modes. While qbz is registered as a QConnect renderer, the Qobuz cloud drives playback through the renderer-command path, which bypasses the cast path entirely — the cast device receives no LOAD and stays silent.
connectToDevice() now checks QConnect status and, if it is on, suspends it (v2_qconnect_disconnect) before connecting the cast device. When the cast ends, disconnect() restores QConnect only if it was on beforehand; a failed cast connection restores it too. Applies to Chromecast and DLNA alike.
Fixes#439.
cc
TAG_DETAILS.md is the curated, user-facing release summary rendered by the website and the in-app About modal. CHANGELOG_FULL.md is the complete commit list for v1.2.12..v1.2.13, restoring the two-file release-docs layout last used at 1.2.5.
cc
Version bumped across Cargo.toml, tauri.conf.json, package.json, snapcraft.yaml (version + source-tag), flake.nix, and the lockfiles. metainfo.xml gains a 1.2.13 release entry, the About modal codename becomes "400+ stars", and the README contributors list adds @Vudgekek and @DoubleGate.
cc
Google Cast closes a control connection that goes roughly 10 seconds without a heartbeat PING. QBZ pinged every 25 seconds, so any cast connection left idle between connect and load — for example while the track downloads, or across a delayed second play attempt — was dropped by the receiver. The next LOAD then failed with EPIPE (broken pipe): the device chimed on connect but never played audio, and the heartbeat thread logged repeating broken-pipe warnings.
Pinging every 5s is the cadence the Cast protocol expects and keeps the connection alive through those idle gaps.
Fixes#439.
cc
Session resume of a Qobuz track no longer forces the streaming start-position path, which buffered the CMAF stream sequentially from byte 0 up to the saved offset (up to ~30s wait, with no buffering indication).
v2_play_track is now always called without a start position. The track resumes at the saved offset only on a cache hit (format_id === null), where the audio is a full in-memory buffer and Seek lands like a local file. On a cache miss it starts from 0:00 instead of blocking on a sequential buffer fill.
No backend or audio-engine change; the start_position_secs streaming path is now unused.
cc
The graphics recommendation banner no longer writes settings: the Apply button and the v2_apply_graphics_recommendation command are removed, so the banner only detects and explains. The user applies the change with the real toggles, matching the HiFi Wizard's advisory model. write_recommendation stays for the qbz --autoconfig-graphics CLI.
The Force DMA-BUF help text is now GPU-aware: the instability warning only shows on NVIDIA-only systems; non-NVIDIA and hybrid hosts are told it is safe to enable. On hybrid hosts the rendering GPU dropdown marks the integrated GPU as recommended.
cc
NVIDIA-only systems no longer get a hardware-acceleration recommendation that visibly stutters under WebKitGTK. The three NVIDIA-only branches (Wayland+GNOME, Wayland, X11) collapse into one CPU-rendering profile, since XWayland does not rescue the stutter either.
NVIDIA + iGPU hybrid now allows DMA-BUF: WebKit composites on the stable iGPU stack, so the instability that affects NVIDIA-only systems does not apply. The recommendation also tells the user to pick the iGPU explicitly in the rendering GPU selector instead of Auto.
cc