Commit Graph
2240 Commits
Author SHA1 Message Date
vicrodh 7a133e2915 Add V2 favorites commands using QbzCore
- Add get_favorites, add_favorite, remove_favorite to QbzCore
- Add favorites methods to CoreBridge
- Add v2_get_favorites, v2_add_favorite, v2_remove_favorite commands
- Migrate favoritesStore, artistFavoritesStore, albumFavoritesStore
- Migrate ArtistDetailView favorites toggle

Favorites operations now flow through V2 architecture.
2026-02-15 00:10:09 -06:00
vicrodh dbad90718a fix(miniplayer): use correct QueueState property name 'queue' 2026-02-14 23:29:09 -06:00
vicrodh 7ef7ff1c7d Make V2 auth required and integrate RuntimeManager with auth
- Login now requires V2 CoreBridge auth to succeed (per ADR)
- V2 auth failure returns typed error, not silent non-blocking warning
- Update RuntimeManager state on login/logout for canonical tracking
- Add runtime_manager parameter to login and logout commands

This implements Phase 4 of the runtime contract: Login Semantics Hardening
2026-02-14 23:24:42 -06:00
vicrodh f34882ccc5 Integrate RuntimeManager with session lifecycle
- Add RuntimeManagerState to activate_user_session
- Add RuntimeManagerState to deactivate_user_session
- Update runtime state on session activation/deactivation
- Add v2_remove_upcoming_track command

This implements Phase 2 of the runtime contract by ensuring session
state changes are reflected in the canonical RuntimeManager.
2026-02-14 23:22:45 -06:00
vicrodh 6cea4149c0 Migrate all queue operations to V2 commands
- Add V2 queue commands: v2_set_shuffle, v2_move_queue_track,
  v2_add_tracks_to_queue, v2_add_tracks_to_queue_next,
  v2_remove_upcoming_track
- Migrate queueStore.ts to use all V2 queue commands
- Update all view components to use V2 queue commands:
  - HomeView, SearchView, ArtistDetailView, FavoritesView
  - LocalLibraryView, PlaylistDetailView
- Update +page.svelte to use V2 shuffle, repeat, and remove commands

No legacy queue commands are called from frontend anymore.
2026-02-14 23:21:19 -06:00
vicrodh 67ed397d02 feat(v2): add runtime contract and queue V2 commands
Runtime contract (ADR Phase 1+2):
- Add RuntimeManager with canonical state machine
- Add RuntimeStatus, RuntimeError (typed errors)
- Add RuntimeEvent for lifecycle events
- Add runtime_get_status and runtime_bootstrap commands

Queue V2 commands:
- v2_add_to_queue, v2_add_to_queue_next
- v2_set_queue, v2_remove_from_queue
- v2_next_track, v2_previous_track, v2_play_queue_index
- V2QueueTrack type with proper field mapping

All queue operations now have V2 equivalents.
Frontend migration to use these commands is next.
2026-02-14 23:13:21 -06:00
vicrodh d79e84fb9c fix(playback): emit events from V2 player when active
The event loop now checks both V2 (qbz-player) and legacy player states.
V2 player takes priority when it has an active track (track_id != 0).

This ensures the frontend receives correct playback position, duration,
sample rate, and bit depth when using v2_play_track commands.

Changes:
- Store V2 player SharedState after CoreBridge initializes
- Event loop checks V2 state first, falls back to legacy
- All playback event values now come from the active player
2026-02-14 23:04:17 -06:00
vicrodh c4b28e37ea fix(session): prevent false login state with userId=0
Addresses race condition and invalid state issues identified by Codex diagnostic:

1. CoreBridge: Add try_get() and is_initialized() methods to gracefully
   handle uninitialized state instead of panicking

2. auth.rs: Use try_get() for V2 auth in login/auto_login/logout to
   avoid panic if CoreBridge init is still in progress

3. LoginView.svelte: Reject userId=0 as fallback - if userInfo or userId
   is missing/invalid, don't call onLoginSuccess (require re-login)

4. +page.svelte:
   - Move get_audio_settings call to after sessionReady is set
   - Add explicit userId > 0 check before activate_user_session
   - Return early if activation fails to prevent sessionReady from
     being set in broken state

