20 KiB
20 KiB
QBZ 2.1.0 — Technical Changelog (v2.0.2 → v2.1.0)
~750 commits on pre-release (2026-07 → 2026-08). This release replaces the
Slint frontend shipped in 2.0.x with a native Qt/QML frontend (qbz-qt,
cxx-qt bridges) running in the same single Rust process, and ships the first
Windows build. The frontend-agnostic core crates carry over unchanged in
architecture; the protected audio path (ALSA direct / PipeWire / Pulse /
JACK, bit-perfect) was not modified by the frontend swap. Sections group work
by area rather than by commit date.
1. Architecture
- Slint frontend retired (
69ed61207): theqbz-ui,qbz,qbz-slint-common,qbz-dac-wizardandvendorcrates left the live workspace. The DAC wizard's headless logic survives asqbz-dac-wizard-core. With the ~1.6 M-line generated Slint module gone, the 20–30 GB compile wall is history — a full release build now takes minutes on standard runners. - New frontend crate
qbz-qt: Qt Quick/QML UI over cxx-qt bridges, built up in phases 0–23 (469f83781…9a4fec6b7) from login shell to full parity, then split out of the initial God-object into per-domain bridges (ade156a72,5c2577c7c,fa433d465). qbz-source— the source-agnostic media seam (f1b424134): one registry that resolves every play/queue/artwork request by provenance (Qobuz, local, offline cache, Plex, Jellyfin, Subsonic, disc), withPlaybackTicketas the single audible step (a55e42633,7113ef579). My QBZ and mixtapes were rewired through the seam (b6cf06ff2,d9ed19eb6).- New crates this cycle:
qbz-jellyfin(e578d8a54),qbz-subsonic(0f41c44da),qbz-media-cache(0dfec607c),qbz-local-catalog(a0d8a7fc9),qbz-disc(db0999bf6),qbz-rip(5d3d51a88),qconnect-lan(LAN discovery/pairing service, merged in28fe63514).
2. Qt frontend port
- Full parity rebuild against the Slint reference: shell, discover/home,
library, album/artist pages, settings 1:1 (
83999fc55), playlists and My QBZ (a16cf56c8,acf23abc9), blacklist manager (494596616), purchases (7d9d3b896-era,475f261dc,7d98ad044), awards pages (dc2496e5c), HiFi Wizard (8ba948346), immersive mode (B1–B5,b5298b78b…c0f4d2f1b), search cortinilla with local sections (15610bc7e,a7e64c0bd), hotkeys with a cheatsheet and customize editor (0cca9abc5…07b0ba818). - Miniplayer and tray: borderless mini window with queue and lyrics
surfaces (
cd89b4094,684fffd7f), system tray via ksni (56d438717), close-to-tray on every exit path (088387f2c), window position restored after un-hiding (f64b8480d). - MPRIS: the desktop sees the player again (
5dd450fb9);Positionis computed at read time andSeekedis emitted, so external widgets stop extrapolating a frozen position (f45b703c6,7ba07ec6f,8bc32a91a). - Session persistence: view triple, window geometry, playback state and
per-page scroll positions restored across restarts (
c2944db97,89c54cd5b,e816436fd,221572b36). - Kiosk profile: a zone-navigated full-screen shell with its own NavRail,
card primitives and eight views, plus a live toggle (
87c20fce8,221f8704d,63e36c754). - i18n: live language switching across the 8 bundled locales via the
trRevdependency pattern (245eaa4d4);qbz-i18nowns its catalogues (1352e499f). - QoL: Excel-style Shift multi-select on every surface (
750c59034), an app-wide typeface setting (3c4c2dae2), system-font script fallback (82953c72d), artwork fade-in on arrival (58fa77a39), desktop notifications only where a toast can arrive (27022fbf4) with fresh portal ids per track on Plasma (f6574cdb9).
3. Windows port (PR #725)
- First-class Windows build: WiX MSI packaging with an install/launch/
uninstall smoke in CI (
8fb02401f,783dbb964,9a0dbd60c), custom title bar with a native hit test (415cba57d), single instance andqbz://protocol deep links (afa32d9bb), session persisted when Windows ends it (3b032e8c4), miniplayer keep-on-top (c2272ad9f), an as-is disclaimer shown once per version (2e0e29277). - WASAPI exclusive-mode sample-rate probe and hotplug watch
(
c068b2fd6).
4. Local Library, catalog & media servers
- Derived local catalog (
qbz-local-catalog): Albums, Artists and Tracks page from a reconciled per-source catalog (1bb3c41dc,299dcde5b,6fd83c42d,7689fb0df), with incremental local scans (480b4b46d), incremental Plex section sync (6e0e0d316), generation-tagged Subsonic sync (6005510bf), background Jellyfin quality hydration (8fff37a8e), recovery guardrails (ed8d11e5c) and album-version unification across sources (d627560f4). - Jellyfin and Subsonic integrations: protocol crates verified against
live servers (
e578d8a54,0f41c44da— the Subsonic HTTP-200 error trap is enforced in code),JellyfinSource/SubsonicSourcethrough the seam with the full chain walked against both real servers (b09f03b73,51aa8ba08), one shared media cache table (0dfec607c), filter chips in Local Library (322b9374f), merged into both local search paths (13e938727). Playback from both verified bit-perfect by checksum. - Library Explorer (
9c22a909d): tree-style browsing across every source, with drag-and-drop of rows into sidebar playlists and the queue, a collapse-caret tooltip and a collapsed filter strip (c37768459). - Local tag editor restored and expanded: split editor workspace,
canonical write verification, remote metadata lookup (
0e5383758,75a668ed1,d81f69113), scoped track-row selection (fa649394d), completed editor sessions (5a14eef25). - Multi-disc albums: a divider that names and illustrates each disc
(
fd25593e3); SACD image tracks persist in the library (23a4eb291). - Favorites freshness (#690): favoriting an album shows up in Library
without a restart (
bb1b1a564). - perf: covers no longer drag their embedded artwork through
loftyjust to read tags (34d19211d, 9.3 s → 0.9 s on a large scan), high-cardinality views windowed and virtualized (e0e9566f8,c6d142ccb), Tracks tab pages append without a model reset and keep the viewport (fcb985173,f8092db81).
5. Discs: CD, SACD and ripping
- CD-DA playback off a real drive, measured before it was written
(
db0999bf6), with a platform-split reader for macOS (8d4ed8e95); a CD track is a source ticket of its own, not a file (3ba476b43). - SACD images: the stereo area reads and plays (
06405969e,452589dda); the variable per-sector header and the byte-interleaved stereo layout are handled correctly (70defb2e8). - Ripping (
qbz-rip): CD → tagged FLAC, bit-exact and seekable (5d3d51a88), with a rip wizard, metadata correction pane (2273db918), remote album metadata search (b6deec0aa), a provenance log and cover beside the tracks (b69a058bc), and disc memory after eject (57ae46566).
6. Playback & audio
- ALSA hardware volume (new, guarded — #726 hardening): capability-probed
enumeration of simple-mixer elements with typed identities
(name, index), per-device+route persistence keyed by udev identity, UCM read-only hints, a closed selector when more than one candidate is valid, and fail-closed behavior on ambiguity or disappearance (0e8614fe3,96342e23b,c3556fe3e, mergedc059c12bf). Exposed in Qt Settings and the qbzd CLI/TUI. The mixer opens on the card ctl id, not the PCM alias (#659,def93528b). - Playback recovery: per-attempt total deadlines, transient retries and
monotonic track-identity signals for source failures; the queue advances
even if polling missed the stopped pulse — an unreachable track can no
longer stop playback (
99722aeda, mergedbb43dea5b); audible state is preserved on source failures (ecae778d5). - Stereo scope visualizers (
d11e94622), with zero-frame packet skipping (6046a959c). (The seekbar-waveform experiment from the same round ended up parked and hidden —a92dafa73— and is deliberately not listed as a feature.) - ALSA Direct visualizer alignment: ~10 ms pacing aligned to the
playhead via
snd_pcm_delay(eff9c484e). - CMAF: the superseded track's segment feeder is cancelled
(
a561e7ae2), stale gapless pending state cleared on PlayStreaming (740adf55d), fetch timing split into send vs body (63b4082e6). - DSD: conversion cancelled when its track is superseded (
a0ed0d752);AudioFormat::Dsdround-trips through the database instead of decaying to Unknown (6328994c0); the quality badge classifier learned DSD (71f8d0897). - Listen log (merged
a3161254e): a per-user listening event store and tracker with reading rules, a Listening history toggle and clear in Settings, cross-source identity capture (ISRC / MusicBrainz recording ids from tags, Plex, Jellyfin and Subsonic), and scrobble start-stamping timed by audible position. A gapless hand-off closes the previous row as natural, not skip (7b05ef55a). Groundwork for offline recommendations. - Pulled tracks: tracks Qobuz removed are marked and kept out of the
queue (
7780efa5d), with an available-version finder (b2c74c414) and a menu that offers nothing leading nowhere (e779b6ec1). - Offline and logged-out scrobbling restored (
aadad1da5); player volume persists across launches (b8b493bd5).
7. Qobuz Connect
- LAN parity (merged
28fe63514): the official-client LAN service — HTTP + mDNS announce, delegated credentials kept isolated, a transactional session coordinator — so official Qobuz apps discover and pair with QBZ on the local network, always-on with the global QConnect switch, exactly like the official clients. The wire contract was normalized from real captures (APK/Electron/web bundle). - Hardening: real buffering states (
PLAYING+BUFFERINGintent preserved), duration/offset corrections, authority fences and bounded teardown for Qt and qbzd, last-wins takeover, and exact Cast↔QConnect transfer with synchronous epochs. Shuffle materializes the WS seed exactly (xoshiro128** + official Fisher-Yates), remote volume/mute project onto the now-playing state, and ceding to another renderer waits forACTIVE_RENDERER_CHANGEDbefore stopping. Race-sensitive logic lives once inqconnect-app, not per-frontend. - Verified in smokes against the official iOS client and Web Player: identical queue order on all three, volume both ways, handoff without pausing the session.
8. Casting
- Round two (merged
44c108d4d): progressive serving, clicked-track routing, a source proxy so Plex/Jellyfin/Subsonic tracks cast too, and a clean shutdown path. - Single remote-renderer seam:
route_play_remote(cast → peer → local) behind every play entry point, a typedNoMediaSessionidle poll and a volume coalescer (merged95f2d6519). - Visualizer while casting: a shadow decoder feeds the scopes/spectrum
for proxied and direct tracks while audio renders on the cast device
(merged
3fc23aea4,41b8e7e6f— the latter also fixes DLNA stop-before-load). - X.509 v1 device certificates accepted in the Cast TLS handshake
(#730, merged
e9ba7a7bf):rust_castvendored with a 38-line delta, verified byte-for-byte against crates.io — older Chromecast built-in devices work again.
9. Playlists & queue
- Add-to-playlist redesign (merged
4c4647bdf): a membership index with a background hydrator, a picker showing "Already in" and the last-used playlist, local drag-and-drop, Jellyfin/Subsonic tracks in playlists, and quality badges with tooltips. - Album Quick View and queue focus controls (
d555b8fde). - Play later, everywhere (merged
ea02cc238): album-level enqueue takes the block-tail arm (#442) so "later" means after the current block; "Play all later" on the Popular Tracks group. - Extended queue view (
c7038a685), drag-and-drop insert at position (c198afb33), reorderable history occurrences (4630c4022), playback history persisted across restarts (a2d0ada42). - Importer expansion: playlist files, JSON, ListenBrainz and Last.fm as
import sources (
7e233104f), reachable from the Playlist Manager toolbar (9899ad6b0). - Mixed playlists open offline (
b4744b6e7); playlist cover editing restored (aa66c58ea); custom covers propagate to every surface (d112c726b).
10. Search & cortinilla
- Local sections in the search cortinilla with click routing into the
LocalLibrary tabs (
15610bc7e,4806584ee), instant cached first paint (b38b0b3d5), module-OFF results fallback (10f249be2), debounce moved from QML into Rust (5a194d249). - Focus-driven lifetime instead of idle timers, a Cut/Copy/Paste/Select-all
menu on the search input, and "Open containing album" on track rows
(merged
ea02cc238). - Tracks results use the app's full track row (
7fc760588); section rows can open their first tab on click, opt-in (e9f09a59a).
11. Immersive & visualizers
- Immersive port and expansion: root overlay and header band (
8bc255269), atmosphere and focus panels (46c1c4240), split view with lyrics, track info, suggestions and queue tabs (0b6e8ed7c), player bar and in-immersive search cortinilla (c0f4d2f1b), shader scenes with RHI items and GL link pairing (e6c058c2f), artist scene with the shared scene-discovery engine (016eb3cd2), remembered shader scene (47cab502f). - Visual cleanup round (merged
0b5ce47f2): polarity-aware ambient veil for light themes (2e3c76561), overAmbient legibility for the quality badge and track info (949399312), accents derived from the album palette (1a4797085), reworked cinematic split-card metadata (460c0cca4), karaoke dim of the unsung line part (68eeb1017), minimize/maximize in the window capsule (aeeb55198).
12. Performance — the GPU campaign (95% → 25%)
Full investigation:
qbz-nix-docs/qt-frontend/2026-08-11-scenegraph-batches/GPU-COST-INVESTIGATION.md.
- The shell (dynamic background + Large-bar visualizer) went from 93–97%
GPU / ~34 W to 25–26% / ~13 W measured on the reference hybrid laptop —
better than the Slint 2.0 UI (35–56%) on the same machine (
f46c5c95d,a2a99a72c). Root cause: full-window present rate, not render cost — Qt Quick has no partial repaints, and with KWin compositing on the dGPU every present costs ~1.2% GPU flat, area-independent. - One repaint pulse for the whole shell (
QbzShell.pulseMs, ~30 Hz,QBZ_PULSE_MSknob): background atmosphere, visualizer and lyrics engine tick on the same edge. Standing rule: no continuous animation owns aTimer/NumberAnimation/Behavior, and a frozen or invisible component writes nothing. - Leaks closed: the ambient FBO double-presented; immersive panels (mounted while the overlay is closed) wrote on every FFT publish; 100 ms Behaviors animated at display rate (immersive 83% → ~25%).
- Blurred background mode composes its four layers + dim + scrim in one opaque ShaderEffect with verified numerical parity; the image stack remains as the software-renderer fallback.
- The animated now-playing row indicator returns (eq bars behind the
play-indicator-animationpref), mounted on the pulse at zero extra presents (eb80e2f36). - Elsewhere: tabs build only what is showing, with a build-gate that fails
on hidden-tab construction (
a4c29c796,a6b373cb5); navigation cost is measurable headless (801543930); route changes acknowledged on the next presented frame (5b274cab5); two-phase route commit cut dead-click time 590 → 73–90 ms; the local albums grid actually recycles (bd9aec144); album page track list rides a uniform-cell ListView (047db8ea9); a long restored queue no longer freezes launch (66ba12bd5).
13. qbzd
- Event hooks (PR #700, Filippo Vicentini, merged
e109e88ec):hooks.script(orQBZD_HOOK) names an executable the daemon forks once per daemon event, the event described inQBZ_*environment variables — the pleezer/shairport-sync push-integration contract, aimed at moOde, Volumio and DIY boxes (ee1bbf9b7), with hardened hook execution (251f153a6). - Gapless robustness (#699, Pi 1 GB): the successor warms on every track
start and cold gapless hand-offs stream instead of buffering
(
e90ea8cad), gapless fetches stay off the driver loop (eb643b9d3), plus inhibit backoff (merged51b7e012f) — smoke-tested on a real Pi. - ALSA hardware volume controls in the CLI/TUI, same probe and closed
selection as Qt (
c3556fe3e). - QConnect: qbzd shares the hardened
qconnect-appcore (shared state, volume mode, task registry adapters), with a hardened ownership lifecycle (c927eb9c9), the quality cap enforced for QConnect (5bae44286), and a cold load of the current track on a state-only resume (7f9397f91). - State reliability: bridge state changes deduplicated on the mapped
three-way state (
e336d2d1d);TrackStartedre-emitted when the same track replays after a stop (8ec1f3c27); per-OS instance lock gate (f06ffc714). - Packaging & deployment: standalone qbzd packages with IP masking in
logs (
efa486ba4); appliance and read-only deployment documented (6bfeb88c3); glibc floor 2.35 on both arches so the Pi streamer keeps working (51daf760b). - Misc: low-memory profile re-wired (#660,
a69491a49); process-level rustlsCryptoProviderinstalled (#663,a01097708); a listen-log subscriber beside the scrobbler (3e060d653); the artist-vector store logs loudly when it fails to open (6587201a1).
14. Community-contributed fixes (2.0.x issues)
- Real desktop keyring backends for secrets (#697,
b60ed2c26) and a mobile KDF fallback (#695,4d8779e17). - Relocatable CMAF offline bundles (#696,
0a934ae4a) and a configurable bundle cache directory (#708,9aeee886d); atomic bundle cache replacement (#707,388a3591e). - Optional mixtape source resolution (#694,
910bee49a); lofty unified at 0.25.1 (#679,c039f0d0e,b6c69509d); qbzd rustls TLS provider (#663,fe2be63f0); ALSA buffer/period sizes asalsa::pcm::Frames(82fea6257); renderer preference honored with an empty backend env (#720,f9b139b44); preferred-GPU startup hardened (79f91492b). - Hotkeys + Vim keymap (PR #724, Niklas Herder, merged
e993bc1e4). - Contributor credits swept into README and About (
b2d3656eb,fbc8e9fa3,acc0e3302); About gained a Sponsors section (mergedea02cc238).
15. Build, CI & packaging
- Release workflows rewritten for Qt on standard GitHub runners:
reusable Qt Linux build (
929ce39f0), Linux + aarch64 (149c021e4), macOS with aqt Qt + macdeployqt (7f98e7b9a), Snap packs the AppDir and the Launchpad source recipe is removed (89030b5b2), Flatpak repacks the bare binary on the KDE runtime (c6bfb90ed), Windows release workflow with WiX authoring and LGPL notices (8fb02401f). - test-crates as a living regression gate with
scripts/cargo-test.shas its exact local mirror (ce57551a7), a Qt gate with the QML audits shipped in-repo (077e5fd97) — six audits including shader bake (af8d373d2) and baked-icon coverage (ffc2d7d66) — and a shared qmllint gate across Linux and Windows (bf710fb7e). - glibc floors: x86_64 2.35, arm64 desktop 2.39 (aqt's arm64 qmake
segfaults on 22.04-arm), qbzd 2.35 everywhere, enforced arch-aware in CI
(
51daf760b). - Desktop-entry ↔ MPRIS ↔ icon invariant checked against recipes and built
packages (
a1763df25); macOS Liquid Glass icon via Icon Composer (8b77f1ae0); hardened AUR/Gentoo/updater-manifest publishing (ac506e4d6,aaf9573c5,ecd4fd05c); MSI smoke verifies both URL protocols (353ed7fff). - README rewritten for the Qt frontend (
08d924e1b).
16. Dependency bumps
cargo update: 194 semver-compatible bumps, plus rand 0.10, roxmltree 0.21, base64 0.23, prost 0.14, tokio-tungstenite 0.30, mpris-server 0.10, mdns-sd 0.21, rfd 0.17 (merged7d9d3b896).- rusqlite 0.40: explicit i64 casts (~40 sites); its multi-statement
execute()rejection exposed a latent 0.31-era bug — the second statement of the editions prune never ran; nowexecute_batch. - reqwest 0.13: feature renames,
query/formopt-in; live HTTP paths re-smoked (login, streaming, scrobbling). - Deferred to 2.1.1 (coupled to the protected audio stack or multi-day): symphonia 0.6 + rodio/cpal/alsa wave, cxx-qt 0.10, keyring 4, the RustCrypto wave, ringbuf 0.5.
Full changelog: https://github.com/vicrodh/qbz/compare/v2.0.2...v2.1.0