Commit Graph
465 Commits
Author SHA1 Message Date
vicrodh 4e80dcf731 chore: reduce log noise by changing frequent commands to debug level
Move high-frequency API read commands from log::info! to log::debug!:
- get_artist, get_artist_detail, get_artist_tracks, get_artist_albums
- get_featured_albums, get_genres
- get_user_playlists, get_playlist, search_playlists
- get_favorites, get_tracks_by_ids, get_track_info, get_current_user_id
- playlist_get_all_settings, playlist_get_all_stats
- playlist_get_all_local_track_counts, get_playlist_folders

User action commands (create, delete, add, remove) remain at info level.
2026-02-02 21:50:01 -06:00
vicrodh 9b12f761b5 feat(alsa): use stable device IDs for persistent device selection
Convert hw:X,0 to front:CARD=name,DEV=0 format when saving device selection.
This makes device IDs stable across reboots and USB reconnections since they
use the card name (e.g., C20) instead of the card number which can change.

- Add normalize_device_id_to_stable() to convert hw:X,0 to front:CARD=name,DEV=0
- Add resolve_stable_to_current_hw() for reverse lookup when needed
- Apply normalization automatically in set_audio_output_device command
- Export functions from audio module for use across codebase
2026-02-02 21:41:34 -06:00
vicrodh 67895aaa9b fix(audio): validate saved device exists in current enumeration
- Clear stale device IDs from DB when they don't exist in enumeration
- Add warning when device lookup fails in handleBackendDeviceChange
- Add logging to set_audio_output_device command for debugging

ALSA card numbers (hw:X,0) can change between boots when USB devices
are connected/disconnected. This fix prevents using invalid device IDs
that cause playback failures.

cc
2026-02-02 21:32:01 -06:00
vicrodh 0fc5c4bb6f feat(blacklist): filter tracks in backend queue commands
Add second level of protection by filtering blacklisted artists
in Rust queue commands: set_queue, add_to_queue, add_to_queue_next,
and add_tracks_to_queue. This ensures blacklisted tracks are never
added to the queue regardless of frontend implementation.
2026-02-02 15:00:46 -06:00
vicrodh c18dfe641d fix(blacklist): filter albums and iterate most_popular for first valid item
- most_popular now iterates through all items to find first non-blacklisted
- Added album filtering to search_all by album.artist.id
- Added BlacklistState to search_albums with filtering
- Fixes edge case where most_popular was empty when first item was blacklisted
2026-02-02 13:26:23 -06:00
vicrodh bd4c9b7792 feat(blacklist): filter playlist suggestions by artist
Phase 2 (part 2) - Suggestions integration:
- Filter SuggestedTrack results by artist_id
- Log filtered count for debugging

cc
2026-02-02 13:10:36 -06:00
vicrodh bec6907dbe feat(blacklist): integrate filtering into search and radio commands
Phase 2 (part 1) - Read-only integration:
- Filter blacklisted artists from search_tracks, search_artists, search_all
- Filter blacklisted artists from get_similar_artists
- Filter most_popular item if blacklisted
- Add blacklist filtering to all radio commands (artist/track/infinite/refill)
- Use output-layer filtering for radio (engine internals unchanged)

cc
2026-02-02 13:09:35 -06:00
vicrodh 26c5a1b0a3 feat(blacklist): add artist blacklist module with O(1) lookups
Phase 1 of artist blacklist implementation:
- Add artist_blacklist module with HashSet-based service for O(1) lookups
- Add SQLite persistence with WAL mode
- Add global enable/disable feature flag
- Add Tauri commands for CRUD operations
- Register BlacklistState in app state

cc
2026-02-02 13:05:50 -06:00
vicrodh a742c22c8f fix(prefetch): limit concurrent prefetches to prevent memory corruption
Add semaphore to limit concurrent prefetch operations to 1 at a time.
This helps prevent potential race conditions in native audio libraries
(CPAL/PipeWire/ALSA) that can cause malloc corruption.

Also:
- Reduce QOBUZ_PREFETCH_COUNT from 3 to 2
- Reduce PREFETCH_LOOKAHEAD from 15 to 10
- Add 50ms delay before cache insertion

Addresses #38 - random crashes during prefetch
2026-02-02 12:20:10 -06:00
vicrodh aa04a26b6c fix(offline): use hybrid strategy to reduce false positives
Implement 9/10 check strategy:
- 9 out of 10 connectivity checks go to 1.1.1.1 (Cloudflare DNS)
- 1 out of 10 checks go to Qobuz to verify service availability

This prevents false offline detections when the app is making many
concurrent API calls (e.g., loading genre hierarchies), which could
trigger Qobuz rate limiting and cause timeout-based false positives.