These are tactical fixes aligned with ADR_RUNTIME_SESSION_CONTRACT.md
invariants. Full runtime state machine implementation to follow.
2026-02-14 22:59:20 -06:00
vicrodh 85757f186b fix(qbz-core): call client.init() to extract bundle tokens
QbzCore::init() was creating the QobuzClient but not calling init()
to extract bundle tokens. This caused "Client not initialized" errors
for all V2 API commands.
2026-02-14 22:52:37 -06:00
vicrodh c3a14c1c87 feat(v2): add v2_reinit_audio_device command
- Added v2_reinit_audio_device for V2 player device reinitialization
- SettingsView now calls both legacy and V2 reinit commands
- Converts legacy AudioSettings to qbz_audio::AudioSettings for reload
2026-02-14 22:48:03 -06:00
vicrodh fbba4f25c1 feat(v2): add automatic prefetch to v2_play_track
- Added spawn_v2_prefetch helper function
- v2_play_track now prefetches next 2 Qobuz tracks after playback starts
- Respects streaming_only mode (skips prefetch when enabled)
- Uses semaphore to limit concurrent prefetch downloads to 1
- Prefetch works with cache (offline, memory, disk) paths
2026-02-14 22:45:29 -06:00
vicrodh 82a81e1ded feat(v2): add MPRIS, gapless, and prefetch V2 commands
V2 Playback Features:
- v2_set_media_metadata: Update MPRIS metadata
- v2_play_next_gapless: Queue next track for gapless playback
- v2_prefetch_track: Pre-download track into memory cache

V2 Command Updates:
- v2_pause/resume/stop: Now update MPRIS state
- v2_seek: Updates MPRIS progress
- v2_get_playback_state: Updates MPRIS progress on poll

Frontend Migration:
- playbackService: Use v2_set_media_metadata
- playerStore: Use v2_play_next_gapless
2026-02-14 22:42:03 -06:00
vicrodh 6778ef4ba6 fix(auth): sync V2 CoreBridge authentication with legacy login
- Modified login/auto_login commands to also authenticate CoreBridge
- Modified logout command to also logout from CoreBridge
- Removed redundant v2_login call from LoginView.svelte frontend

This ensures both legacy AppState.client and V2 CoreBridge.QbzCore.client
are authenticated in sync, allowing V2 commands to work after login.
2026-02-14 22:38:26 -06:00
vicrodh 6b432586a7 feat(frontend): migrate playback commands to V2
Migrate all frontend playback invocations to V2 commands:
- play_track -> v2_play_track
- pause_playback -> v2_pause_playback
- resume_playback -> v2_resume_playback
- stop_playback -> v2_stop_playback
- seek -> v2_seek
- set_volume -> v2_set_volume

Updated files:
- playbackService.ts
- playerStore.ts
- playerStore.test.ts
- castStore.ts
- SettingsView.svelte

Frontend now uses V2 architecture for all Qobuz playback operations.
2026-02-14 22:17:36 -06:00
vicrodh 41c8acce38 feat(tauri): register v2_play_track command 2026-02-14 22:11:05 -06:00
vicrodh af0d88283a feat(v2): implement v2_play_track command
Core playback now flows through V2 architecture:
- Added get_stream_url to QbzCore and CoreBridge
- Implemented v2_play_track with full cache support:
  - Offline cache (persistent disk)
  - Memory cache (L1)
  - Playback cache (L2 - disk)
- Downloads from Qobuz via CoreBridge.get_stream_url()
- Plays via CoreBridge.player() (qbz-player crate)
- Respects quality limits and streaming_only settings

Still uses AppState.audio_cache for caching (to be migrated later).
No dependency on legacy player module for playback.
2026-02-14 22:10:36 -06:00
vicrodh ac26f2079b chore: mark legacy player and queue modules as deprecated
Added DEPRECATED headers to src-tauri/src/player/mod.rs and
src-tauri/src/queue/mod.rs indicating these will be removed
after migration to qbz-player crate is complete.

New code should use:
- qbz_player::Player (via CoreBridge -> QbzCore)
- qbz_player::QueueManager (via CoreBridge -> QbzCore)
2026-02-14 22:07:30 -06:00
vicrodh 0e85285e90 feat(core): wire Player from qbz-player through QbzCore
- QbzCore now accepts Player in constructor and owns playback
- Added playback methods to QbzCore (pause, resume, stop, seek, volume)
- CoreBridge loads audio settings and creates Player from qbz-player
- V2 playback commands use CoreBridge instead of AppState

