Commit Graph
204 Commits
Author SHA1 Message Date
vicrodh a9121ef427 fix(qconnect): correct track_ids wire type to fixed32 packed in outbound queue commands
track_ids in QueueLoadTracks, QueueInsertTracks, QueueAddTracks, and
AutoplayLoadTracks was int32 packed=false but the server expects
fixed32 (packed by default). This silently broke all outbound queue
manipulation commands.
2026-02-21 23:12:09 -06:00
vicrodh 9e8dfd91e5 fix(qconnect): correct wire types for track_id (fixed32) and shuffle_seed (fixed32)
Server sends track_id as fixed32 (wire type 5) and shuffle_seed as
fixed32, but our structs declared them as int32 (wire type 0/varint),
causing decode failures on QueueState, TracksLoaded, and
RendererSetState messages.
2026-02-21 17:29:47 -06:00
vicrodh 4c7be7597d fix(qconnect): remove wrong inner envelope, decode QConnectBatch directly
Root cause: inner_payload from CloudPayload.payload IS a QConnectBatch
(QConnectMessages) with repeated QConnectMessage entries in field 3.

The QCloudInnerEnvelope struct incorrectly treated field 3 as opaque
bytes, keeping only the last repeated entry and losing all messages.
Additionally, QConnectMessages.messages_time was declared as uint64
(wire type 0/varint) but the server sends fixed64 (wire type 1),
causing a wire type mismatch that would fail decoding.

Changes:
- Remove QCloudInnerEnvelope struct entirely
- Pass inner_payload directly to decode_queue_server_events/decode_renderer_server_commands
- Fix messages_time from uint64 to fixed64 to match server wire format

Verified against qonductor proto definitions (QConnectBatch) used in
working POC v1 and v2 implementations.
2026-02-21 17:15:34 -06:00
vicrodh 975fac55f7 diag(qconnect): add decode-layer logging to find envelope unwrap issue
Log whether QCloudInnerEnvelope decode succeeds/fails, message byte
count, and whether queue/renderer decoders succeed or error.
2026-02-21 16:27:01 -06:00
vicrodh 309da97b8e fix(qconnect): unwrap inner QCloud envelope and defer renderer join
Root cause: server wraps QConnectMessage inside an inner envelope with
fields 1 (correlation_id), 2 (sequence), 3 (actual message bytes).
We were passing the envelope directly to QConnectMessage::decode(),
which silently skipped the unknown wrapper fields and found nothing.

Also restructure bootstrap: controller join first (works without
session_uuid), renderer join deferred until SESSION_STATE (type 81)
arrives with the session_uuid the server requires.
2026-02-21 16:12:37 -06:00
vicrodh 8a9e08ad91 feat(qconnect): add session management protobuf types and outbound reporting
Add missing SRVR_CTRL session management message types (81-87, 97-101)
that were completely absent from the protobuf definitions. Without these,
prost silently discarded all session topology messages from the server.

- Add 12 new proto structs for session/renderer/volume/quality events
- Add decoder branches and map functions for all new message types
- Add SessionManagementEvent variant to bypass queue reducer
- Add v2_qconnect_report_playback_state and v2_qconnect_report_volume
- Add comprehensive logging for inbound message diagnostics
2026-02-21 15:35:55 -06:00
vicrodh b160773e50 fix(qconnect): add device capabilities, initial state, and channel subscriptions
- Add DeviceCapabilitiesMessage to protobuf (tag 7 on DeviceInfo) with
  volume_remote_control=ALLOWED, min/max audio quality
- Extend JoinSessionMessage with reason, initial_state, is_active fields
  matching Android renderer proto (tags 3-5)
- Bootstrap sends renderer JoinSession with is_active=true and initial
  RendererState (STOPPED) so other devices see QBZ immediately
- Send initial StateUpdated, VolumeChanged, MaxAudioQualityChanged
  reports after join for full renderer presence
- Default subscribe channels to 0x01,0x02,0x03 instead of empty
- Request queue state from server after bootstrap

