Commit Graph
2352 Commits
Author SHA1 Message Date
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 92b82a62dd fix(qconnect): revert seconds back to milliseconds for position reports
Logs confirm the QConnect protocol uses milliseconds for current_position
and duration fields. The 'seconds' fix was incorrect - the position
display issue was already resolved by the spam fix in 807b775a.
2026-02-22 02:39:39 -06:00
vicrodh bf66bd2eac fix(qconnect): send position/duration in seconds, not milliseconds
QConnect protocol uses seconds for current_position and duration fields.
Frontend was sending milliseconds (currentTime * 1000), causing controllers
to show track position at the end instead of actual progress.
2026-02-22 02:36:06 -06:00
vicrodh 807b775acf fix(qconnect): fix state report spam and null queue_item_id errors
Two bugs caused "Current track not found in queue nor autoplay" errors:

1. prevTrackId only updated when allowLocalSessionPersistence was true,
   but during QConnect mode this is always false. This made every 1s
   playback tick fire a state report (trackId != null always != prevTrackId
   which was stuck at null).

2. Frontend sent currentQueueItemId: null in all reports because it
   doesn't know about QConnect queue_item_ids. Server rejected every
   report.

Fixes:
- Update prevTrackId unconditionally (not gated by session persistence)
- Backend auto-fills queue_item_ids from renderer state when frontend
  passes null
- Track next_queue_item_id in sync state alongside current
- Add throttled 5s periodic position report for controller progress
2026-02-22 02:17:13 -06:00
vicrodh aa6ac1977b fix(qconnect): add remote control mode to playerStore to prevent gapless interception
The gapless transition detection was intercepting QConnect track changes
before the "Track changed externally" handler could process them. Since
the gapless callback returned early (QConnect guard), the event was
silently swallowed and the UI never updated.

Added remoteControlMode flag to playerStore that disables:
- Gapless transition interception (the root cause)
- Fallback end-of-track detection
- Auto-advance on track end

This allows the "Track changed externally" handler to run and sync
the song card, seek bar, and volume with the QConnect-controlled track.
2026-02-22 01:53:01 -06:00
vicrodh 925d04c062 fix(qconnect): disable frontend auto-advance, gapless, and resume-from-stop during QConnect renderer mode
When QConnect is connected as renderer, the server/controller manages
track advancement. The frontend must not auto-advance, pre-queue gapless
tracks, or resume from stop on its own, as this fights the remote controller
and causes rapid queue scrolling without actual playback.
2026-02-22 01:47:40 -06:00
vicrodh 6e8ef5d535 feat(qconnect): add controller UI with session info, transport controls, volume, and renderer selector
- Add session card showing active renderer and renderer list
- Add controller transport buttons (play/pause/stop) via v2_qconnect_set_player_state
- Add volume slider and mute toggle via v2_qconnect_set_volume/mute_volume
- Add renderer selector with set-active-renderer button
- Pass sessionSnapshot prop from +page.svelte to QconnectPanel
- Add i18n keys for all new controller UI elements (en + es)
2026-02-21 23:30:02 -06:00
vicrodh ce362f5635 feat(qconnect): relay local player state changes to QConnect server
When QBZ is acting as a QConnect renderer, local user interactions
(volume change, play/pause) are now reported back to the server via
v2_qconnect_report_volume and v2_qconnect_report_playback_state.
This keeps the controller in sync with QBZ's actual state.
2026-02-21 23:23:56 -06:00
vicrodh 101ef42b6d feat(qconnect): store session management data and expose via snapshot
Session management events (ADD_RENDERER, REMOVE_RENDERER,
ACTIVE_RENDERER_CHANGED, SESSION_STATE) were only being logged.
Now they are stored in QconnectSessionState with renderer list,
active renderer ID, and session UUID. Exposed via new
v2_qconnect_session_snapshot command for frontend use.
2026-02-21 23:19:45 -06:00
vicrodh 83ce91364c fix(qconnect): prevent auto-scroll on queue materialization
Instead of defaulting start_index to 0 when renderer position is
unknown, preserve the current CoreBridge cursor position. This prevents
the queue from resetting to track 0 on every incremental queue update.
2026-02-21 23:14:56 -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 68f901257d diag(qconnect): add full transport lifecycle logging
Log every transport event at info level to diagnose silent
connection death. Previous logging only covered decoded queue/renderer
events, missing Connected/Disconnected/Auth/Subscribe/Keepalive/
Outbound/Frame events entirely.
2026-02-21 15:50:25 -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 1fdfc4eb9d fix(qconnect): materialize remote queue and hydrate remote playback
cx
2026-02-21 14:43:58 -06:00
vicrodh 4a8d923a5b fix(tls): install rustls crypto provider at startup
cx
2026-02-21 14:26:23 -06:00
vicrodh 9a0bdc33cb fix(qconnect): register renderer role during connect bootstrap
cx
2026-02-21 14:22:09 -06:00
vicrodh 4dc60c7bb6 fix(qconnect): bootstrap renderer visibility on connect
cx
2026-02-21 14:16:55 -06:00
vicrodh 0dad07a50d fix(qconnect): auto-resolve websocket endpoint via qws createToken
cx
2026-02-21 14:07:22 -06:00
vicrodh b89c9eb5f0 feat(qconnect-ui): isolate local session persistence and route batch queue ops via admission
cx
2026-02-21 13:49:58 -06:00
vicrodh 9764b4d39a feat(qconnect-ui): apply admission-aware queue actions and i18n messages
cx
2026-02-21 13:49:55 -06:00
vicrodh ec4a9e41eb feat(ui): add Qobuz Connect panel with runtime diagnostics
cx
2026-02-21 13:38:23 -06:00
vicrodh a85f7ca324 feat(qconnect-tauri): add admission middleware commands and blocked events
cx
2026-02-21 13:38:17 -06:00
vicrodh 95f916c6d4 feat(ui): add Qobuz Connect toggle button beside cast
cx
2026-02-21 13:23:20 -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 a0790f9a1d feat(qconnect-tauri): add runtime service bridging renderer commands to CoreBridge
cx
2026-02-21 12:54:06 -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 221a66c675 fix(audio): reload settings before first play to prevent 48kHz lock
On first play after app start, the audio thread held stale settings from
Player::new() — if the user had configured PipeWire/ALSA backend, the
thread might not use the correct backend/device, defaulting to 48kHz.

