Commit Graph
206 Commits
Author SHA1 Message Date
vicrodh 9444269fd6 fix(qconnect): fix seekbar position sync with remote controllers
Root cause: server pause/resume commands send None for position and
tracks, which the renderer state blindly overwrote. Then renderer
reports sent null position to the server, making controllers show the
track as finished.

Three-part fix:
1. renderer.rs: only overwrite position/tracks when server provides
   a non-None value (retain last known values through pause/resume)
2. app.rs + qconnect_service.rs: update renderer state position from
   frontend periodic reports so the internal state always has the
   real playback position
3. +page.svelte: increase position report frequency from 5s to 2s
   for smoother seekbar updates on controllers
2026-02-22 12:57:50 -06:00
vicrodh 65f0f25203 fix(qconnect): fix queue error decode and add context_uuid to queue commands
ErrorMessage.code was defined as int32 but server sends string codes
(e.g. 'ERROR_QUEUE_INSERT_TRACKS'). Changed to string type and added
logging for queue error responses.

Added context_uuid (random UUID v4) to queue insert and add commands -
the server likely requires this to associate tracks with a context.
2026-02-22 02:45:04 -06:00
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