Backend changes:
- Add audio_diagnostics module to read actual hardware sample rate from /proc/asound
- New command get_hardware_audio_status() parses hw_params for real-time monitoring
- Add glob dependency for file pattern matching
Frontend (AudioOutputBadges):
- Implement 3-state DAC badge system:
- Green: DAC passthrough working correctly (rates match)
- Yellow: DAC passthrough ON but resampling detected (warning)
- Gray: DAC passthrough OFF (intentionally disabled)
- Dynamic tooltips showing actual hardware sample rate
- Compare file sample rate vs hardware sample rate
- Increase tooltip z-index to 9999 to prevent occlusion issues
- Add pointer-events: none to tooltip to prevent interaction issues
Frontend (SettingsView):
- Replace hardcoded "192 kHz" with dynamic hardware sample rate
- Load and display actual hardware audio status
- Show format details (e.g., S32_LE) when available
- Display "No active playback" when no audio is playing
- Add muted styling for inactive state
Frontend (QualityBadge):
- Add native tooltip showing tier and format
- Add cursor: help to indicate interactivity
i18n:
- Add "noActivePlayback" translations (EN/ES)
This allows users to verify bit-perfect playback is working and detect
resampling issues that would compromise audio quality.
- Update LASTFM_API_URL to point to proxy endpoint
- Remove MD5 signature generation (proxy handles it)
- Remove api_key and api_secret from LastFmClient (proxy manages credentials)
- Simplify client to only handle session_key
- Update get_token() to return (token, authUrl) tuple from proxy
- Update all methods (get_session, scrobble, update_now_playing) to use proxy
- Add User-Agent header 'QBZ/1.0.0' for proxy validation
- Update bootstrap.ts to skip credential restoration (proxy handles it)
- Maintain backward compatibility with set_credentials as no-op
- Remove DEFAULT_API_SECRET constant and get_api_secret() function
- Update Default impl to require user-provided secret
- Update has_embedded_credentials() to only check API key
- API secret will be handled by Cloudflare Workers proxy
Add auto-disable logic to prevent incompatible settings from being enabled simultaneously:
Frontend (SettingsView.svelte):
- DAC Passthrough ON → auto-disables Gapless Playback and Crossfade
- Gapless Playback ON → auto-disables DAC Passthrough
- Crossfade > 0 → auto-disables DAC Passthrough
- Added handleGaplessPlaybackChange() and handleCrossfadeChange() handlers
- Modified handleDacPassthroughChange() to disable incompatible settings
Backend (player/mod.rs):
- Only recreate OutputStream on format change when DAC passthrough is enabled
- When DAC passthrough is OFF: reuse existing stream for gapless transitions
- When DAC passthrough is ON: recreate stream for bit-perfect playback
- Added detailed logging for stream recreation decisions
This fixes the issue where Gapless Playback was enabled but gaps still occurred
due to DAC passthrough forcing stream recreation on sample rate changes.
The previous implementation tried to map ALSA device names (from cpal)
to PipeWire descriptions, but the naming schemes are completely different:
- cpal returns names like "plughw:1,0"
- pactl returns sink names like "alsa_output.usb-Cambridge_Audio_..."
This fix changes the approach:
1. Use PipeWire sinks directly for the device dropdown (already filtered
to real outputs and have friendly descriptions like "DacMagic Plus")
2. Store the PipeWire sink name when user selects a device
3. Set the PipeWire default sink using pactl when device is selected
4. Use rodio's default device (which follows PipeWire's default)
This ensures users see 4-5 meaningful devices with friendly names
instead of 30+ raw ALSA device names.
Backend changes:
- Add set_pipewire_default_sink command (pactl set-default-sink)
- Register new command in lib.rs
Frontend changes:
- Use pipewireSinks directly for device dropdown
- Display sink description (friendly name)
- Store sink name (for pactl)
- Call set_pipewire_default_sink when device changes
- Remove unused AudioDevice interface and getDevicePrettyName import
The play_data function was calling decode_with_symphonia which decoded
the entire audio file (271MB+) just to extract sample_rate and channels.
This caused 30-35 second delays between download complete and playback.
New extract_audio_metadata function:
- For FLAC: uses rodio decoder (reads headers only)
- For isomp4: uses symphonia probe to read codec_params without decoding
This restores the fast playback we had before.
Improvements to the Wayland compatibility fix:
Detection:
- Add NVIDIA GPU detection via /proc and kernel modules
- Apply DMA-BUF workaround only on NVIDIA systems
- Preserve full acceleration on Intel/AMD GPUs
User Overrides:
- QBZ_FORCE_DMABUF=1: Force-enable DMA-BUF (bypass workaround)
- QBZ_DISABLE_DMABUF=1: Force-disable DMA-BUF
- QBZ_FORCE_X11=1: Force X11 backend on Wayland
Diagnostics:
- Clear logging of display server type
- NVIDIA detection status
- Applied workarounds and reasons
- User override notifications
This makes the fix selective and transparent while protecting
NVIDIA + Wayland users from fatal protocol errors.
Related: #6
cc
This commit implements proper sample rate passthrough for high-quality
audio playback, addressing issue #10 where DAC sample rates were locked
at 44.1kHz regardless of file quality.
Core Changes:
- Modified Player to accept and store AudioSettings (exclusive mode, DAC passthrough)
- Updated AudioCommand::Play to include sample_rate and channels metadata
- Created create_output_stream_with_config() to build custom StreamConfig
- Added sample rate tracking to detect changes between tracks
- Modified Play handler to recreate OutputStream when sample rate changes
- Updated play_data() to extract audio metadata before playback
- Modified lib.rs to read AudioSettings on startup and pass to Player
Device Detection Improvements (Issue #4):
- Added display_name and device_type fields to AudioDevice struct
- Implemented get_pipewire_device_descriptions() to extract friendly names
- Updated get_audio_devices() to map technical names to user-friendly names
- Added device type detection (USB, HDMI, PCI, Bluetooth)
Technical Details:
- Audio files are decoded twice: once for metadata, once for playback
- Stream is recreated only when sample rate or channel count changes
- Falls back to default device config if custom config fails
- Supports both PipeWire and PulseAudio device naming via pactl
- Logs sample rate transitions and OutputStream configuration
Files Modified:
- src-tauri/src/player/mod.rs: Core playback engine changes
- src-tauri/src/lib.rs: Initialize Player with AudioSettings
- src-tauri/src/commands/playback.rs: Device enumeration improvements
Tested: Compiles successfully on Linux with Rust 1.92
L1 (Memory): 300MB for ~3-4 Hi-Res tracks, instant access
L2 (Disk): 500MB playback cache at ~/.cache/qbz/playback/
Flow:
- When track evicted from memory, saved to disk cache
- When loading: check memory → disk → download cache → network
- Tracks promoted back to memory when accessed from disk
This reduces re-downloads when navigating back to recently played
tracks in mixed playlists.
- Stop playback immediately when starting a Qobuz track to prevent
previous track from continuing during download
- Show buffering toast while downloading Qobuz audio
- Skip buffering UI for local tracks since they load instantly
- Add is_local field to QueueTrack for prefetch optimization
- Update prefetch to skip local tracks (no network needed)
- Update all queue track builders to include is_local flag
Wire onPlaylistCountUpdate from PlaylistDetailView to Sidebar's
updatePlaylistCounts method in +page.svelte. This ensures the
sidebar displays accurate track counts from the database instead
of calculated values that could drift out of sync.
- Add PlaylistLocalTrack struct with playlist_position field
- Add playlist_get_local_tracks_with_position command
- Update PlaylistDetailView to merge tracks by position instead of
appending local tracks at the end
- Update PlaylistModal to calculate correct position when adding local
tracks (total count = qobuz + existing local)
- Local tracks now appear in the order they were added, interleaved
with Qobuz tracks
- Add playlist_get_all_local_track_counts command to get local track
counts for all playlists efficiently
- PlaylistDetailView: show combined track count and duration in header,
style local count with muted color, remove redundant count below
- Sidebar: load and display local track counts in tooltips
- PlaylistModal: show combined counts when selecting playlist to add to
- PlaylistManagerView: show combined counts in both grid and list views
- TrackRow: move local indicator to download column position, add
placeholder for favorite column to maintain alignment, update tooltip
- Remove local tracks notice banner (info now in tooltip)
cc
- clear_all_tracks now only deletes local library tracks
- Qobuz downloads (source = 'qobuz_download') are preserved
- Local library management is now completely separate from Qobuz downloads
- Fix offline settings database migrations to run individually
- Add network detection refresh when loading folder list
- Add network detection during folder scan
- Show folder alias with path tooltip in folder list
- Add folder path change capability in FolderSettingsModal
- Add network content visibility toggle in offline settings
- Add i18n strings for new features (en/es)
- Disable import playlists button when offline (shows disabled state)
- Add Cast, immediate scrobbling, and accumulated scrobbling toggles
- These settings only appear when manual offline mode is enabled
- Add time limit note for accumulated scrobbling (Last.fm 2 week limit)
- Persist new settings in SQLite with proper migrations
- Add i18n strings for all new settings (en/es)
- Add batch check command for track local status in Rust
- Show unavailable tracks with grayed-out visual state in PlaylistDetailView
- Disable interactions for unavailable tracks (click, menu, play)
- Add WifiOff indicator for unavailable tracks
- Update QueuePanel to show unavailable track styling
- Implement auto-skip for unavailable tracks during playback
- Add offline mode sync to queue store
- Add offline-specific filter states to PlaylistManager:
- Available Offline
- Partially Available
- Not Available Offline (view-only)
- Disable edit/drag for unavailable playlists in PlaylistManager
- Add i18n strings for offline states (en/es)
The login state check was causing race conditions on app startup because
offline status was being checked before auto_login completed. Since the
frontend already handles showing LoginView when not logged in, the
NotLoggedIn check in offline detection was redundant and problematic.
Now offline detection only checks:
1. Manual offline mode (from settings)
2. Network connectivity (via HTTP HEAD to qobuz.com)
- Change OfflinePlaceholder icon to use offline.svg instead of Power icon
- Add flex-shrink protection to Toggle component to prevent collapsing
- Add network verification when disabling offline mode:
- Check network connectivity before switching to online
- Show semi-transparent overlay during check
- Revert to offline and show toast if no internet available
- Add check_network Tauri command to verify connectivity
- Add i18n strings for noNetworkToast and checkingNetwork (en/es)
Research findings:
- Last.fm API supports batch scrobbling (up to 50 tracks)
- Timestamps can be backdated up to 14 days
Implementation:
- Add scrobble_queue table to offline database
- Add queue_scrobble, get_queued_scrobbles, mark_scrobbles_sent commands
- Queue scrobbles when offline instead of skipping
- Flush queue automatically when transitioning to online
- Skip scrobbles older than 14 days (Last.fm limit)
- Cleanup sent scrobbles after 7 days
- Add LocalContentStatus enum (unknown, no, some_local, all_local)
- Add has_local_content column to playlist_settings with migration
- Implement local track matching by Qobuz ID and by metadata (title+artist+album)
- Add commands: playlist_analyze_local_content, playlist_get_local_content_status,
playlist_track_is_local, playlist_get_local_track_id, playlist_get_offline_available
- Add database functions for local content filtering and status updates
cc
- Create offline module with OfflineReason enum and OfflineStatus struct
- Add OfflineSettings for manual mode and partial playlist preferences
- Implement network connectivity check via HEAD request to qobuz.com
- Add SQLite persistence for offline settings
- Register Tauri commands: get_offline_status, get_offline_settings,
set_manual_offline, set_show_partial_playlists
- Add event emission for offline status changes
cc
- Albums: 30 results with view more card if total > 30
- Artists: 12 results with carousel navigation
- Artists carousel: same pagination logic as albums
- View more cards: 160x220px dashed border, navigate to tab
- Artists gap: reduced to 16px (same as albums)
- Separate page tracking for albums and artists
cp
- Albums carousel: display flex (one row only)
- Backend: 20 albums (was 16)
- Artist cards: all 160x220px (most popular and grid)
- Carousel buttons work with multiple pages
cp
- Artist cards: fixed spacing with 16px gap, 120x120 circular images
- Layout: Albums and Tracks now in separate full-width rows
- Carousel: Changed to single row (was 2 rows)
- Crown icon: Shimmer gradient effect (no glow/drop-shadow)
- Backend already at 16 albums
cp
- Add search_all command to Rust backend (parallel search execution)
- Create SearchAllResults type for unified search results
- Add 'all' tab as new default in search view
- Implement unified results layout:
- Most Popular section (top artist or album)
- Artists section (up to 5 artist cards)
- Albums carousel (horizontal scroll, 10 albums)
- Tracks section (top 8 tracks)
- Add View all links to navigate to individual tabs
- Update search state store to include allResults
- Maintain existing tab functionality (albums, tracks, artists)
- Add responsive grid layout for unified view
cp
- Playlist artwork now copied to artwork cache directory
- Artist custom images now copied to artwork cache directory
- Prevents issues when source files are moved or deleted
- Uses MD5 hash for artist filenames to avoid special characters
cp
Artist biographies were being cached per locale but not refreshed
when user changed language settings. Now automatically clears artist
cache when language is changed to force refetch in new language.
cp
- Add loadCachedArtistImages() to load from database
- Add fetchMissingArtistImages() with Qobuz + Discogs fallback
- Add discogs_search_artist command for artist lookups
- Artist images now persist between sessions
- Discogs used as fallback when Qobuz has no image
cp
- Modify cached_artists table to include locale in primary key
- Update get_artist and set_artist methods to consider locale
- Add get_locale() method to QobuzClient for cache key generation
- Ensure artist descriptions respect language settings
cp
- Add locale field to QobuzClient for API language preference
- Pass lang parameter to Qobuz artist detail endpoints
- Create set_api_locale command to update client locale
- Sync API locale with UI i18n settings automatically
- Render artist biography HTML tags properly using @html
- Fix syntax error in FavoritesView (missing comma)
cp
- Increase initial artist albums fetch from 500 to 1000
- Increase load more albums batch from 50 to 200
- Increase top tracks display from 5 to 20
- Prefer full biography content over summary
- Increase bio truncate threshold from 500 to 800 characters
cp
- Increase artist detail album fetch from 100 to 500
- Increase bio truncation from 300 to 500 chars
- Add Others category for greatest hits/anthologies
- Add configurable limits for Home sections
- Update HomeSettingsModal with limits grid UI
cp
- Add backend command to check if album is fully downloaded
- Add frontend functions to check album download status
- Pass download handlers to HomeView
- Update HomeView AlbumCards with download status props
cp
- Add submenu to album download option when album is fully downloaded
- New options: Open containing folder and Re-download album
- Backend command to open album folder in file manager
- Helper function to check if album is fully downloaded
- Works in both AlbumCard overlay and AlbumDetailView menus
cp
Changes all application data paths from qbz-nix to qbz:
- Cache directory: ~/.cache/qbz-nix -> ~/.cache/qbz
- Config directory: ~/.config/qbz-nix -> ~/.config/qbz
- Data directory: ~/.local/share/qbz-nix -> ~/.local/share/qbz
This fixes path inconsistencies between development and production builds
and resolves asset protocol permission issues with downloaded album artwork.
cp
- Remove existing qobuz_track_id entry before inserting new one
- Ensures clean database state when re-downloading same track
- Prevents artwork and metadata confusion from duplicates
cp
- Changed download quality from HiRes to UltraHiRes to support 24-bit/192kHz
- Updated playback streaming to use UltraHiRes for highest quality
- Added debug logging for artwork path storage and retrieval
- Quality fallback will still work for lower subscription tiers
cp
Downloaded albums now properly display cover art in local library by
passing artwork_path when inserting tracks to database. Cover art is
saved as cover.jpg in album directory during post-processing.
cp