Fixed missing type annotations in row.get() calls for sample_rate.
Without explicit type annotation, Rust was inferring the wrong type
causing 'Invalid column type Real' errors when reading from database.
Added ::<_, f64> type annotation to all three locations:
- row_to_track (line 1168)
- get_playlist_local_tracks (line 1772)
- get_playlist_local_tracks_with_qobuz (line 1821)
This is the critical fix for the database migration error.
Changed LocalAlbum.sample_rate from u32 to f64 to match database REAL type.
Fixed database queries to read sample_rate as f64 instead of u32.
Removed incorrect i64 casts when inserting sample_rate values.
This fixes the 'Invalid column type Real' error that prevented the app
from loading after the database migration.
Drop any existing local_tracks_new table before starting migration
to handle cases where a previous migration attempt failed mid-execution.
This prevents 'table already exists' errors when retrying the migration.
Use COALESCE in migration SELECT to safely handle NULL file_size_bytes
values that may exist in older database versions. This prevents the
'NOT NULL constraint failed' error when migrating from INTEGER to REAL
sample_rate type.
The migration now uses COALESCE(file_size_bytes, 0) to ensure all
rows have a valid file size value, defaulting to 0 for missing data.
Fixes critical migration failure that prevented app startup for users
with existing databases.
1. QUEUE SHUFFLE FIX (CRITICAL):
- When shuffle enabled and set_queue called, starting track was placed randomly in shuffle_order
- This caused queue UI to show only tracks AFTER that random position
- Example: 11 tracks, start at index 5, but index 5 was at position 7 in shuffle → only 3 tracks visible
- FIX: When shuffle enabled, swap starting track to position 0 in shuffle_order
- Now all tracks appear in queue UI as expected
2. LOCAL LIBRARY CONTEXT FIX:
- handleLocalTrackPlay was clearing context immediately after LocalLibraryView set it
- FIX: Removed clearPlaybackContext() call - LocalLibraryView handles context correctly
- Context icon should now display for Local Library playback
3. SAMPLE RATE CRASH FIX:
- show_track_notification expected u32 but received f64 (44.1, 88.2, etc.)
- Error: "invalid type: floating point 44.1, expected u32"
- FIX: Changed sample_rate parameter from u32 to f64 in notification command and format_quality
- Bonus: Format shows "44.1" for fractional rates, "48" for whole numbers
Files modified:
- src-tauri/src/queue/mod.rs - Fixed set_queue to move start_index to front of shuffle_order
- src/routes/+page.svelte - Removed clearPlaybackContext from handleLocalTrackPlay
- src-tauri/src/commands/notification.rs - Changed sample_rate to f64 with proper formatting
Multiple critical fixes for context tracking:
1. Add 'local_library' to backend context type parser
- Was rejecting 'local_library' causing "Invalid context type" error
- Now properly recognized in set_playback_context command
2. Fix shuffle to not always start with first track
- Removed logic that moved current track to front of shuffle
- Now finds current track position in shuffled order instead
- Shuffle is truly random, not biased to original order
3. Add missing await to all setPlaybackContext calls
- PlaylistDetailView: await context creation
- FavoritesView: make setFavoritesContext async + await calls
- ArtistDetailView: await context creation
- HomeView: await context creation
- SearchView: await + use home_list type (search type doesn't exist)
This fixes intermittent context tracking (race condition)
Fixes: shuffle starting with first song, Local Library context error,
intermittent playlist/favorites/artist/home/search context tracking
cc
Add proper context tracking for Local Library playback so the stack
icon shows correctly when playing from Local Library.
Changes:
- Add 'local_library' to ContextType enum (frontend and backend)
- Add 'Local Library' display label in context info
- Import setPlaybackContext in LocalLibraryView
- Call setPlaybackContext when playing tracks from Local Library
- For album view: context is the album with album title
- For tracks view: context is "Local Tracks"
- Track IDs and position are properly set for context navigation
Fixes issue where Local Library playback showed stale Qobuz context.
cc
Previous implementation had a critical bug in the shuffle algorithm
that used the same seed value for all iterations, resulting in
weak/predictable shuffling.
Changes:
- Add rand crate dependency for proper PRNG
- Fix Fisher-Yates to use unique random value per iteration
- Use StdRng with timestamp-based seed for reproducible randomness
- Each shuffle iteration now gets a new random number from the RNG
This fixes shuffle working inconsistently across different contexts
(albums, playlists, continuous play).
cc
Two critical fixes for Flatpak sandbox environment:
1. PipeWire detection now uses pactl instead of pw-cli
- pw-cli is not available in GNOME Flatpak runtime
- pactl works for both PipeWire and PulseAudio (compatibility layer)
- Fixes missing PipeWire backend in Settings
2. App ID migration now works in Flatpak sandboxes
- Detects Flatpak environment and uses absolute paths
- Accesses old sandbox (com.blitzkriegfc.qbz) via HOME directory
- Added read-only permission to old App ID in manifest
- Properly migrates config/data/cache from old to new sandbox
Features:
- Flatpak runtime detection (/.flatpak-info check)
- Automatic migration of user data from old App ID to new ID
- Config: ~/.config/com.blitzkriegfc.qbz → ~/.config/qbz
- Data: ~/.local/share/com.blitzkriegfc.qbz → ~/.local/share/qbz
- Cache: ~/.cache/com.blitzkriegfc.qbz → ~/.cache/qbz
- Migration runs on startup before any other initialization
- Old directories are preserved (manual cleanup required)
- Tauri commands for frontend integration:
- is_running_in_flatpak()
- get_flatpak_help_text()
Migration is non-destructive and idempotent.
Changes:
- Renamed Flatpak manifest, desktop file, and AppStream metainfo
- Updated App ID in all metadata files
- Fixed MPRIS name: qbz_nix → qbz
- Added NAS support permissions: /mnt, /media, /run/media
- Updated CI workflow to use new filenames
- Updated tauri.conf.json bundle identifier
BREAKING CHANGE: Users will need to migrate data from old App ID directories
Data migration will be implemented in next commit
The 1s polling of get_hardware_audio_status was logging every second:
"Hardware audio status: 192000Hz, format: Some("S32_LE")"
This is useful for debugging but too noisy for normal usage.
Change:
- log::info! → log::debug! (line 52)
Result:
- Console stays clean during playback
- Log still available with RUST_LOG=debug for debugging
- Polling continues working, just silently
This fixes the 2-5s delay when rapidly changing tracks. The drain() was
blocking for hundreds of milliseconds waiting for the PCM buffer to empty,
even when the user was skipping tracks.
Root cause (playback_engine.rs:136):
- drain() was called ALWAYS after playback loop ended
- drain() blocks until PCM buffer is completely empty (~125-500ms)
- When user skips tracks, we don't need elegant buffer drain
- This caused 'need to wait 2-5s' or 'double click next' behavior
Solution:
- Added 'natural_end' flag to track WHY playback stopped
- natural_end=true: Song finished normally → DO drain (elegant end)
- natural_end=false: User skipped/paused → SKIP drain (instant response)
Benefits:
- Track changes now instant (no 2-5s wait)
- Songs that finish naturally still drain properly (no clicks/pops)
- Maintains audio quality while improving responsiveness
Testing:
- Rapid track skipping should be instant
- Songs that play to end should have smooth transition
- No audio artifacts (clicks/pops) in either case
This addresses the 'slow playback' issue on first large sample rate transition
(e.g., 88.2kHz → 44.1kHz). The hardware needs extra time to stabilize when
changing sample rates significantly, especially on cold start.
Root cause:
- First transition 88.2→44.1 kHz: song started slowly
- Subsequent identical transitions: clean
- Affects both ALSA Direct and PipeWire DAC Passthrough
- Hardware 'warm-up' time needed for large rate changes
Fix:
- Increased delay from 50ms to 150ms (still imperceptible to user)
- Gives DAC hardware more time to stabilize clock after rate change
- Extra time especially critical for downward transitions (88.2→44.1)
Testing needed:
- Verify first 88.2→44.1 transition is now clean
- Ensure 150ms delay doesn't create noticeable gap
- Check all other transitions still work
The badge was always showing 'Default' regardless of the selected device because
thread_state.set_current_device() was only being called in the legacy CPAL path,
not in the backend system path (PipeWire DAC, ALSA Direct).
Root cause:
- Lines 801-805: Backend system creates stream but never sets current_device
- Lines 847, 883: Only legacy CPAL path was setting device name from CPAL device.name()
- UI badge calls get_audio_output_status() which returns player.state.current_device()
- current_device was None/outdated when using backend system
Fix:
- After successful stream creation (line 897), read output_device from settings
- Set thread_state.current_device with the device name from settings
- Fallback to 'Default' string if output_device is None
- Now badge shows: 'hw:4,0', 'DacMagic Plus Analog Stereo', etc.
Testing:
- ALSA Direct: Should show 'hw:4,0' or configured device
- PipeWire DAC: Should show 'DacMagic Plus Analog Stereo' or selected sink
- System Default: Should show 'Default'
This is an integral fix that resolves the multi-second gap when changing
sample rates, without breaking bit-perfect modes (DAC Passthrough, ALSA Direct).
Root causes identified:
1. Missing ALSA Direct format change detection - stream wasn't recreated
2. Unnecessary CPAL device enumeration before backend system - added latency
3. Decoder started before stream fully initialized - seekbar advanced without audio
Changes made:
1. Added ALSA Direct to format change detection (line 773-775)
- Stream now recreates on sample rate change for ALSA Direct
- Maintains bit-perfect playback like DAC Passthrough
2. Restructured device initialization to skip CPAL enumeration (lines 798-893)
- Backend system (PipeWire DAC, ALSA Direct) checked FIRST
- CPAL enumeration only used as fallback for legacy path
- Eliminates "Device 'hw:4,0' not found" warning for ALSA Direct
3. Added 50ms delay after stream creation (line 904-906)
- Ensures stream is fully initialized before decoder starts
- Prevents seekbar advancing without audio output
- Small enough not to be noticeable, large enough to prevent gap
Testing needed:
- ALSA Direct: 192→96→192→44.1→48→88.2 kHz transitions
- PipeWire + DAC Passthrough: Same sample rate transitions
- System Default: Verify gapless still works
- Verify bit-perfect modes still work (no resampling)
- Add alsa_hardware_volume to AudioSettings interface
- Add Tauri command set_audio_alsa_hardware_volume
- Add checkbox in Settings (visible only for ALSA Direct + Hw plugin)
- Checkbox disabled by default for maximum compatibility
- Clear tooltip explaining experimental nature and risks
- Add hardware_volume parameter to PlaybackEngine::new_alsa_direct()
- Update all three call sites in player.rs to pass alsa_hardware_volume setting
- Implement conditional volume control: hardware mixer or DAC/amplifier
- Add device_id() getter to AlsaDirectStream for mixer access
- Hardware volume disabled by default for maximum compatibility
Added set_hardware_volume() to AlsaDirectStream:
- Tries common mixer control names (Master, PCM, Speaker, Headphone, Digital)
- Returns error if DAC doesn't support mixer (doesn't break playback)
- Stores device_id in struct for mixer access
This enables bit-perfect volume control via DAC hardware when supported.
Falls back gracefully if not supported.
PROBLEM: App crashed on startup with existing databases:
'table audio_settings has no column named alsa_hardware_volume'
CAUSE: ALTER TABLE with 'NOT NULL' constraint fails on SQLite migrations.
FIX:
- Removed NOT NULL from ALTER TABLE statement
- Handle NULL values with unwrap_or(0) in get_settings()
- Defaults to false (disabled) for old databases
Now works with both new and existing databases.
Preparation for hybrid volume control (B+C option):
- New field: alsa_hardware_volume: bool (default: false)
- Database schema updated with migration support
- Setter/getter methods added
When enabled, will attempt to control DAC volume via ALSA mixer.
When disabled (default), in-app volume control is disabled for maximum
compatibility (user controls volume on DAC/amplifier).
PROBLEM: Hardcoding S32_LE only worked for Cambridge Audio DAC, not generic.
SOLUTION: Auto-detect best format at runtime:
- Try formats in priority order: FloatLE > S32LE > S24LE > S16LE
- Store detected format in AlsaDirectStream struct
- write() auto-converts from i16 based on detected format:
* FloatLE: i16 -> f32 (normalize to -1.0..1.0)
* S32LE: i16 -> i32 (bit-perfect: shift left 16 bits)
* S16LE: i16 -> i16 (no conversion)
Now works with any DAC regardless of supported formats.
PROBLEM: Cambridge Audio DAC only supports S32_LE format, not FloatLE.
Previous code failed with 'Invalid argument (22)' when trying to set FloatLE.
SOLUTION:
- alsa_direct.rs: Changed format from FloatLE to S32_LE
- alsa_direct.rs: Changed write() to accept i32 samples instead of f32
- playback_engine.rs: Convert i16 to i32 using bit-perfect left shift:
i32 = (i16 as i32) << 16
This maintains full bit-perfect precision while using hardware-supported format.
PROBLEM: Original code tried to collect() entire audio source into memory
before playback, causing 600+ MB allocations for 192kHz tracks and silent
failures.
SOLUTION: Stream samples in 8192-frame chunks:
- Read chunk from source iterator
- Convert i16 to f32
- Write to ALSA immediately
- Repeat until end of stream
This reduces memory usage from 600+ MB to ~300 KB per chunk and fixes
the no-audio issue.
- Replace Sink with PlaybackEngine abstraction throughout player
- Update StreamType to use Arc<AlsaDirectStream> for sharing
- Adapt all player commands (Play, Pause, Resume, Seek, Stop, SetVolume, ReinitDevice)
- Add as_any() trait method for backend downcasting
- Fix all type mismatches and compilation errors
- Compilation successful with 0 errors, 30 warnings
- Detect missing icon_background column in favorites_preferences table
- Add ALTER TABLE migration to preserve existing user data
- Apply migration in both FavoritesPreferencesStore::new() and create_table()
cx
- Add alsa 0.9 as Linux-only dependency
- Will replace CPAL-based ALSA backend with direct alsa-rs implementation
- Required to properly access hw:X,Y devices for bit-perfect playback
- Create favorites_preferences.rs with FavoritesPreferences struct
- Add database table (custom_icon_path, custom_icon_preset, tab_order)
- Implement State wrapper and Tauri commands
- Register state and commands in lib.rs
- Default preset: heart, default order: tracks/albums/artists/playlists
cc
Problem:
- Users updating from previous versions would crash on startup
- Table created with new column caused "no column named show_context_icon" error
- Existing users had old schema without the column
Solution:
- Step 1: Create table with old schema (backwards compatible)
- Step 2: Check if show_context_icon column exists using pragma_table_info
- Step 3: Add column ONLY if it doesn't exist (safe migration)
- Step 4: Insert default row
This ensures:
✅ New installs work (table created fresh)
✅ Existing users migrate seamlessly (column added, settings preserved)
✅ No data loss
✅ Idempotent (can run multiple times safely)
Tested scenarios:
- Fresh install (no DB) → creates table, adds column, inserts defaults
Result: id=1, autoplay_mode='continue', show_context_icon=1
- Existing DB without column → adds column, preserves user settings
Result: id=1, autoplay_mode='track_only' (preserved), show_context_icon=1
- Existing DB with column → no changes (idempotent)
Migration logs added for debugging.
cp
- Add PlaybackContext module with ContextManager
- Context tracks semantic origin (album, playlist, artist top, etc.)
- Separate from queue - defines what 'next' means
- Backend: Rust struct with track IDs and position tracking
- Frontend: Svelte store with reactive subscriptions
- Tauri commands for context CRUD operations
- Context is nullable (single track playback has no context)
cp
Critical Fix:
- CPAL only enumerates named ALSA devices (front:, sysdefault:, etc.)
- CPAL does NOT enumerate hw:X,Y or plughw:X,Y devices
- These must be created manually from aplay -l card list
Solution:
- Parse aplay -l to get card numbers and descriptions
- Manually create hw:X,0 and plughw:X,0 for each card
- Assume max_sample_rate: 384000 (devices are capable)
- Description: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"
For user's system (based on aplay -l):
- hw:0,0 → CS201 (Direct Hardware - Bit-perfect)
- hw:2,0 → HDA NVidia (Direct Hardware - Bit-perfect)
- hw:3,0 → sof-hda-dsp (Direct Hardware - Bit-perfect)
- hw:4,0 → Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect) ← USER NEEDS THIS
- Plus plughw: variants for each
These devices will now appear in the dropdown and can be selected for
true bit-perfect playback without resampling.
Critical Fix - Use CPAL Device Names:
- CPAL enumerates devices → these IDs actually work for playback
- aplay -L provides friendly descriptions → update CPAL device descriptions
- Stop creating hw:X,Y devices that CPAL doesn't recognize
- Fixes: "Device not found → fallback to default → audio from laptop"
Flow:
1. enumerate_via_cpal() returns devices CPAL can actually use
2. enumerate_via_aplay() calls #1, then parses aplay -L for descriptions
3. Updates device.description with friendly names from aplay -L
4. Frontend shows: "Cambridge Audio USB Audio 2.0" not "front:CARD=C20,DEV=0"
Loading Overlay:
- Full-screen semi-transparent overlay during device enumeration
- Animated spinner (Loader2 icon)
- "Loading audio devices... / Parsing hardware information"
- Prevents user thinking app is frozen during 4-5 second aplay parse
For Bit-Perfect Playback:
- User should select devices like front:CARD=C20,DEV=0 (CPAL knows these)
- These are more direct than sysdefault:CARD= (which has dmix/plugins)
- Not as direct as hw:4,0 but CPAL can actually find them
PipeWire and PulseAudio backends unchanged - still working as before.
Critical Fix:
- Changed hw: devices from hw:CARD=C20,DEV=0 to hw:4,0
- CPAL only accepts numeric format (hw:X,Y), not named format
- Parse aplay -l to get actual card numbers and device numbers
Before: hw:CARD=C20,DEV=0 → Device not found → fallback to default
After: hw:4,0 → CPAL finds device → bit-perfect playback works
Example from user's system:
- aplay -l shows: card 4: C20 [Cambridge Audio USB Audio 2.0], device 0
- We now create: hw:4,0 (not hw:CARD=C20,DEV=0)
- Description still shows: "Cambridge Audio USB Audio 2.0 (Direct Hardware - Bit-perfect)"
This fixes the issue where selecting the DAC device would play through
laptop speakers instead because CPAL couldn't find the device.
ALSA Device Filtering (45 → ~12 devices):
- Aggressively filter to show only useful devices for audiophiles
- Keep: default, sysdefault:CARD=, front:CARD=, hdmi:CARD=, iec958:CARD=
- Blacklist: surround* variants, usbstream:, pipewire, pulse, bare sysdefault
- Reduces 45 listed devices to ~12 actually useful ones
Add hw:CARD=XXX,DEV=0 Devices for Bit-Perfect Playback:
- Auto-generate hw: devices for each detected card
- hw: = direct hardware access without ALSA plugins (true bit-perfect)
- sysdefault:CARD= has dmix/resampling plugins (NOT bit-perfect)
- Description shows "(Direct Hardware - Bit-perfect)" to distinguish
- This fixes user's issue: DAC was stuck at 44.1kHz with sysdefault:CARD=C20
Loading Indicator:
- Show "Loading devices..." while parsing aplay -L output
- Fixes 4-5 second frozen UI on ALSA Direct backend selection
- isLoadingDevices state added to SettingsView
Stop Playback on Device Change:
- Call stop_playback() in handleBackendDeviceChange
- Prevents stuck/dead audio streams when changing output device
- Fixes: "After any config change, song in player stops working"
User can now:
1. Select hw:CARD=C20,DEV=0 for true bit-perfect to DAC
2. See loading indicator instead of frozen UI
3. Change device without breaking playback
4. Only see relevant devices (not 7 surround variants per card)
ALSA Backend Fallback:
- enumerate_via_aplay() - Preferred method with real hardware descriptions
- enumerate_via_cpal() - Fallback without descriptions (frontend will translate)
- enumerate_alsa_devices() tries aplay first, falls back to CPAL on failure
- Warning log suggests installing alsa-utils if aplay fails
Edge Case Handled:
- System has ALSA installed but not alsa-utils package
- CPAL detects ALSA host → AlsaBackend available
- aplay -L command fails → fallback to CPAL enumeration
- User sees translated names instead of real hardware descriptions
- App still works, just with less friendly device names
Package Dependencies:
- Added alsa-utils as "Recommends" for .deb packages (Debian/Ubuntu)
- Added alsa-utils as "Recommends" for .rpm packages (Fedora/RHEL)
- "Recommends" = installs by default but not required (apt/dnf behavior)
- If user explicitly removes alsa-utils, app falls back gracefully
In practice alsa-utils is almost always installed with ALSA, but this
ensures we handle the edge case properly.
ALSA Backend Changes:
- Parse aplay -L output to get real hardware descriptions
- Description from hardware: "Cambridge Audio Cambridge Audio USB Audio 2.0, USB Audio"
- No more hardcoded CARD_NAMES for specific hardware
- Still uses CPAL to verify device exists and get max sample rate
- Blacklist filtering preserved (null, lavrate, samplerate, etc.)
Frontend Changes:
- Prefer device.description (from aplay -L) when available
- Falls back to getDevicePrettyName() translation only if no description
- ALSA Direct shows: "Cambridge Audio ... USB Audio, Front output / input"
- Not: "Cambridge Audio USB Front Output" (hardcoded translation)
Removed Hardcoding:
- Removed CS201 → "Cambridge Audio USB" (was specific to user's DAC)
- Removed C20 → "Cambridge Audio"
- Generic CARD_NAMES remain (NVidia, Intel HD Audio, etc.)
This makes device names work for ANY hardware, not just known cards.
Each backend should use native tools:
- ALSA: aplay -L
- PipeWire: pw-cli/pactl (future)
- PulseAudio: pactl (future)
Problem 1: ALSA Direct showed NO devices because aplay -L output doesn't
match CPAL device names, causing create_output_stream to fail.
Solution: Revert to CPAL enumeration but with blacklist filtering to skip
non-playback devices (null, lavrate, samplerate, speexrate, jack, oss,
speex, upmix, vdownmix).
Problem 2: Auto backend didn't apply fancy names because needsTranslation()
was too restrictive and didn't cover all ALSA device name patterns.
Solution: Simplify to: if device name starts with 'alsa_output.' it's from
PipeWire (already has friendly name), otherwise it's ALSA (needs translation).
This covers ALL ALSA naming patterns including front:CARD=, sysdefault:CARD=,
hw:, hdmi:, iec958:, etc.
Changes:
- alsa_backend.rs: Back to CPAL with blacklist filter
- SettingsView.svelte: Simplified needsTranslation() logic
Problem 1: ALSA backend showed only 1 device or listed plugins/virtual devices
that don't work for playback (null, lavrate, samplerate, etc.).
Solution: Filter aplay -L output to only include actual playback devices:
- Hardware devices (hw:, plughw:, sysdefault:CARD=, front:CARD=)
- System defaults (default, pipewire, pulse)
- Digital outputs (hdmi:, iec958:, surround)
Problem 2: Quality badge showed file sample rate (e.g., 44.1kHz) instead of
actual hardware sample rate (e.g., 192kHz) when DAC Passthrough was active.
Solution: ExpandedPlayer now polls hardware_sample_rate every second when
DAC Passthrough is enabled and displays the real hardware rate instead of
the file metadata rate.
Changes:
- alsa_backend.rs: Add is_valid_device() filter for playback devices
- ExpandedPlayer.svelte: Poll get_hardware_audio_status every 1s
- ExpandedPlayer.svelte: Use displaySamplingRate (hardware rate when DAC on)
This ensures the quality badge accurately reflects bit-perfect playback status.