Daemon side: registers _qbz._tcp.local mDNS service on startup
so desktop apps can discover it on the LAN. Uses hostname as
service name (configurable via qbzd.toml).
Desktop side: new qbzd_discovery.rs module with Tauri commands:
- v2_qbzd_start_discovery: browse _qbz._tcp services
- v2_qbzd_stop_discovery: stop browsing
- v2_qbzd_get_devices: list discovered daemons
Uses same mdns-sd crate already used for AirPlay discovery.
cc
Accent-colored banner shows 'Controlling: {device_name}' with a
disconnect button when the app is targeting a remote qbzd daemon.
Hidden in local mode.
i18n: player.controllingRemote in all 5 locales.
cc
New commandRouter.ts provides a single bifurcation point for all
playback and queue operations. When playbackTarget is 'qbzd', commands
route to the daemon HTTP API. When 'local', they use Tauri invoke().
Stores updated:
- playerStore.ts: pause, resume, stop, seek, volume, play_track (6 sites)
- queueStore.ts: shuffle, repeat, add, add-next, set, clear (8 sites)
No behavioral change in local mode — invoke() calls are identical.
cc
Playback event loop polls QbzCore player state with adaptive timing
(250ms playing, 1s paused, 5s idle) and broadcasts PlaybackSnapshot
events to the event bus.
New SSE endpoint at /api/events streams all DaemonEvents to clients.
Broadcast channel bounded to 256 events with lagged client handling.
cc
New session.rs module ports activate_session from Tauri to headless:
- Creates per-user directories (same layout as desktop app)
- Persists last_user_id marker for session restore
- Emits RuntimeEvent::Ready via event bus
- Skips desktop-only stores (tray, visualizer, remote control)
Session activation wired into daemon startup after auto-login.
Audio settings reload deferred until qbz-player crate exposes
reload_settings (currently on legacy player only).
cc
DaemonCore now initializes the real QbzCore with DaemonAdapter,
creates Player with audio settings from shared database, and
attempts auto-login from saved OAuth token in system keyring.
Working features:
- QbzCore init (bundle token extraction from Qobuz web player)
- Player audio thread (ready for playback commands)
- AudioCache with auto-detected size from RAM
- Shared audio settings with desktop app (same SQLite DB)
- Keyring credential lookup (same service as desktop)
- HTTP endpoints: /api/ping, /api/info, /api/status
- Graceful shutdown (SIGTERM/SIGINT -> stop player -> exit)
cc
Rename commands_v2.rs to commands_v2/mod.rs as the first step
of splitting the 14,635-line file into domain-specific modules.
No functional changes.
cc
Chain Option combinators to reduce nesting. The await boundary
requires one outer if-let, but the rest flattens into a single
and_then/map/filter pipeline.
cc
The custom QConnect device name was stored in-memory only,
resetting to "Qbz - hostname" on every app launch.
Now persists to qconnect_settings.db in the global data dir
(~/.local/share/qbz/). Loaded on startup, saved on change.
Fail-open: any db error silently falls back to default.
Fixes#284
cc
- V2_PREFETCH_COUNT: 2 -> 5 (cache ~2 min ahead)
- V2_MAX_CONCURRENT_PREFETCH: 1 -> 2 (overlap downloads)
- V2_PREFETCH_LOOKAHEAD: 10 -> 15 (find more Qobuz tracks)
Combined with parallel CMAF segments from previous commit,
this keeps the cache well ahead of playback. The sliding
window re-triggers on every track play, so the 5-track
buffer advances continuously through the queue.
cc
Download CMAF segments concurrently (3 slots) instead of
sequentially for prefetch/cache operations. Each slot has a
500ms cooldown after download to stay under CDN rate limits.
This only affects cache prefetch (try_cmaf_full_download),
not real-time streaming which remains sequential for smooth
playback. Segments are reassembled in order after download.
Typical improvement: 10-segment track goes from ~10 serial
HTTP requests to ~4 batches of 3 concurrent downloads.
cc
WebView OAuth: cancel button appears during loading, captcha hint
shows after 25s or when user closes WebView. Hint suggests trying
system browser instead with one-click action.
System browser OAuth: cancel link appears next to loading text,
immediately aborts the wait.
i18n: new keys captchaHint and trySystemBrowser in all 5 locales.
cc
Add OAuthCancelState with shared Notify for system browser flow.
v2_start_system_browser_oauth uses tokio::select! to race between
code received, 120s timeout, and user cancel signal.
New commands:
- v2_cancel_system_browser_oauth: triggers cancel notify
- v2_cancel_oauth_login: closes all OAuth WebView windows
Timeout reduced from 300s to 120s for system browser flow.
cc
When the user closes the OAuth WebView window manually, the
Destroyed event now triggers notify to unblock the 5-minute
timeout immediately, returning oauth_cancelled instead of
hanging until timeout.
cc
Add request_ts + request_sig (MD5 signature) to catalog/search,
album/search, track/search, artist/search, and playlist/search
endpoints matching the mobile app authentication pattern.
Migrate v2_search_all from legacy AppState to CoreBridge, removing
the last direct API call that bypassed the V2 crate architecture.
cc
Pass dragTrackIds to TrackRow in album, playlist, favorites, top-q,
weekly suggest, dynamic suggest, and fav-q views. VirtualizedTrackList
also passes selected IDs for multi-drag.
cc
Tracks are now draggable from any view. Dropping onto a playlist in the
sidebar adds them directly via v2_add_tracks_to_playlist. Supports
multi-select: when tracks are selected in playlist view, dragging any
selected track carries all selected IDs.
cc