Also reduced timeout from 15s to 10s and retry wait from 2s to 1s
since 1.1.1.1 responds much faster than Qobuz.
2026-02-02 11:29:26 -06:00
vicrodh 733a12d702 fix(reco): fix migration for existing users without genre_id column
Remove genre_id from base schema and let migration add it.
This ensures existing databases don't fail when trying to create
index on non-existent column during initial schema execution.
2026-02-02 09:07:47 -06:00
vicrodh d0dd42db65 feat(reco): add genre backfill commands
- Add reco_backfill_genres command to populate genre_id for existing events
- Add reco_needs_genre_backfill command to check if backfill is needed
- Fetches album details from Qobuz API and updates events in batches
- Includes rate limiting to avoid API throttling
2026-02-02 08:49:02 -06:00
vicrodh b4d7f7f4c3 feat(reco): add genre_id to reco_store schema
- Add genre_id column to reco_events table
- Add migration to add column to existing databases
- Update RecoEventInput and RecoEventRecord structs
- Update insert_event and get_events_since to handle genre_id
- Add get_album_ids_without_genre() for backfill
- Add update_genre_for_album() for backfill
2026-02-02 08:47:43 -06:00
vicrodh c2d61b46dc feat(api): add genre_id filter to get_featured_albums
- Add optional genre_id parameter to client method
- Add optional genre_id parameter to Tauri command
- Qobuz API filters featured albums by genre when provided
2026-02-02 08:45:44 -06:00
vicrodh 8e31717908 feat(api): add genre list endpoint
- Add GENRE_LIST endpoint constant
- Add GenreInfo model with id, name, color, slug, path
- Add get_genres() method to client
- Add get_genres Tauri command
2026-02-02 08:44:42 -06:00
vicrodh 61dd071326 feat(search): integrate playlists into search page
- Add playlists to unified search results (SearchAllResults)
- Add dedicated Playlists tab in jump-nav
- Create SearchPlaylistCard with album-like overlay (heart/play/menu)
- Add playlist menu: Play next, Add to queue, Copy to library, Share
- Add offset parameter to search_playlists command
- Add PlaylistGenre and extra fields to Playlist model
- Update searchState with Playlist type and playlistResults

UI changes:
- Remove glyphs from search tabs
- Reduce tab spacing (gap 8px→4px, padding 16px→12px)
- Reduce filter font size (13px→12px)
2026-02-02 07:57:16 -06:00
vicrodh 39e8814848 feat(settings): add limit quality to device toggle and max sample rate tracking
Frontend changes for quality limiting feature:
- Add limitQualityToDevice state and handler
- Add AudioSettings interface fields for limit_quality_to_device and device_max_sample_rate
- Update handleBackendDeviceChange to send max_sample_rate when device changes
- Update handleOutputDeviceChange to find max_sample_rate from backend devices
- Add Settings UI toggle for "Limit Quality to Device"

This completes the quality limiting feature that prevents requesting tracks
that exceed the device's sample rate capabilities, ensuring bit-perfect playback.
2026-02-01 19:29:15 -06:00
vicrodh 6d53907233 feat(audio): add quality limiting based on device capabilities
- Add limit_quality_to_device setting (enabled by default)
- Add device_max_sample_rate field to store selected device's max rate
- Implement limit_quality_for_device() to restrict quality based on device
- Apply quality limiting in play_track and prefetch_track commands
- Add set_audio_device_max_sample_rate Tauri command

This ensures bit-perfect playback by not requesting tracks that
exceed the device's sample rate capabilities. When enabled:
- Devices <= 48kHz: limited to Lossless (44.1kHz)
- Devices <= 96kHz: limited to HiRes (up to 96kHz)
- Devices > 96kHz: allow UltraHiRes (up to 192kHz)
2026-02-01 19:21:02 -06:00
vicrodh be4786e6d3 feat(settings): add limit_quality_to_device setting
Add new audio setting to limit streaming quality based on device
capabilities. When enabled (default: true), QBZ will avoid requesting
tracks that exceed the device's max supported sample rate.

This ensures bit-perfect playback by preventing the need to resample
high-res tracks on devices that don't support them.

- Add limit_quality_to_device field to AudioSettings
- Add database migration for new column
- Add Tauri command for frontend integration

cc
2026-02-01 19:15:11 -06:00
vicrodh da5ec2abcb feat(audio): implement controlled hw→plughw fallback for ALSA Direct
- Add AlsaDirectError enum for structured error classification
- Add BitPerfectMode enum to track runtime bit-perfect status
- Implement automatic fallback: hw fails with format error → try plughw
- Abort on non-recoverable errors (busy, permissions, invalid params)
- Respect ALSA plugin selection from UI settings
- Log runtime mode for debugging

