Rename commands_v2.rs to commands_v2/mod.rs as the first step
of splitting the 14,635-line file into domain-specific modules.
No functional changes.
cc
Chain Option combinators to reduce nesting. The await boundary
requires one outer if-let, but the rest flattens into a single
and_then/map/filter pipeline.
cc
The custom QConnect device name was stored in-memory only,
resetting to "Qbz - hostname" on every app launch.
Now persists to qconnect_settings.db in the global data dir
(~/.local/share/qbz/). Loaded on startup, saved on change.
Fail-open: any db error silently falls back to default.
Fixes#284
cc
- V2_PREFETCH_COUNT: 2 -> 5 (cache ~2 min ahead)
- V2_MAX_CONCURRENT_PREFETCH: 1 -> 2 (overlap downloads)
- V2_PREFETCH_LOOKAHEAD: 10 -> 15 (find more Qobuz tracks)
Combined with parallel CMAF segments from previous commit,
this keeps the cache well ahead of playback. The sliding
window re-triggers on every track play, so the 5-track
buffer advances continuously through the queue.
cc
Download CMAF segments concurrently (3 slots) instead of
sequentially for prefetch/cache operations. Each slot has a
500ms cooldown after download to stay under CDN rate limits.
This only affects cache prefetch (try_cmaf_full_download),
not real-time streaming which remains sequential for smooth
playback. Segments are reassembled in order after download.
Typical improvement: 10-segment track goes from ~10 serial
HTTP requests to ~4 batches of 3 concurrent downloads.
cc
WebView OAuth: cancel button appears during loading, captcha hint
shows after 25s or when user closes WebView. Hint suggests trying
system browser instead with one-click action.
System browser OAuth: cancel link appears next to loading text,
immediately aborts the wait.
i18n: new keys captchaHint and trySystemBrowser in all 5 locales.
cc
Add OAuthCancelState with shared Notify for system browser flow.
v2_start_system_browser_oauth uses tokio::select! to race between
code received, 120s timeout, and user cancel signal.
New commands:
- v2_cancel_system_browser_oauth: triggers cancel notify
- v2_cancel_oauth_login: closes all OAuth WebView windows
Timeout reduced from 300s to 120s for system browser flow.
cc
When the user closes the OAuth WebView window manually, the
Destroyed event now triggers notify to unblock the 5-minute
timeout immediately, returning oauth_cancelled instead of
hanging until timeout.
cc
Add request_ts + request_sig (MD5 signature) to catalog/search,
album/search, track/search, artist/search, and playlist/search
endpoints matching the mobile app authentication pattern.
Migrate v2_search_all from legacy AppState to CoreBridge, removing
the last direct API call that bypassed the V2 crate architecture.
cc
Pass dragTrackIds to TrackRow in album, playlist, favorites, top-q,
weekly suggest, dynamic suggest, and fav-q views. VirtualizedTrackList
also passes selected IDs for multi-drag.
cc
Tracks are now draggable from any view. Dropping onto a playlist in the
sidebar adds them directly via v2_add_tracks_to_playlist. Supports
multi-select: when tracks are selected in playlist view, dragging any
selected track carries all selected IDs.
cc
When selecting ALSA Direct as backend, alsa_plugin was never persisted
to the DB if the user didn't explicitly change it. The UI showed
"hw (Direct Hardware)" as a visual default but the DB stayed NULL,
causing isAlsaDirect to evaluate false and showing a gray DAC badge
instead of the blue HW badge.
cc
The NowPlayingBar correctly locked the volume slider but the immersive
player (fullscreen/focus mode) was missing the volumeLocked prop chain,
allowing the slider to move visually even though the volume change was
blocked. Respects Qobuz Connect peer renderer override.
cc
Reduce log noise by demoting ALSA device enumeration from info to debug.
Add a 5th backoff retry at 800ms for both hw and plughw paths as a
safety net for any EBUSY cases that slip through the lifecycle fix.
cc
In the Play and PlayStreaming handlers, the old engine's writer thread
still holds an Arc<AlsaDirectStream> when the stream_opt is dropped and
a new snd_pcm_open() is attempted. This causes EBUSY on track switches
with sample rate changes. Reorder to: stop engine -> drop stream -> open
new stream, matching the Stop handler's correct lifecycle.
cc
- Device enumeration (48 CPAL devices): info → debug
- Playlist fetch completion: info → debug
- Playlist command invocation: info → debug
- zbus socket_reader/tracing: filtered to warn level
These remain visible with RUST_LOG=debug or verbose log capture.
cc
Add per-app secret from org.freedesktop.portal.Secret as entropy
in the AES-256-GCM key derivation. DE-agnostic (works with GNOME,
KDE, etc.) and Flatpak-safe (portals auto-accessible in sandbox).
Falls back to machine_id + salt when portal is unavailable (headless,
old DEs, non-Linux). Cached for session lifetime via OnceLock.
Users will need to re-login once on upgrade (key derivation changed).
cc
Token was stored as plaintext in the system keyring, visible via
secret-tool. Now AES-256-GCM encrypted before storing — keyring
entry shows ciphertext, not the raw token.
Handles migration from both plaintext keyring entries and encrypted
files from previous versions.
cc
OAuth token was saved to BOTH keyring and encrypted file, making the
keyring pointless. Now:
- Save: keyring first. Only writes file if keyring unavailable.
- Load: keyring first. If found in file, migrates to keyring and
deletes the file.
- Result: when keyring works, no token file exists on disk.
cc
Move the CMAF app seed from a hardcoded constant in qbz-cmaf to a
parameter passed by callers. The seed now lives in qbz-qobuz::auth
where other app-level constants are defined. This resolves CodeQL
alerts for hard-coded cryptographic values.
Also truncate session_id in logs to avoid cleartext logging of
sensitive session identifiers.
cc
Logout was not clearing OAuth tokens or credentials from keyring
and encrypted files. Users had to manually delete files to fully
sign out. Now clears both keyring and file storage.
cc
Cargo.toml was left at 1.2.2 because the version bump sed command
only matched JSON format ("version": "1.2.2") not TOML format
(version = "1.2.2"). This caused CARGO_PKG_VERSION to report 1.2.2
at runtime, triggering the update-available modal on every launch.
cc
Keyring save/load failures were logged at debug level, making them
invisible in normal logs. Users on KDE Plasma 6 reported the keyring
feature not working (#276) but couldn't see why.
Promoted keyring failures to warn level with actionable messages
(e.g., KDE users need org.freedesktop.secrets enabled in KWallet).
cc
Loop used 1..n_segments (exclusive) but n_segments is the count of
audio segments, not the last index. Segments are numbered 1 through
n_segments, so the loop must be 1..=n_segments (inclusive).
This caused the last ~9 seconds of every CMAF-streamed track to be
missing. Confirmed with diagnostic logging: table_total_samples
exceeded actual decoded frames by exactly one segment's worth.
cc
Template had a different src_compile (npm run build + cargo build)
than the overlay's working ebuild (npx tauri build --no-bundle).
Now they match exactly, preventing the release workflow from
breaking the overlay.
cc
Logout left audio playing through the ALSA/PipeWire backend while
tearing down the session. Now explicitly stops playback and clears
MPRIS state as the first step of logout.
cc
OAuth token save/load/clear now use the system keyring (GNOME Keyring,
KDE Wallet) as primary storage, with the existing AES-256-GCM
encrypted file as automatic fallback.
On first load from file, the token is migrated to the keyring
automatically for future use.
The previous commit enabled the keyring backend but only wired it
for email+password credentials (no longer used). This wires it
for the OAuth token which is the active auth method.
cc
Enable async-secret-service + crypto-rust features on the keyring
crate, activating GNOME Keyring and KDE Wallet support. The keyring
integration code was already written but the crate was compiled
without any platform backend, so it silently failed every time.
OAuth tokens will now be stored in the system keyring when available,
with encrypted file as automatic fallback.
Added org.freedesktop.secrets to Flatpak finish-args for sandbox
keyring access.
Fixes#215
cc
pause, resume, stop, seek, and volume commands required
RequiresClientInit which fails when offline (no internet = no bundle
extraction = client not initialized). These commands only use the
local Player/CoreBridge, not the Qobuz API client.
Changed to RequiresUserSession which passes in offline mode since
the offline session activates from cached user data.
Fixes#173
cc
Fixes 3 dev server vulnerabilities:
- Path traversal in optimized deps .map handling
- Arbitrary file read via WebSocket
- server.fs.deny bypass with queries
Closes#274
cc
New encrypted CMAF streaming via Akamai CDN, replacing legacy nginx
direct downloads that were failing with "1 byte EOF" for many tracks.
Key changes:
- qbz-cmaf crate: HKDF key derivation, AES-CBC/CTR decryption, CMAF parsing
- qbz-qobuz: session/start + file/url endpoints
- Streaming playback: init segment fetch (~500ms) then background segment download
- CMAF-first with legacy fallback on all download paths
- Quality matching: request track's actual quality, not always UltraHiRes
cc
playTrack() was always sending the user's max quality setting (Hi-Res+)
regardless of what the track actually offers. A 44.1kHz/24bit track
would trigger 3 API fallback calls (UltraHiRes -> HiRes -> Lossless)
and the cache was discarded as "lower quality" for re-download.
New effectiveQuality() picks the minimum of user max and track metadata
(bit_depth + sampling_rate), so a 24bit/96kHz track requests Hi-Res
directly (1 API call) and cache matches perfectly.
cc
Frontend sends "24bit/44.1kHz" format from track metadata, but
parse_quality fell through to default UltraHiRes for unknown formats.
This caused unnecessary API quality fallback cascades (3 calls per
track) and cache mismatches where cached 24bit/44.1kHz data was
discarded for re-download that returns the same quality.
Now parses "XXbit/YYYkHz" format and maps to the correct Quality
level, requesting exactly what the track offers.
cc
A track mastered at 44.1kHz/24bit IS the maximum quality available.
Re-downloading won't produce a higher sample rate. Only check bit
depth for UltraHiRes/HiRes quality matching — if cache has 24-bit,
use it regardless of sample rate.
Previously, requesting UltraHiRes with a 44.1kHz/24bit cache would
discard the cache and re-download, getting the exact same data.
cc
Add CMAF fallback to streaming probe failure, standard download,
and individual prefetch paths. Legacy nginx CDN path kept as
automatic fallback for each. Cast/DLNA/offline-cache paths marked
with TODO (require CoreBridge parameter plumbing).
cc
Prefetch was still using the legacy nginx CDN path, causing "1 byte
EOF" failures on tracks that only work via the new Akamai CDN. Now
tries CMAF full download first, falls back to legacy on failure.
cc
Replace full-download CMAF with streaming: fetch init segment, start
playback, then download+decrypt audio segments in background. Player
begins playing after ~500ms instead of waiting for full download.
cc
Download tracks via Qobuz Akamai CDN with encrypted CMAF segments.
Derives session key (HKDF), unwraps content key (AES-CBC), decrypts
frames (AES-CTR), reassembles into standard FLAC. Falls back to legacy
track/getFileUrl if CMAF fails.
- Add qbz-cmaf dependency to src-tauri
- Make ensure_cmaf_session public on QobuzClient
- Add ensure_cmaf_session and get_file_url passthroughs to CoreBridge
- Add download_audio_cmaf and try_cmaf_download helpers
- Wire CMAF as primary download path in v2_play_track with legacy fallback
cc
Auto-advance from a previous track could arrive after the user
switched to a new album/playlist, advancing the new queue's index
and skipping a track (observed: Sorceress track skipped).
Added queue epoch counter that increments on setQueue/clearQueue.
nextTrackGuarded() captures epoch before the invoke and discards
the result if the queue changed while in-flight. Applied to all
auto-advance paths (track-end, gapless transition) but not to
manual skip (user-initiated).
cc
download_audio() was sending bare GET requests without User-Agent,
while all other CDN request paths (probe, streaming) sent Mozilla/5.0.
CDNs commonly reject or truncate responses from requests without
User-Agent, explaining the "1 byte then EOF" failures on tracks
that work fine in the web player.
Also removed the 120s total timeout that contradicted the comment
saying "no total timeout" — Hi-Res+ files can be 200MB+ and need
more than 120s on slower connections.
cc
When skipping tracks quickly, the previous PCM handle may not be
fully released by the kernel before the new track tries to open
the same ALSA device (snd_pcm_open fails with EBUSY).
Previous behavior: single retry after 200ms PipeWire suspend, then
immediate failure. This didn't cover our own PCM race condition.
New behavior: 4 retries with progressive backoff (50/100/200/400ms)
for both hw and plughw paths. PipeWire suspend still attempted once
upfront. Total worst-case wait: 750ms (vs 200ms before), but the
early retries at 50-100ms cover the common fast-skip case.
cc
StreamFetcher used a persistent reqwest::Client for the entire app
session. When a CDN connection broke mid-transfer, the HTTP/2 pool
kept reusing the dead connection, causing all subsequent downloads to
fail after 1 byte ("end of file before message length reached").
Changes:
- StreamFetcher now creates a fresh client per download attempt,
matching the pattern used by download_audio() which never had
this issue
- Added retry logic with exponential backoff (3 retries, 1s/3s/5s)
— offline cache previously had zero retries for multi-hundred-MB
downloads
- Clean up partial .tmp files on failure before retrying
- Extracted try_download() for clean retry loop
Root cause: issue #268 / reported in #270 testing
cc
Remove dead alsa dependency from src-tauri/Cargo.toml (code lives in
qbz-audio since V2 migration) and bump qbz-audio to alsa 0.10.
Eliminates duplicate alsa versions in Cargo.lock.
Breaking changes in 0.10 (Format/ChmapPosition reorder) do not affect
QBZ — we use catch-all matches and never rely on PartialOrd.
Closes#260
cc
Artwork bumped from min(45vh, 360px) to min(52vh, 460px), matching
coverflow center cover. Padding reduced to 52px/88px consistent with
other panels. Responsive breakpoints updated accordingly.
cc
The side covers partially overlapping the center is correct coverflow
behavior — they just need to render BEHIND the center cover. Center
has z-index: 10, sides have 4/3. Restored original-style offsets
(200px/340px) with 45deg rotation for the classic coverflow look.
cc
Remove transform-style: preserve-3d and translateZ which caused center
cover to render as ghost/transparent. Remove duplicate perspective on
parent panel. Add dark backdrop to quality badge in all immersive views
for legibility on light backgrounds.
cc