cc
2026-02-21 15:11:25 -06:00
vicrodh 9a0bdc33cb fix(qconnect): register renderer role during connect bootstrap
cx
2026-02-21 14:22:09 -06:00
vicrodh a85f7ca324 feat(qconnect-tauri): add admission middleware commands and blocked events
cx
2026-02-21 13:38:17 -06:00
vicrodh 4152d740c9 feat(qconnect-tauri): add controller command API and snapshots
cx
2026-02-21 13:10:42 -06:00
vicrodh 3e91c2f8b0 feat(qconnect-protocol): add Android controller websocket messages
cx
2026-02-21 13:04:17 -06:00
vicrodh e9619b1b43 feat(qconnect-app): emit renderer command events and outbound reports
cx
2026-02-21 12:54:01 -06:00
vicrodh c6bcf6296e feat(qconnect-protocol): encode renderer->server RNDR_SRVR reports
cx
2026-02-21 12:53:55 -06:00
vicrodh fc0135246a feat(qconnect-app): apply renderer commands from WS transport events
-cx
2026-02-21 12:35:20 -06:00
vicrodh 0124728aa9 feat(qconnect-protocol): decode SRVR_RNDR command contracts from protobuf
-cx
2026-02-21 12:35:16 -06:00
vicrodh 5659bf794a feat(qconnect-core): add renderer domain state and reducer
-cx
2026-02-21 12:35:13 -06:00
vicrodh 9959117439 feat(qconnect-app): add app adapter with pending timeout, resync and concurrency policy
-cx
2026-02-21 12:17:55 -06:00
vicrodh c4ffbf4f57 feat(qconnect-transport): add native websocket transport with qcloud framing
-cx
2026-02-21 12:17:47 -06:00
vicrodh d1b01bbd5e feat(qconnect-protocol): implement protobuf queue command and server event contracts
-cx
2026-02-21 12:17:42 -06:00
vicrodh 18b1bd76e0 feat(qconnect-core): add queue domain model, reducer and pending lifecycle
-cx
2026-02-21 12:17:36 -06:00
vicrodh 373220c379 chore(qconnect): register workspace crates and tauri adapter dependency
-cx
2026-02-21 12:17:30 -06:00
vicrodh aa1aa32f7a fix(session): save full queue with all tracks and metadata
The save was broken in two ways:
1. Only current_track + upcoming (capped at 20) were saved, dropping
   all history tracks. Queue was reconstructed incomplete on restore.
2. Track metadata (hires, bit_depth, sample_rate, source, etc.) was
   not being persisted, only the basic 6 fields.

Add v2_get_all_queue_tracks command that returns the full uncapped
track list with correct current_index. Use it in saveSessionBeforeClose
instead of the capped v2_get_queue_state. Map all metadata fields.
2026-02-20 20:33:39 -06:00
vicrodh 585053bd42 feat(label): redesign label detail view with Android-style sections
Replace the flat album grid LabelView with a rich detail page featuring:
- Header with label image, name, description, and play button
- Popular Tracks section (5/10/all expand, same UX as ArtistDetailView)
- Releases horizontal scroll row with "See all" → LabelReleasesView
- Critics' Picks section from second releases container
- Playlists, Artists, and More Labels sections

Backend: add /label/page and /label/explore API endpoints with V2
commands (v2_get_label_page, v2_get_label_explore). Models use
serde_json::Value for flexible section parsing.

Old LabelView renamed to LabelReleasesView for the "See all" flow.
i18n keys added to all 4 locales (en, es, de, fr).
2026-02-20 02:51:47 -06:00
vicrodh cbc2ff0b26 feat(audio): add PipeWire force bit-perfect mode
Add experimental toggle to force PipeWire clock.force-quantum alongside
clock.force-rate for true bit-perfect playback. Quantum is matched to
sample rate (1024 for <=48kHz, 2048 for <=96kHz, etc). Both values
reset to 0 on stop and pause-suspend to avoid affecting other apps.