This allows SMSL-class DACs that fail on hw: to work via plughw:
while maintaining bit-perfect guarantees (format conversion only,
no resampling).

cc
2026-02-01 19:13:11 -06:00
vicrodh 4670b1548a feat(audio): add S24_3LE format support for SMSL-class USB DACs
- Add S24_3LE (S243LE) to format priority list as first choice
- Implement S24_3LE write handler with 3-byte packed conversion
- Add S24LE write handler (was missing explicit implementation)
- Reorder format priority: S24_3LE > S32LE > S24LE > S16LE > FloatLE

This fixes bit-perfect playback on USB DACs that only support
the S24_3LE format (SMSL, Topping, Fosi with TAS1020B chip).

Closes #30

cc
2026-02-01 19:07:17 -06:00
vicrodh bb4fcfd6b8 Optimize visualizer: 16 bins mirrored, Material You colors
- Reduce FFT to 30fps with 16 bins (mirrored to 32 visual bars)
- Use 1024 FFT size for faster processing
- Extract dominant colors from album artwork
- Center artwork with track info and quality badge
- Bars render from edges inward with color gradient
2026-02-01 18:44:52 -06:00
vicrodh 7bc429744b Integrate visualizer with audio thread
- Add VisualizerTap to Player for sample capture
- Create wrap_source helper in audio thread
- Wrap all audio sources with TappedSource at 4 append points
- Player now captures samples for visualization when enabled
- Zero overhead when disabled (fast atomic check)

Audio samples flow: Source -> TappedSource -> RingBuffer -> FFT Thread -> Frontend
2026-02-01 15:10:51 -06:00
vicrodh b8cfcfb34a Add audio visualizer backend infrastructure
- Add spectrum-analyzer crate for FFT processing
- Create visualizer module with:
  - RingBuffer: lockless SPSC buffer for audio samples
  - FFT processor: dedicated thread for spectrum analysis
  - TappedSource: rodio Source wrapper for sample capture
  - VisualizerTap: shared state for audio thread integration
- Add Tauri commands: set_visualizer_enabled, is_visualizer_enabled
- Register visualizer in AppState and lib.rs

Backend compiles. Next: integrate with audio thread to capture samples.
2026-02-01 15:07:21 -06:00
vicrodh 5a93ac509d Fix volume spam: deduplicate set_volume calls in backend
- Skip setting volume if already at same value (0.001 tolerance)
- Skip logging when volume unchanged
- Prevents MPRIS/PipeWire feedback loop spam
2026-02-01 14:33:56 -06:00
vicrodh 6265e685ea Update logo and regenerate all app icons
- Updated qbz-logo.svg and qbz-logo.png
- Added qbz-vinyl-v3.svg for VinylPanel
- Regenerated all Tauri icons from new logo
- Updated Android icons and favicon
2026-02-01 14:09:57 -06:00
vicrodh 15bfad00b8 Add unmaximize permission for restore button 2026-02-01 07:55:30 -06:00
vicrodh 9502ca9013 Add fullscreen permissions and lighten immersive UI
- Add core:window:allow-set-fullscreen permission
- Add core:window:allow-is-fullscreen permission
- Lighten all buttons and tabs by ~20%
- Reduce background darkness from 0.6 to 0.5
- Increase border visibility
2026-02-01 07:28:03 -06:00
vicrodh 7a76973920 fix(audio): expand ALSA bit-perfect detection for front:CARD devices
- Add front:CARD=X,DEV=Y device enumeration from aplay -L output
- Recognize front: devices as bit-perfect in AlsaDirectStream
- Group front:CARD and "bit-perfect" named devices correctly in UI
- Add tooltip to device dropdown showing full device name
- Add current selection indicator in device dropdown
- Extend dropdown idle timeout to 10 seconds for long device names

Addresses issue #30 for USB DACs that expose front: but not hw: devices
2026-02-01 05:55:37 -06:00
vicrodh 4548845fbf fix(notification): fail silently when notification system unavailable
Systems without a notification daemon (e.g., minimal Linux setups) would
crash when the app tried to show notifications. Now we log a warning and
continue instead of propagating the error.

Fixes #32
2026-02-01 04:35:59 -06:00
vicrodh 90e4216826 Merge feature/immersive-player into pre-release 2026-02-01 04:31:59 -06:00
vicrodh 52bde38d9f fix(audio): return format_id from play_track and set device name for backend system
1. play_track now returns PlayTrackResult with format_id from Qobuz API
   - Returns actual format (5=MP3, 6=FLAC16, 7=FLAC24, 27=Hi-Res)
   - Returns None for cache hits (format unknown)