This closes the "operational playback core" gap identified by Codex.
V2 commands now flow: frontend -> commands_v2 -> CoreBridge -> QbzCore -> Player

Legacy player in src-tauri/src/player/ remains as emergency fallback.
2026-02-14 20:20:37 -06:00
vicrodh b12a06fff3 feat(v2): expand V2 commands with playback and dual event emission
V2 Commands expanded:
- Add searchType parameter to search commands
- Add blacklist filtering to V2 search commands
- Add V2 playback commands (pause, resume, stop, seek, volume, state)

TauriAdapter dual emission:
- Emit both "core-event" (new) and legacy events during migration
- Legacy events: queue:updated, queue:shuffle-changed, queue:repeat-changed
- Legacy events: auth:logged-in, auth:logged-out, error

Frontend migration (partial soft-delete test):
- SearchView, ArtistDetailView, LocalLibraryView use v2_search_*
- HomeView, +page.svelte use v2_get_album, v2_get_track
- TrackReplacementModal uses v2_search_tracks

Old commands remain registered but are no longer called by migrated
frontend code. This enables testing V2 architecture while keeping
fallback available.
2026-02-14 20:10:58 -06:00
vicrodh a8f56444ce feat(v2-commands): fix parameter names for frontend compatibility
- Use camelCase parameter names (albumId, trackId, artistId) to match
  frontend expectations
- Add #[allow(non_snake_case)] to suppress warnings

V2 commands are ready for testing. Full frontend migration pending:
- Search commands need searchType parameter
- Need to add get_user_info, login with full flow, etc.
2026-02-14 19:48:12 -06:00
vicrodh b930fa11ba fix(v2-commands): return SearchResultsPage for search commands
Update search commands to return full SearchResultsPage<T> with pagination
info instead of just Vec<T>. This makes V2 commands compatible with the
frontend's expected response format.
2026-02-14 19:46:16 -06:00
vicrodh 8ed8f02fcd Phase 7a: Replace src-tauri audio module with qbz-audio re-exports
- Remove duplicate audio module files from src-tauri
- Update audio/mod.rs to re-export from qbz_audio
- Single source of truth for audio types now in qbz-audio crate
- Compilation passes, types are aligned
2026-02-14 17:48:03 -06:00
vicrodh 71f8d09c41 feat(qbz-player): Phase 6.5 - export full Player module
Reorganize qbz-player to export the complete Player struct with all
playback functionality.

Changes:
- Restructure: mod.rs -> player/mod.rs with submodules
- Add rodio, serde, reqwest, qbz-qobuz dependencies
- Export Player, SharedState, PlaybackState, PlaybackEvent
- Export streaming types (BufferedMediaSource, etc.)

The Player module is now fully available from qbz-player crate.
Full workspace and src-tauri compile successfully.
2026-02-14 15:38:53 -06:00
vicrodh 0238b1c559 feat(qbz-nix): Phase 6.4 - add search and catalog V2 commands
Add search and catalog retrieval methods to QbzCore and V2 commands.

QbzCore methods added:
- search_albums, search_tracks, search_artists
- get_album, get_track, get_artist

V2 commands added:
- v2_search_albums, v2_search_tracks, v2_search_artists
- v2_get_album, v2_get_track, v2_get_artist

All commands registered in invoke_handler.
2026-02-14 15:16:31 -06:00
vicrodh 255dcf06fe feat(qbz-nix): Phase 6.3 - wire up CoreBridge in app setup
Initialize CoreBridge in Tauri setup hook and register V2 commands.

Changes:
- Add CoreBridgeState to managed Tauri state
- Initialize CoreBridge asynchronously in setup hook
- Register V2 commands in invoke_handler

The app now initializes the new multi-crate architecture alongside
the existing system. V2 commands are available for frontend testing.
2026-02-14 14:56:36 -06:00
vicrodh f59f0e1b87 feat(qbz-nix): Phase 6.2 - CoreBridge and V2 commands
Add bridge layer connecting QbzCore to Tauri and example V2 commands.