Requires DAC Passthrough to be enabled, PipeWire backend only.
2026-02-19 18:01:42 -06:00
vicrodh 42915197e3 feat: add downloaded purchases registry in SQLite backend with V2 commands
- downloaded_purchases table in library DB (permanent, per-user)
- V2 commands: mark_downloaded, remove_downloaded, get_downloaded_track_ids
- Frontend enriches purchase data with download status from backend
- Grid overlay for unavailable albums centered on artwork
- Quality filter expanded: All / Hi-Res / CD / Lossy (radio group)
- Album download status derived from track-level registry
2026-02-17 18:57:24 -06:00
vicrodh ceaf299558 feat(immersive): add spectral ribbon and fix home blacklist filtering
- add Spectral Ribbon analyzer/render path and immersive panel
- tune ribbon density/interpolation and bars edge-bin rendering
- apply artist blacklist filtering to discover index home containers
- include supporting i18n/settings/player wiring changes

cx
2026-02-17 17:28:32 -06:00
vicrodh 985f634817 feat(qobuz): add link resolver module with tests
Pure-Rust URL parser for qobuzapp:// and https://play.qobuz.com/ links.
Resolves to typed ResolvedLink enum (OpenAlbum, OpenTrack, OpenArtist,
OpenPlaylist). 24 unit tests covering happy paths and edge cases.
2026-02-17 00:22:26 -06:00
vicrodh a58116212c fix(v2): stabilize plex playback control and shuffle reorder semantics
cx
2026-02-16 15:23:46 -06:00
vicrodh 8ac5ba92fd refactor(v2): enforce backend purity and stabilize playback runtime
cx
2026-02-16 07:45:43 -06:00
vicrodh 045cf24a61 fix(v2): unify queue architecture - all commands use CoreBridge
Previously, V2 queue commands had a dual-queue bug:
- Writes (v2_set_queue, v2_add_to_queue, etc.) went to AppState.queue (legacy)
- Reads (v2_get_queue_state) came from CoreBridge/QbzCore queue
- Result: frontend sets queue, syncs, gets empty/stale data

This commit fixes the architecture:
- All V2 queue commands now use CoreBridge methods
- Added queue methods to QbzCore: add_track, add_tracks, add_track_next,
  set_queue, remove_track, remove_upcoming_track, move_track, play_index,
  next_track, previous_track, peek_upcoming
- Added corresponding methods to CoreBridge
- Fixed v2_set_shuffle to use CoreBridge (was using legacy AppState)
- Fixed runtime requirements: v2_toggle_shuffle and v2_clear_queue now
  use RequiresUserSession like other queue commands (consistency)
- Prefetch now gets upcoming tracks from CoreBridge, not legacy queue
- Updated tests to match V2 behavior (v2_toggle_shuffle, available field)
2026-02-15 20:07:01 -06:00
vicrodh 63994c0a20 refactor(cast): extract qbz-cast crate
Extract casting functionality (Chromecast, AirPlay, DLNA) to standalone
qbz-cast crate. This crate is Tauri-agnostic and can be used by TUI/CLI
clients.

- Move device connection, discovery, threading, and media server to crate
- Keep only Tauri commands in src-tauri/src/cast/
- Thin re-exports in src-tauri for backwards compatibility
- Remove duplicate dependencies from src-tauri Cargo.toml
2026-02-15 14:51:06 -06:00
vicrodh 59e3141aa6 fix: address 7 code review findings for V2 architecture
HIGH priority fixes:
- Enable WAL mode in qbz-integrations caches (ADR-002)
- v2_auto_login/v2_manual_login now call activate_session()
- RequiresUserSession allows offline sessions (only checks session_activated)

MEDIUM priority fixes:
- Consolidate lifecycle: legacy commands delegate to session_lifecycle
- ListenBrainz teardown now calls disconnect() to clear tokens
- Add SessionActivationFailed degraded reason and emit event on failure
2026-02-15 12:21:17 -06:00
vicrodh d6e4b3349b feat(qbz-library): extract local library management to standalone crate
Create qbz-library crate with:
- LibraryScanner: directory scanning for audio files
- MetadataExtractor: audio metadata extraction via lofty
- LibraryDatabase: SQLite persistence with WAL mode
- CueParser: CUE sheet parsing for single-file albums
- Thumbnails: artwork extraction and thumbnail generation
- Tag sidecar: album metadata override files