2. Frontend updates track.format based on format_id
   - QualityBadge can now correctly show "MP3" when format_id=5
   - Previously badge showed "CD" even for MP3 because format was empty

3. Device name now set after backend system init
   - PipeWire and ALSA backends now set current_device from settings
   - Fixes tooltip showing no device when using backend system
2026-02-01 04:27:23 -06:00
vicrodh ba4c969570 fix(playback): respect streaming_only setting in prefetch
When streaming_only is enabled, prefetch is now skipped entirely.
This ensures tracks are always streamed fresh from the network
without being pre-cached in the background.

When streaming_only is disabled, prefetch works normally.
2026-02-01 03:55:16 -06:00
vicrodh d33ea8b69e feat(playback): add detailed logging for cache vs streaming behavior
Logs now clearly show:
- [CACHE HIT] when playing from memory/disk/offline cache
- [STREAMING] when streaming from network with cache_after flag
- [DOWNLOAD] when using full download mode
- [CACHED] / [NOT CACHED] after download completes
- [Playback Settings] shows stream_first, streaming_only, buffer values
2026-02-01 03:49:37 -06:00
vicrodh f699cf0e63 fix(search): resolve notification loops causing multiple API requests
- Make auth_token() public for catalog/search endpoint authentication
- Fix setSearchQuery to skip notification when value unchanged
- Fix setSearchState to only notify listeners when query actually changed
- Add title case transformation for better most_popular API results
- Add stale result detection to ignore outdated search responses
- Preserve most_popular when API returns inconsistent results
- Clean up debug logging
2026-02-01 02:41:55 -06:00
vicrodh d8bd93374f feat(search): use Qobuz API most_popular field directly
Replace heuristic-based most popular detection with Qobuz's actual
most_popular field from the catalog/search endpoint.

- Add CATALOG_SEARCH endpoint path
- Make app_id() and get_http() public on QobuzClient
- Add MostPopularItem enum with tagged serialization (artists/albums/tracks)
- Update search_all to use catalog/search and parse most_popular field
- Update SearchView template to use allResults.most_popular with new types
- Add MostPopularItem type to searchState store
2026-02-01 01:33:17 -06:00
vicrodh b4944aa527 feat(audio): add streaming-only mode backend
Add streaming_only setting that skips L1+L2 cache writes while still
respecting offline cache (local-first). When enabled:
- download_and_stream() skips final cache.insert()
- Standard download path skips cache.insert()
- Offline cache continues to work normally