New files:
- core_bridge.rs: CoreBridge wraps QbzCore for Tauri integration
- commands_v2.rs: Example commands using new architecture

V2 commands implemented:
- v2_is_logged_in, v2_login, v2_logout (auth)
- v2_get_queue_state, v2_set_repeat_mode, v2_toggle_shuffle, v2_clear_queue

These coexist with old commands during migration. Frontend can
gradually switch to V2 commands as they're tested.
2026-02-14 14:54:20 -06:00
vicrodh d0e46aca05 feat(qbz-nix): Phase 6.1 - integrate extracted crates
Add dependencies on extracted crates and create TauriAdapter.

Changes:
- Add qbz-models, qbz-audio, qbz-player, qbz-qobuz, qbz-core as deps
- Add async-trait dependency for FrontendAdapter impl
- Create tauri_adapter.rs implementing FrontendAdapter
- TauriAdapter emits CoreEvents to frontend via Tauri event system

This is the first step of integration. The old modules still exist
but the new crates are now available for gradual migration.
2026-02-14 14:32:24 -06:00
vicrodh 30fb25a187 feat(qbz-core): implement Phase 5 - core orchestrator
Add QbzCore<A: FrontendAdapter> as the main orchestrator that ties
all subsystems together and provides a unified API for frontends.

Changes:
- Add core.rs with QbzCore struct
- Add error.rs with CoreError enum
- Update lib.rs with re-exports

QbzCore provides:
- Initialization and shutdown
- Authentication (login/logout)
- Queue operations (shuffle, repeat, clear)
- Event emission through FrontendAdapter
- Access to underlying subsystems (client, queue)

Full workspace compiles with cargo check.
2026-02-14 14:20:24 -06:00
vicrodh ee3a50af89 feat(qbz-player): implement Phase 3b - player module
Extract player/mod.rs, playback_engine.rs, and streaming_source.rs to
qbz-player crate. Also adds settings.rs and visualizer module to
qbz-audio (Tauri-specific code removed, core functionality retained).

Changes:
- Add settings.rs to qbz-audio with AudioSettings, AudioSettingsStore
- Add visualizer/ to qbz-audio with RingBuffer, TappedSource, VisualizerTap
- Add player module files to qbz-player
- Update all imports from crate::audio to qbz_audio
- Add serde_json dependency to qbz-audio

Full workspace compiles with cargo check.
2026-02-14 14:18:06 -06:00
vicrodh 0be0f6dcb0 feat(qbz-qobuz): implement Phase 4 - Qobuz API client
Extracted Qobuz API client from qbz-nix to qbz-qobuz crate.

Modules:
- client.rs: Main QobuzClient with all API methods
- auth.rs: Request signing and authentication
- bundle.rs: Bundle token extraction
- endpoints.rs: API endpoint definitions
- error.rs: API error types
- performers.rs: Performers parsing

Updated qbz-models with additional types:
- Discover types (DiscoverResponse, DiscoverAlbum, etc.)
- Artist page types (PageArtistResponse, ReleasesGridResponse, etc.)
- Playlist tags (PlaylistTag, PlaylistTagsResponse)

Dependencies:
- reqwest for HTTP
- md-5 for request signing
- futures-util for concurrent requests
- anyhow for error handling
2026-02-14 13:25:18 -06:00
vicrodh ad7333d426 feat(qbz-player): implement Phase 3a - queue management
Extracted QueueManager from qbz-nix to qbz-player crate.

Features:
- Queue manipulation (add, remove, reorder, clear)
- Current track tracking
- Shuffle mode with Fisher-Yates algorithm
- Repeat modes (off, all, one)
- Play history for going back
- Full test coverage preserved

Uses types from qbz-models:
- QueueTrack
- QueueState
- RepeatMode

Phase 3b (player module extraction) deferred - requires:
- Trait abstraction for API client
- Trait abstraction for audio backend
- Config system refactoring
2026-02-14 13:13:25 -06:00
vicrodh 3c8fc35fcb feat(qbz-audio): implement Phase 2 - audio backend system
CRITICAL: Copied audio modules from qbz-nix WITHOUT modifying logic.
This code handles bit-perfect playback and is IMMUTABLE.

