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
Three changes to prevent overlap:
1. translateX increased from 280px to 400px for adjacent covers
2. rotateY reduced from 45deg to 30deg (less inward swing)
3. Added translateZ(-120px) to push side items behind the Z-plane
The 3D perspective projection with rotateY causes the near edge of
side covers to swing toward center. Reducing rotation and adding
Z-depth separation fixes the visual overlap.
cc
Center cover bumped from 360px to 460px. Side covers pushed out to
280px/460px offsets (from 180/300) to prevent overlapping the center.
Removed broken slideLeft/slideRight keyframe animations that
referenced unset --translate-x variable and conflicted with the CSS
transitions already on each item. Transitions alone handle all
position changes smoothly on click/skip.
cc
Bump artwork vh constraint from 60vh to 72vh — the previous value was
the bottleneck on laptop screens (461px on 768px viewport). Now yields
~553px. Reduce vertical padding from 164px to 140px total. Compact
track info margins below artwork.
cc
Change from center-center floating layout to stretch-fill approach.
Artwork section now gets fixed 42% width proportion and fills column
height. Artwork max-width overridden to 100% of column (no viewport
caps). Columns stretch to fill available height, eliminating dead
space above/below. Track info made more compact. Panel section
removes max-width cap for lyrics to breathe.
cc
Remove fixed px caps on artwork — now scales purely with viewport
(42vw/60vh). Switch lyrics font from condensed Oswald to Montserrat
for better legibility. Hide scrollbar in immersive lyrics (scrolling
still works). Add padding to artwork column for breathing room.
cc
Artwork scales with viewport (min of 500px/38vw/58vh) instead of fixed
380px cap. Lyrics use responsive clamp() sizing (up to 34px inactive,
42px active) with better line-height. Reduced padding/gaps throughout
split mode for less dead space. Tighter mask gradient on lyrics panel.
cc
The cargo eclass requires pre-generated offline crate sources which
the CI workflow never set up. Revert to the working approach: inherit
xdg only, RESTRICT network-sandbox, direct npm ci + cargo build.
cc
Off by default. Toggle in Audio settings controls whether QBZ
silently downgrades quality when the requested tier is unavailable.
i18n keys added to all 5 locales.
cc
Off by default. When disabled, if the requested quality is unavailable
the download/stream fails instead of silently downgrading. Persisted
to audio_settings DB. Applied to offline cache downloads.
cc
When a track is cached at lower quality than requested (e.g. CD quality
cached during a Qobuz outage, but user wants Hi-Res), the cache hit is
now detected via FLAC header inspection (sample_rate + bit_depth).
If quality is insufficient, QBZ attempts a fresh download at the
requested quality. If the download fails (network error), the cached
lower-quality version is played instead of failing entirely.
cc
Bootstrap no longer attempts email/password login — only OAuth token
restore. v2_manual_login and v2_auto_login now return immediately with
deprecation errors. Users with old saved credentials will see the
login screen instead of hitting Qobuz's deprecated endpoint.
cc
Secondary link-style option below the main OAuth button. Opens the
user's default browser (Firefox, Chrome, etc.) instead of the Tauri
WebView. Useful for password managers and saved sessions.
i18n keys added to all 5 locales.
cc
Refactor: shared finalize_oauth_login() handles code exchange, CoreBridge
injection, session activation, and token persistence. Frontend-agnostic,
reusable by GUI, TUI, and headless clients.
New command: v2_start_system_browser_oauth spawns a local axum listener,
opens the user's default browser for Qobuz OAuth, and captures the
redirect code automatically.
cc
Qobuz no longer accepts basic auth. Remove email/password fields,
manual login handler, and "Remember me" checkbox from LoginView.
OAuth browser login is now the sole authentication method.
cc
setCurrentTrack during session restore was missing albumId and artistId,
causing Go to Album / Go to Artist clicks to silently fail until a new
track was played. Also restore isLocal, source, and parental_warning for
full parity with the normal playback path.
Closes#252
cc
handleBulkMakeOffline was calling v2_cache_favorite_track which only
saves metadata to the favorites cache. Replaced with
cacheTracksForOfflineBatch which triggers the real offline download.
cc
Three fixes:
- resolve_artist now prefers exact name matches over highest score,
fixing "The Warning" resolving to "Fates Warning" (score=100 but
wrong name vs score=97 exact match)
- resolve_area_country now returns ISO country code alongside name,
fixing flag display (was showing US flag for German artists)
- country_code updated in location metadata for correct flag rendering
cc
Add resolve_area_country() that walks the MB area hierarchy up to
the country level (e.g., Frankfurt -> Hessen -> Germany).
Use it in v2_musicbrainz_get_artist_metadata to get the real birth
country instead of MB's "country" field (which is where the artist
is active, not where they were born).
Hans Zimmer now shows "Frankfurt am Main, Germany" and clicking
the location shows German composers, not American ones.
cc
MB's country field is where the artist is active, not where they
were born. When begin_area exists (city-level), display only the
city name to avoid incorrect attribution (e.g., Hans Zimmer born
in Frankfurt showing "United States").
cc
Two bugs in location_discovery.rs:
- Display format was "Country, City" instead of "City, Country"
- Country was derived from area (where artist is active) instead
of country code (where artist was born/formed)
Hans Zimmer now shows "Frankfurt am Main, Germany" instead of
"United States, Frankfurt am Main"
Closes#235
cc
- Send track metadata to MPRIS on session restore so playerctl
shows title/artist/album immediately after app restart
- Persist playback context (album, playlist, etc.) to localStorage
on session save and restore it on startup
- Add buffer_progress to frontend PlaybackEvent type
Closes#240
cc
Show a subtle buffer progress bar in the seekbar when streaming
tracks (before they are fully cached). The bar disappears once
the track is fully buffered.
Backend: expose buffer_progress from BufferedMediaSource through
SharedState -> PlaybackEvent -> playerStore.
Frontend: render .seekbar-buffer bar behind the playback fill.
cc
F11 now toggles window fullscreen from anywhere in the app, not just
inside the immersive player. ESC exits window fullscreen when no UI
overlays are open.
Closes#202
cc
The metainfo.xml had a fake full hash (cff32e5d47f3b44568af9...) that
returned 404 on raw.githubusercontent.com. Replace with the real commit
hash (cff32e5da7) where screenshots exist.
Normalize notification artwork to square PNG (max 512x512, <=4 MiB) before sending Icon::Bytes via ashpd portal, so album art renders reliably without legacy fallback.\n\ncx
The XDG portal notification spec's icon field doesn't support inline
album artwork the way freedesktop Notifications does with image-path.
Dual-path approach:
- Native (non-Flatpak): use org.freedesktop.Notifications D-Bus
interface directly with image-path hint for album artwork
- Flatpak (sandboxed): use ashpd portal (Flathub requirement)
Uses ashpd::is_sandboxed() for detection and ashpd's re-exported
zbus for the direct D-Bus call, avoiding extra dependencies.
The XDG portal was not displaying album artwork when passed as a
file:// URI (showed KDE default icon instead). Send artwork as
Icon::Bytes so the portal receives the image data directly.
Replace direct D-Bus notifications (notify-rust) with XDG Desktop
Portal notifications (ashpd) for Flathub compliance. Portal
notifications use the app's .desktop file identity and don't require
--talk-name=org.freedesktop.Notifications permission.
- Replace notify-rust dep with ashpd (linux-only, tokio+notification)
- Use NotificationProxy for portal-based notifications
- Use stable ID "track-now-playing" to replace instead of stacking
- Convert artwork path to file:// URI via ashpd::Uri
- Graceful fallback when portal is unavailable
Replace binary blur toggle with a 3-option dropdown. Full mode uses
the existing WebGL2 shader, Lite mode renders a pre-blurred static
image animated with CSS transforms (compositor-driven, immune to
Wayland frame callback stutter), and Off mode shows a solid color.
Includes config migration from disableBlurBackground to backgroundMode,
updated composition profiles, and i18n for all 4 locales.
Closes#127
Detect when QBZ runs inside a Snap sandbox (via SNAP env var) and show
a one-time welcome modal with required plug connection commands. Adds a
Snap Sandbox section in Settings with copyable commands, mirroring the
existing Flatpak pattern.
Backend: snap.rs module, DB table snap_welcome_shown, V2 commands.
Frontend: updatesService, +page.svelte modal queue, SettingsView section.