Now v2_play_track reloads settings from the DB before every play_data()
call, matching what v2_reinit_audio_device already does. This is why
re-selecting the device fixed the issue: it called reload_settings first.

Also extracts convert_to_qbz_audio_settings() helper to deduplicate the
settings conversion between v2_play_track and v2_reinit_audio_device.

Fixes #69
2026-02-21 08:54:57 -06:00
vicrodh 06c6b3457b feat(radio): add track radio submenu to playlist and favorites views
Thread onCreateQbzRadio/onCreateQobuzRadio through TrackRow,
VirtualizedTrackList, PlaylistDetailView, and FavoritesView so
track context menus in playlists and favorites show the radio
submenu with QBZ Radio and Qobuz Radio options.
2026-02-21 07:34:58 -06:00
vicrodh 209cacb569 feat(mixes): add FavQ and TopQ to Your Mixes section
FavQ shuffles up to 50 tracks from the user's Qobuz favorites.
TopQ seeds the Qobuz suggest API from the user's most-played playlists.
Both have same-day cache, refresh button, red/blue gradient artwork,
and a "qbz" badge (vs "qobuz" on DailyQ/WeeklyQ).
2026-02-20 22:45:55 -06:00
vicrodh f4113a9622 feat(ui): add right-click context menu to track rows
Add context menu support (right-click) to the inline track rows in
ArtistDetailView and LabelView popular tracks sections. Right-clicking
a track opens the TrackMenu at the cursor position with all available
actions. Also wires up TrackRow component with contextMenuPosition
prop for the same functionality in reusable track rows.
2026-02-20 22:12:35 -06:00
vicrodh 2dcaa76027 feat(radio): add Qobuz artist/track radio with submenu selection
Add /radio/artist and /radio/track Qobuz API endpoints alongside the
existing /radio/album. TrackMenu now shows a radio submenu with QBZ
Radio (RadioPoolBuilder) and Qobuz Radio options when both are
available. Artist header radio button opens a dropdown to choose
between the two engines.

Backend: get_radio_artist(), get_radio_track() in client.rs,
v2_create_qobuz_artist_radio and v2_create_qobuz_track_radio commands.
Frontend: TrackMenu radio submenu with mutual exclusion against
share/download submenus, ArtistDetailView dropdown, LabelView support.
i18n keys added to all four locale files.
2026-02-20 22:10:44 -06:00
vicrodh f8a56d7f4b fix(radio): fetch full track data for album radio
The /radio/album endpoint returns partial track objects without the
performer field, causing all tracks to show as "Unknown Artist".
Extract track IDs from the response and fetch each via get_track()
to get complete data, matching the pattern used by artist/track radio.
2026-02-20 21:38:56 -06:00
vicrodh 557414ce4e feat(radio): add album radio button to UI
Add Radio button to album detail header (next to favorite button)
that calls v2_create_album_radio, populates the queue with Qobuz
API recommended tracks, and starts playback of the first track.

Add i18n keys for album radio in all 4 locale files (en/es/de/fr).
2026-02-20 21:35:15 -06:00
vicrodh d47f89c6b2 feat(radio): add RadioResponse model, API method, and v2_create_album_radio command
Add /radio/album Qobuz API endpoint support:
- RadioResponse struct in models.rs with lenient deserialization
- get_radio_album() client method
- v2_create_album_radio command that calls the API directly (no
  RadioPoolBuilder/RadioDb), filters blacklisted artists, populates
  queue, and sets Radio playback context
- Register command in lib.rs invoke handler
2026-02-20 21:32:23 -06:00
vicrodh 06c0e9735c fix(session): use struct return type and proactive saves
Two fixes for session not persisting:

1. Changed v2_get_all_queue_tracks to return a struct instead of a
   tuple — Tauri IPC serialization of tuples is unreliable.

2. Added proactive session saves instead of relying on async
   beforeunload (which races with window close):
   - Debounced full save on track change (2s after new track starts)
   - Debounced full save on pause
   - Reduced periodic save from 60s to 15s during playback
   - Close handler remains as best-effort backup
2026-02-20 20:43:35 -06:00