Copied modules:
- backend.rs: Backend trait and manager
- pipewire_backend.rs: PipeWire/PulseAudio backend
- alsa_backend.rs: ALSA backend with device enumeration
- pulse_backend.rs: PulseAudio fallback
- alsa_direct.rs: Direct ALSA hardware access (hw:X,Y)
- diagnostic.rs: Audio diagnostic tools
- loudness.rs: ReplayGain extraction
- loudness_analyzer.rs: EBU R128 analysis
- loudness_cache.rs: SQLite cache for loudness data
- dynamic_amplify.rs: Dynamic volume amplification
- analyzer_tap.rs: Audio analyzer tap

Dependencies:
- Uses vendored rodio from src-tauri/vendor/rodio
- symphonia-all feature for f32 pipeline
- ebur128 for loudness analysis
- rusqlite for caching

Also updated qbz-models to remove incomplete audio types
(AudioBackendType, AudioDevice) - these stay in qbz-audio.
2026-02-14 13:10:41 -06:00
vicrodh deafa15205 feat(qbz-models): implement Phase 1 - shared types, events, and traits
Tier 0 crate with no dependencies on other qbz-* crates:

- types.rs: Core API types (Track, Album, Artist, Playlist, Quality, etc.)
- playback.rs: Playback types (QueueTrack, RepeatMode, PlaybackState, etc.)
- events.rs: CoreEvent enum for frontend communication
- traits.rs: FrontendAdapter trait enabling multiple frontends
- error.rs: QbzError enum with error codes and recovery flags

All types extracted from qbz-nix/src-tauri/src/api/models.rs and
queue/mod.rs. Ready for use by Tier 1 crates (qbz-audio, qbz-qobuz).
2026-02-14 12:54:52 -06:00
vicrodh 0911cf75df refactor(backend): Phase 0 - Initialize multi-crate workspace
Create the foundational workspace structure for the multi-crate
architecture refactor. This phase establishes placeholder crates
that will be populated in subsequent phases.

Crates created:
- qbz-models: Shared types, events, and traits (Tier 0)
- qbz-audio: Audio backend system (Tier 1)
- qbz-player: Playback engine and queue (Tier 1)
- qbz-qobuz: Qobuz API client (Tier 1)
- qbz-core: Orchestrator (Tier 3)

All crates compile successfully with placeholder implementations.
The workspace uses shared dependencies via workspace.dependencies.

cc
2026-02-14 12:48:52 -06:00
vicrodh 1c9bb8dfdb Merge branch 'pr-79' into pre-release 2026-02-14 12:37:54 -06:00
Joel C 1ad22000ea Add unit tests 2026-02-14 17:57:26 +01:00
Joel C f29f95f6d2 Fix clear queue, preserve playing track if needed 2026-02-14 17:57:16 +01:00
Joel C 4908421308 Fix play queue move_track, map incoming from, to index to internal state 2026-02-14 17:57:00 +01:00
vicrodh 6c509225a4 fix(playlist): ensure progressive loading completes for large playlists
- Track failed batches and retry them once after initial load
- Detect playlist change and abort background loading if user navigates
- Log final status (COMPLETE vs INCOMPLETE with missing count)
- Prevents silent failures that left playlists partially loaded
2026-02-13 18:54:19 -06:00
vicrodh 752c236c11 fix(queue): correct index calculation when no track is playing
- When current_index is None, upcoming starts at index 0, not 1
- Add syncQueueState() after removing track to refresh UI
2026-02-13 14:23:00 -06:00
vicrodh 8dcad203c7 fix(queue): correct remove-from-queue index and add history context menu
- Add remove_upcoming_track command that correctly maps upcoming index
  to actual queue index (handles shuffle mode properly)