src-tauri re-exports from qbz-library for backwards compatibility.
Enables TUI/headless clients to manage local libraries without Tauri.
2026-02-15 12:12:52 -06:00
vicrodh 384459d4d7 feat(qbz-cache): extract audio caching to standalone crate
Create qbz-cache crate with:
- AudioCache: L1 in-memory LRU cache (~300MB)
- PlaybackCache: L2 disk-based cache (~500MB)
- LRU eviction with spillover from L1 to L2

src-tauri now re-exports from qbz-cache for backwards compatibility.
This enables TUI/headless clients to use caching without Tauri.
2026-02-15 11:27:38 -06:00
vicrodh 0c50c67a61 feat(v2): add integrations V2 commands using qbz-integrations crate
Wire qbz-integrations to Tauri via new V2 commands:

ListenBrainz V2:
- v2_listenbrainz_get_status, is_enabled, set_enabled
- v2_listenbrainz_connect, disconnect
- v2_listenbrainz_now_playing, scrobble

MusicBrainz V2:
- v2_musicbrainz_is_enabled, set_enabled
- v2_musicbrainz_resolve_track, resolve_artist

Last.fm V2:
- v2_lastfm_get_auth_url, complete_auth
- v2_lastfm_is_authenticated, disconnect
- v2_lastfm_now_playing, scrobble

New module integrations_v2.rs provides Tauri state wrappers for the
Tauri-independent clients from qbz-integrations crate.
2026-02-15 11:11:35 -06:00
vicrodh bd30e7cf24 feat(qbz-integrations): create third-party integrations crate
New crate for Last.fm, ListenBrainz, and MusicBrainz integrations.
Designed to work WITHOUT Tauri for TUI/headless clients.

Modules:
- lastfm: Scrobbling and now-playing via Cloudflare Workers proxy
- listenbrainz: Direct API client with user token auth
- musicbrainz: Entity resolution with rate limiting

Features:
- `cache` (default): SQLite persistence for offline support
  - ListenBrainz: Credential storage, offline listen queue
  - MusicBrainz: Resolved entity cache with TTL

Architecture:
- Pure async Rust, no Tauri dependencies
- Shared error types via IntegrationError enum
- Rate limiting for MusicBrainz (1 req/sec direct, faster via proxy)
2026-02-15 10:36:53 -06:00
vicrodh efb707d972 feat(v2): add v2_get_discover_albums command
Add get_discover_albums to qbz-core, CoreBridge, and commands_v2.
Supports endpoints: newReleases, idealDiscography, mostStreamed,
qobuzissimes, albumOfTheWeek, pressAward.

Migrate DiscoverBrowseView to use v2_get_discover_albums.
2026-02-15 05:32:33 -06:00
vicrodh 2f7cae58f4 feat(v2): add extended catalog and playlist V2 commands
Add missing API methods to QbzCore, CoreBridge, and V2 commands:
- Playlist management: create, delete, update, search
- Catalog: genres, discover index/playlists, playlist tags, featured albums
- Artists: artist page, similar artists, artist with albums pagination
- Labels: label details
- Tracks: batch fetch

All methods follow the V2 architecture with RuntimeManager requirements.
2026-02-15 04:25:31 -06:00
vicrodh 846da0270e feat(v2): migrate playlist commands to V2 architecture
- Add playlist methods to QbzCore (get_user_playlists, get_playlist,
  add_tracks_to_playlist, remove_tracks_from_playlist)
- Add playlist methods to CoreBridge
- Create V2 Tauri commands for all playlist operations
- Update frontend to use V2 playlist commands
- Support both playlistTrackIds and trackIds in remove command
  (with automatic resolution like legacy)
2026-02-15 00:41:48 -06:00
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 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 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 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 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 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 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