This reduces storage usage for users who prefer pure streaming.
2026-02-01 00:37:26 -06:00
vicrodh ae81b1c3c4 Fix quality setting and device name display issues (#34)
- Clear both L1 memory and L2 disk caches when quality setting changes
  This ensures cached tracks at wrong quality are removed
- Fix device name not showing in popup on first hover
  Now refreshes status on each hover instead of only when null
- Add quality downgrade indicator when stream quality is lower than
  track's original quality (shows yellow arrow with tooltip)
- Store original track quality values for comparison with actual stream
2026-01-31 23:46:02 -06:00
vicrodh 8b7e4dab27 feat: show actual stream quality in player UI
Issue #34 - Display actual stream quality, not track metadata

Backend changes:
- Add sample_rate and bit_depth to PlaybackEvent
- Add stream quality tracking to SharedState
- Extract bit_depth from FLAC/M4A headers in streaming path
- Player sets stream quality when decoding audio

Frontend changes:
- Update PlaybackEvent interface with new fields
- Update currentTrack with actual stream quality from backend

This allows the UI to show the actual quality being played (e.g., 44.1kHz
when streaming a 192kHz track at CD Quality) instead of the track's
maximum advertised quality.
2026-01-31 23:32:29 -06:00
vicrodh f83eed1076 debug: add quality logging to diagnose issue #34
- Log requested vs returned format_id and sample_rate in get_stream_url_with_fallback
- Log quality_str, parsed quality, and format_id in play_track and prefetch_track commands
- Add console.log in getStreamingQuality for frontend debugging

This will help diagnose why 192kHz is played despite lower quality settings.
2026-01-31 20:54:05 -06:00
vicrodh 011ee02ec9 Merge pre-release into feature/immersive-player
Resolved conflicts:
- src-tauri/src/api/client.rs: Keep TrackUnavailable error handling
- i18n files: Combined immersive player keys with trackUnavailable key
2026-01-31 17:19:03 -06:00
vicrodh e68c3128b6 feat: auto-skip unavailable tracks with user-friendly error
Backend:
- Add TrackUnavailable error type for tracks removed from Qobuz
- Validate stream URL response - detect missing URL with restrictions
- Add SampleRestrictedByRightHolders to restriction check
- Return specific error when track is unavailable vs quality unavailable

Frontend:
- Detect track unavailable errors in playTrack()
- Show friendly toast: Track title is no longer available
- Auto-skip to next track in queue after 500ms delay
- Add i18n translations (en/es) for trackUnavailable message
2026-01-31 16:10:26 -06:00
vicrodh 1483c05913 Fix: Validate stream URL response before making HEAD request
- Add validation to check if URL is empty before returning StreamUrl
- Add debug logging for JSON response keys
- Return proper error instead of silently using empty URL
- This prevents 'builder error' from HEAD request to empty URL
2026-01-31 15:58:36 -06:00
vicrodh 5ecf9ce24f Fix intermittent HEAD request builder error in streaming playback
- Use lazy_static to create a single reusable HTTP client instead of
  creating a new client for each stream info request
- Add retry logic (3 attempts with 100ms delay) to handle transient
  network/TLS initialization failures
- This fixes 'Failed HEAD request: builder error' toasts that required
  pressing play button twice
2026-01-31 15:54:57 -06:00
vicrodh 14f0cae1c1 feat(suggestions): major UX overhaul with popular tracks fallback
Backend:
- Add get_artist_tracks command for fetching artist's popular tracks

Frontend:
- Fallback to artist's popular tracks when tracks_appears_on is sparse
- Remove card hover movement, only image moves up on hover
- Add hover overlay with (add to queue) (play) (play next) buttons
- Use playlist.svg icon for playlist cards and glyph
- Radio glyph shown by default, replaced by action buttons on hover
- Fix icon sizes: QBZ (22px) vs Qobuz (18px) for visual balance
- Track playlist.svg asset
2026-01-31 14:48:26 -06:00
vicrodh d495c43c39 Revert "feat(visualizer): real-time audio FFT visualization"
This reverts commit 16b35c33fd.
2026-01-30 22:29:05 -06:00
vicrodh 7e17b74f99 fix(visualizer): improve responsiveness and visibility
- Increase FFT emit rate: ~21fps → ~60fps (interval 2048 → 735 samples)
- Boost scale factor: 4x → 50x for more visible response
- Gentler log mapping: powf(2.0) → powf(1.4) for better spread across bars
- Faster smoothing: attack 0.7→0.85, decay 0.15→0.5
- Faster CSS transitions: 80ms → 35ms
- Solid colors instead of gradients for better visibility
2026-01-30 22:20:11 -06:00
vicrodh 16b35c33fd feat(visualizer): real-time audio FFT visualization
Backend (Rust):
- Add rustfft crate for Fast Fourier Transform
- Create visualizer module with FFT computation (1024 samples, Hann window)
- VisualizerSource wrapper taps into audio stream without modifying playback
- Logarithmic frequency bin mapping for perceptual accuracy
- Emit audio-spectrum events to frontend (~20-30fps)
- Tauri commands: set_visualizer_enabled, get_visualizer_enabled

Frontend (Svelte):
- CSS-based rendering (GPU accelerated via transform/transition)
- Three visualization modes: bars, wave, mirror
- Dynamic colors extracted from album artwork
- Listens to audio-spectrum Tauri events
- ~0% CPU usage (GPU handles all transforms)
2026-01-30 21:51:25 -06:00
vicrodh 3926209b0d fix(auth): persist ToS acceptance in Rust/SQLite backend
ToS acceptance was stored in localStorage which gets cleared on app
updates, causing users to re-login frequently. Now stores ToS state
in SQLite (~/.local/share/qbz/legal_settings.db) which survives
updates.

- Add legal_settings.rs with SQLite storage for ToS acceptance
- Update qobuzLegalStore.ts to use Rust invoke commands
- LoginView now loads ToS from Rust before auto-login check
- Migration from localStorage for existing users

Closes #31
2026-01-30 14:49:39 -06:00
vicrodh 5b03af41bc feat(audio): add alsa-utils help modal with distro-specific install commands
- Add check_alsa_utils_installed and get_linux_distro Tauri commands
- Detect Linux distribution from /etc/os-release
- Show appropriate package manager command for each distro
- Add (?) help icon next to ALSA Direct device dropdown in Settings
- Modal explains why alsa-utils is needed for bit-perfect detection
- Updated PKGBUILDs to list alsa-utils as optdepend
2026-01-30 11:43:34 -06:00