- Update frontend to use remove_upcoming_track instead of remove_from_queue
- Add working context menu for history tracks (Add to Playlist, Track Info)
- Remove unused .track-menu CSS class
2026-02-13 13:29:30 -06:00
vicrodh 3a8ef43227 feat(audio): add device refresh button and per-DAC sample rate limit
- Add refresh button next to device dropdown to re-enumerate devices
  without app restart (fixes #69)
- Re-enable quality limiting with per-device sample rate storage
- User can manually set max sample rate per DAC (44.1k to 384k)
- Quality limiting is opt-in (disabled by default)
- Streaming Quality dropdown always takes priority (fixes #72)

Backend: HashMap<device_id, max_rate> stored as JSON in SQLite
Frontend: Conditional UI shows rate dropdown when limit is enabled
2026-02-13 12:56:50 -06:00
vicrodh 333e6f224a feat(ui): show default device name under System Default option
Adds a help text showing which device is the current system default.
This helps users understand what "System Default" will actually use.

Backend:
- New command `get_default_device_name` that uses pactl for PipeWire/Pulse
  and CPAL for ALSA to get the current default device

Frontend:
- Shows "(System Default is: <device name>)" under the output device setting
- Styled as secondary description text (italic, muted)

Translations added for en, es, de, fr.
2026-02-13 12:35:56 -06:00
vicrodh d3fec83aa2 refactor(alsa): use /proc/asound as primary enumeration source
Previously, CPAL was the primary source and /proc/asound supplemented
missing devices. This caused inconsistent device lists because CPAL
cannot enumerate devices that are in exclusive mode.

New architecture:
- /proc/asound = PRIMARY source (always complete, stable IDs)
- CPAL = OPTIONAL enrichment (sample rates, may be unavailable)

Benefits:
- Device list is now consistent regardless of playback state
- No more devices appearing/disappearing when entering settings
- Stable device IDs using card NAMES instead of numbers (fixes #69)

Tradeoff:
- Sample rates may be unavailable when device is in exclusive mode
- This is acceptable: consistency > sample rate display
2026-02-13 11:56:08 -06:00
vicrodh 543f49ef82 fix(alsa): use consistent description format for proc devices
When adding devices from /proc/asound (because CPAL couldn't enumerate
them during exclusive mode), use the same description format as CPAL:
"{card_name}, {device_id}" instead of the longer format with
"Direct Hardware - Bit-perfect" suffix.

This ensures the device dropdown shows consistent names regardless
of whether the device was found by CPAL or added from /proc.
2026-02-13 11:50:01 -06:00
vicrodh abb2a30e6e fix(ui): prevent duplicate System Default in device list
Skip the 'default' device from backend enumeration since we already
add an explicit "System Default" entry. This prevents showing
"System Default" twice in the dropdown.
2026-02-13 11:49:56 -06:00
vicrodh 4af65fede5 fix(alsa): remove hw:X,Y enumeration for device ID stability
ALSA hw:X,Y device IDs use card NUMBERS which are unstable and change
between boots depending on USB enumeration order. This caused saved
devices to become unrecognized after reboot (issue #69).

Now only enumerate front:CARD=name,DEV=Y devices which use the card
NAME instead of number. Both formats are functionally equivalent for
bit-perfect playback, but front: format is stable across reboots.

Fixes #69
2026-02-13 11:38:39 -06:00
vicrodh c07325d26f fix(ui): use device.id as each block key to prevent duplicates
Changed the keyed each block in DeviceDropdown.svelte from device.value
to device.id. This fixes the each_key_duplicate error when multiple
devices have the same display name (e.g., "System Default").
2026-02-13 11:38:33 -06:00
vicrodh 0e6b1fc686 refactor(alsa): replace aplay with /proc/asound reads
Remove dependency on alsa-utils package by reading device information
directly from /proc/asound filesystem:

- /proc/asound/cards for card list with names
- /proc/asound/cardX/ for detailed card info
- /proc/asound/cardX/pcmXp/ for PCM device info

This addresses Flathub reviewer feedback about not using CLI tools
when API alternatives exist. The /proc/asound interface provides
all the information we need for device enumeration and descriptions.

Changes:
- alsa_backend.rs: New enumerate_with_proc_descriptions() using /proc
- audio_backends.rs: Remove aplay calls from DAC capabilities query
- check_alsa_utils_installed() now always returns true (no longer needed)
2026-02-13 10:30:42 -06:00
vicrodh d3a4e4a688 Bump version to 1.1.14 for release
Changes since 1.1.13:
- Fix MPRIS dbus name to use appid (Flathub compliance)
- Fix SQLite WAL mode for all connections (UI lag fix)
- Add volume +/- step buttons in player bar
- Add playlist search in sidebar with folder auto-expand
- New playlist collage layout (3+1 grid)
2026-02-13 09:07:02 -06:00