Commit Graph
132 Commits
Author SHA1 Message Date
vicrodh 808b9db0db fix(offline): real offline mode — snapshot streaming, block network, diag logs (#279)
Addresses the reported behavior where tracks marked available offline
still buffered from Qobuz after the v1.2.2 upgrade. Three layers:

1. **Snapshot / restore stream-first-track.** offline_settings gains a
   new nullable column pre_offline_stream_first_track (SQLite ALTER
   migration). v2_set_manual_offline now coordinates with
   AudioSettingsState: entering offline mode stashes the user's current
   stream_first_track preference and forces it to false; exiting
   restores the snapshot. User's preference survives the round-trip
   even across app restarts.

2. **Explicit offline refusal.** v2_play_track now takes
   OfflineState, reads manual_offline_mode up front, and — when every
   local cache miss has been exhausted — returns a new
   RuntimeError::TrackNotAvailableOffline instead of silently reaching
   for the network. Low-quality offline hits still play (better than
   nothing). The playback path also masks stream_first_enabled while
   offline as a belt-and-suspenders defense.

3. **Cache-miss visibility.** Every call site that consults the
   offline cache DB now logs [V2/CACHE STALE] at warn level when the
   DB says a track is cached but the filesystem path is missing. This
   made the issue #279 behavior invisible; the orphan entries from
   per-user isolation migration (aab3e6764) are now diagnosable from
   logs.

Frontend:
- playbackService surfaces TrackNotAvailableOffline as a toast ('…
  isn't saved for offline playback') instead of the generic error
- SettingsView listens for a new audio-settings-changed event so the
  stream-first-track toggle in the UI flips when the offline toggle
  mutates it
- i18n key offline.trackNotAvailableOffline added to all 5 locales

cc
2026-04-13 22:13:16 -06:00
vicrodh 10b5d95852 feat(updater): add performAutoUpdate and eligibility to store/service
Additive changes only: adds isAutoUpdateEligible() getter and
eligibility fetch in initUpdatesStore (keeps existing skipIfRemote
guards); adds performAutoUpdate() in updatesService for the
download/install/relaunch flow with cancellation support.

cc
2026-04-13 14:57:57 -06:00
vicrodh fb10b46ac7 fix(qbzd): early return on remote play, zero local side effects
When target is qbzd, playTrack() sends play-track to daemon and
returns immediately. No local stop (was killing daemon audio),
no cmdStop (was sending HTTP stop to daemon), no metadata update,
no scrobbling, no QConnect handoff.

The daemon handles stop-before-play internally in its play_track
endpoint. The desktop sends ONE command and exits. Zero leakage.

cc
2026-04-09 14:49:26 -06:00
vicrodh b49fce7662 feat(qbzd): guard all stores/services for remote mode override
cc
2026-04-09 14:19:25 -06:00
vicrodh 98286c6123 fix(qbzd): skip QConnect handoff when controlling daemon via HTTP
When playbackTarget is 'qbzd', the play-track command goes directly
to the daemon HTTP API. Previously, it also went through
handoffPlayTrackToRemoteRenderer (QConnect) which sent play to ALL
connected renderers, causing audio on both local desktop AND remote
daemon simultaneously.

Now: qbzd mode = HTTP API only. QConnect handoff only runs in
local mode. Local player stopped explicitly via invoke before
sending to daemon.

cc
2026-04-09 14:04:38 -06:00
vicrodh 72254ac44e fix(qbzd): remove token requirement from remote API client
LAN-only mode means no token auth. The empty string token ('') was
failing the truthy check in requireRemote(), causing 'Not in remote
mode' error on every command after connecting.

Now: token header only sent when token is non-empty. requireRemote
only checks type === 'qbzd' and baseUrl presence.

This was THE bug preventing QBZ Desktop from controlling the daemon.

cc
2026-04-09 13:11:26 -06:00
vicrodh fd1a4fade1 fix(qbzd): route ALL playback and queue calls through commandRouter
playbackService.ts: v2_play_track and v2_stop_playback now route
to daemon HTTP API when in remote mode.

9 views: bulk queue operations (add_tracks_to_queue, add_tracks_to_queue_next)
now use cmdAddTracksToQueue/cmdAddTracksToQueueNext instead of
direct invoke(). 14 call sites total.

This was the root cause of 'connects but nothing plays' — the
play/queue commands were always going to local Tauri, never to
the remote daemon.

cc
2026-04-09 10:51:20 -06:00
vicrodh 8c947c02b4 feat(qbzd): audio settings API + remote settings routing (Phase 5e)
Daemon: 5 new audio settings endpoints:
- GET /api/audio/settings (full settings)
- PATCH /api/audio/settings (partial update)
- GET /api/audio/backends (available backends)
- GET /api/audio/devices?backend= (devices for backend)
- GET /api/audio/hardware-status (current hw state)

Frontend: commandRouter gains audio settings commands that route
to daemon API when in remote mode, local invoke when local.

Total daemon endpoints: 32.

cc
2026-04-09 04:35:49 -06:00
vicrodh e3c1e471c6 feat(qbzd): bifurcate playback and queue stores for remote mode
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
2026-04-09 04:16:28 -06:00
vicrodh 0d94f21479 feat(qbzd): add remote playback target store and API services
New files for desktop controller mode (Phase 5):
- playbackTargetStore.ts: local/remote mode switching
- remoteApi.ts: HTTP client for daemon API (GET/POST + error handling)
- remoteEvents.ts: SSE client for real-time daemon events

cc
2026-04-09 04:09:24 -06:00
vicrodh 3df84f81e7 fix(quality): request track's actual quality instead of always UltraHiRes
playTrack() was always sending the user's max quality setting (Hi-Res+)
regardless of what the track actually offers. A 44.1kHz/24bit track
would trigger 3 API fallback calls (UltraHiRes -> HiRes -> Lossless)
and the cache was discarded as "lower quality" for re-download.

New effectiveQuality() picks the minimum of user max and track metadata
(bit_depth + sampling_rate), so a 24bit/96kHz track requests Hi-Res
directly (1 API call) and cache matches perfectly.

cc
2026-04-06 15:07:41 -06:00
victorrhgap 904502c57c feat(ui): add degraded service indicator for Qobuz server issues
Track 504/server errors and show orange warning triangle in
NowPlayingBar when Qobuz is having issues. Auto-clears after
10 minutes without errors. Shows status in Settings too.

cc
2026-03-25 16:54:04 -06:00
victorrhgap 3559cedeca feat(playback): handle QualityExhausted with user preference
When v2_play_track returns QualityExhausted, the service checks the
persisted fallback behavior: auto-retry at lowest quality, auto-skip,
or emit a custom event so the UI can show the QualityFallbackModal.

Also passes forceLowestQuality option through to the backend invoke.

cc
2026-03-25 15:27:29 -06:00
victorrhgap 39e5867a7d fix: proxy all images through reqwest to bypass WebKit TLS (#163)
When image cache is disabled or unavailable, images were returned
as original HTTPS URLs for WebKit to load directly. On AppImage
distros where GnuTLS can't find system CA certs, this fails silently.

Now all images go through reqwest (rustls, bundled CA) and are
written to temp files as file:// URLs. WebKit never needs to
resolve HTTPS for images.

cc
2026-03-24 20:35:32 -06:00
vicrodh b795d59f1f fix: show actual error message instead of [object Object] on playback failure (#182)
Tauri invoke errors are objects with { code, details, message } fields.
Template literal interpolation with ${err} produces [object Object].
Extract .message or .details, falling back to JSON.stringify().

The quality fallback itself (UltraHiRes -> HiRes) is already handled
by get_stream_url_with_fallback in qbz-core.

cc
2026-03-19 12:30:11 -06:00
vicrodh b4ebad20c5 Revert "fix(qconnect): suppress local playback automation when connected"
This reverts commit b5ab4cd148.
2026-03-15 23:23:57 -06:00
vicrodh a3cde0449d feat: wire autoplay mode sync to QConnect
- setAutoplayMode now sends autoplay preference to QConnect server
- queue_load_tracks syncs local autoplay mode after loading
- QConnect panel shows autoplay_mode status
2026-03-15 10:32:59 -06:00
vicrodh b5ab4cd148 fix(qconnect): suppress local playback automation when connected
When QBZ is the local renderer in a QConnect session and the web player
presses next, the server sends SET_STATE with the new track. The backend
correctly starts playback, but the frontend's gapless transition handler
saw the track_id change, treated it as a local gapless event, and called
nextTrack() — which advanced the CoreBridge queue every 1s polling cycle,
flooding the queue and destroying the shuffle order.

Root cause: shouldQconnectSuppressLocalPlaybackAutomation only suppressed
when a peer renderer was active (activeRendererId !== localRendererId).
When QBZ IS the renderer, remoteControlMode stayed false, so all local
automation (gapless, auto-advance, resume-from-stop) remained active and
fought the server.

Fix: suppress local playback automation whenever QConnect is connected,
regardless of which renderer is active. The server is the authority for
track transitions in all QConnect scenarios.

cc
2026-03-14 22:09:50 -06:00
vicrodh 8a468ed725 fix(qconnect): restore local repeat renderer flow
cx
2026-03-14 13:09:23 -06:00
vicrodh 19b4182dd6 fix(qconnect): render connect queue in remote shuffle order
cx
2026-03-13 22:45:56 -06:00
vicrodh de29f1e2e0 chore(qconnect): checkpoint current poc state
cx
2026-03-13 17:28:05 -06:00
vicrodh 65a0d523bb fix(qconnect): track active remote renderer state for skip and seekbar
cx
2026-03-13 11:55:48 -06:00
vicrodh 0da98aab8c fix(qconnect): hand off queue-start playback to remote renderer
cx
2026-03-13 10:45:37 -06:00
vicrodh 4629e776f1 fix(qconnect): hand off play toggle to active renderer
cx
2026-03-12 23:49:33 -06:00
vicrodh c83aedac75 fix(qconnect): align renderer state with official queue semantics
cx
2026-03-12 23:10:31 -06:00
vicrodh e748ab6616 fix(qconnect): treat omitted cloud queue ids as zero
cx
2026-03-12 20:56:56 -06:00
vicrodh a46c5a016f fix(qconnect): anchor play next to local current track
cx
2026-03-12 20:17:02 -06:00
vicrodh a2c4017607 fix(qconnect): treat remote queue tracks as qobuz playback
cx
2026-03-12 19:48:17 -06:00
vicrodh d3c2d4c6ca fix(qconnect): align queue load payload and inbound diagnostics
cx
2026-03-12 19:31:27 -06:00
vicrodh 2d643a9bfa fix(qconnect): suppress stale renderer drift during queue loads
cx
2026-03-12 17:45:09 -06:00
vicrodh bb110f3654 fix(qconnect): unify queue replacement through shared helper
cx
2026-03-12 17:36:18 -06:00
vicrodh 674fff996b refactor(qconnect): extract runtime helpers from page\n\ncx 2026-03-12 17:21:50 -06:00
vicrodh c026fce2ca fix(qconnect): avoid stale local report drift
cx
2026-03-12 17:08:18 -06:00
vicrodh 5cb4574e37 fix(qconnect): instrument runtime and anchor play next
cx
2026-03-12 15:54:52 -06:00
vicrodh 02155f54fe merge(pre-release): sync qconnect-poc-v3 with latest local integration state
cx
2026-03-12 15:23:32 -06:00
vicrodh e128a7866f feat(v2): implement streaming playback and fix seekbar position
Add real streaming path to v2_play_track: when stream_first_track
is enabled, uses play_streaming_dynamic + background chunked
download instead of full download before playback.

Fix seekbar not showing position for streamed tracks by passing
duration_secs from frontend to the backend. Without it, the
player stored 0 as duration, making the seekbar appear empty.
2026-03-11 14:28:00 -06:00
vicrodh 8eb055d06e fix: use 230px images for album cards instead of 600px
Prioritize image.small (230px) over image.large (600px) for all
AlbumCard artwork and queue track thumbnails. Hero/detail views
retain 600px via getQobuzImageForSize(image, 'large').
2026-03-09 19:51:31 -06:00
vicrodh 6b87554dbb feat: frontend image cache with size-aware loading and settings UI
- getQobuzImageForSize(): picks small (230px) for grids, large for hero
- cachedSrc Svelte action: resolves images through backend disk cache
- imageCacheService: in-memory URL map with dedup for pending requests
- AlbumCard uses cachedSrc action for artwork rendering
- Settings UI: Toggle for cache on/off, numeric input for max size (MB),
  stats display, clear button — uses existing Toggle and input styles
- Removed unused Artwork Thumbnails section (never showed data)
- i18n: added image cache keys to all 4 locales (en/es/de/fr)
2026-03-08 10:52:35 -06:00
vicrodh 5b9f5a75c2 feat(ui): add explicit content badge across entire app
Display parental_warning badge (E icon) next to track titles in all
views: TrackRow, QueuePanel, NowPlayingBar, ImmersivePlayer with all
13 visualizer panels, MiniPlayer surfaces, and AlbumDetailView.

Propagate parental_warning field through the full data chain from
Rust backend (qbz-models, queue, commands_v2, session_store, API
server) to frontend stores, adapters, and UI components. Badge uses
CSS mask technique with explicit.svg for theme compatibility.
2026-03-06 08:48:51 -06:00
vicrodh 00675de6b8 feat(snap): add snap sandbox detection and welcome modal wiring
Detect when QBZ runs inside a Snap sandbox (via SNAP env var) and show
a one-time welcome modal with required plug connection commands. Adds a
Snap Sandbox section in Settings with copyable commands, mirroring the
existing Flatpak pattern.

Backend: snap.rs module, DB table snap_welcome_shown, V2 commands.
Frontend: updatesService, +page.svelte modal queue, SettingsView section.
2026-03-02 11:10:50 -06:00
vicrodh 55a1ef25e6 fix(ui): polish mini/immersive layout and queue favorite behavior
cx
2026-03-01 18:56:23 -06:00
vicrodh 976e5226b4 feat: add miniplayer keyboard shortcuts and fix X button behavior
Add Shift+M toggle shortcut for mini player mode (main and mini window).
Add 1-5 number keys for surface switching (micro/compact/artwork/queue/lyrics)
in mini player context. Add playback shortcuts (Space, Ctrl+Arrow) to mini
player window. Fix X button to close app (or hide to tray) instead of
duplicating the Expand button behavior. Closes #134.
2026-03-01 14:04:18 -06:00
vicrodh 86450f629d fix(security): use cryptographically secure session id generation
cx
2026-03-01 09:20:39 -06:00
vicrodh e0cfc5d92b feat(miniplayer): add default surface setting and queue row play controls
cx
2026-02-26 02:14:09 -06:00
vicrodh d6b0e0f5ea feat(miniplayer): refine micro surface layout and controls
cx
2026-02-26 01:45:59 -06:00
vicrodh 99c8ae1ed3 feat(miniplayer): rebuild surfaces window with compact overlay polish
cx
2026-02-26 00:54:13 -06:00
vicrodh 6067eb280b feat: format-aware download status in album detail view
Download checkmarks now only show for the currently selected format.
In-memory store tracks formatId per album download, and the album
detail view checks track.downloaded_format_ids from backend against
the selected format dropdown. Switching quality hides checkmarks
from a different format's downloads.
2026-02-24 15:09:16 -06:00
vicrodh 328dd39dc2 fix: download path order now Artist/Album/Quality/track
Quality folder was being prepended to the destination path by the
frontend, resulting in Quality/Artist/Album/track. Now quality_dir
is passed as a separate parameter through the full stack and inserted
between album and filename by the backend path builder.
2026-02-24 14:28:26 -06:00
vicrodh 8f2fd7cf1a fix: resolve queue_item_id from track_id, fix loadQconnectQueue, push queue on connect
Three fixes for QConnect seekbar and queue sync:

1. Position reports now include currentTrackId from frontend. Backend
   looks up queue_item_id by matching track_id against QConnect queue
   state when renderer sync state has no cached value. This resolves
   the root cause of seekbar showing position 0 on controllers.

2. loadQconnectQueue changed from fire-and-forget (void) to proper
   error handling with logging at every step. Added logging before
   isQconnectConnected check to diagnose silent failures.

3. When TransportConnected event fires while QBZ is already playing,
   automatically push the local queue to QConnect server so controllers
   immediately see the correct tracks.
2026-02-22 14:01:42 -06:00
vicrodh db0360f4e0 fix(qconnect): sync full queue to remote when starting album or playlist
When playing an album or playlist from QBZ, the local queue was
replaced but the QConnect remote queue was never updated, causing
controllers to show a stale queue.

Add loadQconnectQueue() function that sends queue_load_tracks to the
remote with all track IDs. Wire it into playAlbumById() and
playPlaylistById() so the remote queue updates when a new
album/playlist starts playing locally.
2026-02-22 13:00:19 -06:00