Commit Graph

30 Commits

Author SHA1 Message Date
joren
9ca17b4406 feat: now-playing album art scales to full dock width
Stores the raw downloaded pixmap and rescales it (smooth, aspect-ratio
preserved) whenever the dock is resized, so the image always fills
the available width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:46:43 +01:00
joren
7b0e5dcfa8 feat: show resolution in album header (e.g. 24-bit / 96 kHz)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:44:35 +01:00
joren
1e4c234b5c fix: gapless toggle now actually controls audio output lifecycle
When gapless is off, the AudioOutput is dropped after each track ends
naturally, producing a real gap on the next play. When on, the output
stays alive so tracks transition seamlessly. Also re-adds URL prefetch
gating behind the same toggle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:39:24 +01:00
joren
c035ce2dee fix: remove gapless toggle — architecture is always gapless
The player keeps the audio output alive between tracks unconditionally.
The toggle only controlled URL prefetching, not actual audio gaplessness.
Remove the setting and always prefetch the next track URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:36:54 +01:00
joren
8088412d4b feat: album/playlist header and playlist ownership filtering
- Add TrackContextHeader widget: shows album art (fetched via NAM),
  title, subtitle (artist/description), and metadata (year · tracks · duration)
  above the track list when an album or playlist is opened
- Hide header for favorite tracks and search results
- Store user ID in AppSettings on login
- Only show "Delete playlist" for playlists the user owns
- "Add to playlist" submenu only lists owned playlists
- "Remove from this playlist" only appears when viewing an owned playlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:34:04 +01:00
joren
75e2b623b8 feat: refresh views on all playlist mutations
- Add EV_PLAYLIST_TRACK_ADDED (22) — emitted when a track is
  successfully added to a playlist
- If the currently-open playlist is the one modified, re-fetch it
  so the track appears in the list immediately
- After creating a playlist, open it automatically so the user
  lands in the new (empty) playlist view right away
- Sidebar already refreshes on create/delete; this ensures the
  track list view also stays in sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:23:45 +01:00
joren
f30e2058c1 feat: playlist management, gapless playback, ReplayGain, Qobuz theme
Playlist management:
- Add/remove tracks from playlists via right-click context menu
- Create new playlists (right-click Playlists sidebar header)
- Delete playlists with confirmation dialog (right-click playlist item)
- Playlist view removes track immediately on delete (optimistic)
- Deleting currently-open playlist clears the track view

Gapless playback:
- Single long-running audio thread owns AudioOutput; CPAL stream stays
  open between tracks eliminating device teardown/startup gap
- Decode runs inline on the audio thread; command channel polled via
  try_recv() so Pause/Resume/Seek/Stop/Play all work without spawning
- New Play command arriving mid-decode is handled immediately,
  reusing the same audio output for zero-gap transition
- Position timer reduced from 500 ms to 50 ms for faster track-end detection
- URL/metadata prefetch: when gapless is enabled Qt pre-fetches the next
  track while the current one is still playing

ReplayGain:
- Toggled in Settings → Playback
- replaygain_track_gain (dB) from track audio_info converted to linear
  gain factor and applied per-sample alongside volume

Qobuz dark theme:
- Background #191919, base #141414, accent #FFB232 (yellow-orange)
- Selection highlight, slider fill, scrollbar hover all use #FFB232
- Links use Qobuz blue #46B3EE
- Hi-res H badges updated to #FFB232 (from #FFD700)
- Now-playing row uses #FFB232 (was Spotify green)
- QSS stylesheet for scrollbars, menus, inputs, buttons, groups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:19:32 +01:00
joren
c8086e1f2f fix: single-line queue items — Title · Artist on left, duration right
Compact single row: title in medium weight, separator dot and artist
name dimmed, duration right-aligned. Height scales with font.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:37:17 +01:00
joren
6f4029c4bb fix: compact queue item height based on font metrics
Height is now 2 line-heights + 10px padding instead of a fixed 52px,
keeping the two-line layout without wasting vertical space.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:34:34 +01:00
joren
d8619d9efe feat: custom queue item delegate with two-line layout
Each queue item now shows:
- Track title (medium weight) top-left
- Duration (small, dimmed) top-right
- Artist name (small, dimmed) bottom-left

Replaces the flat "Artist — Track" single-line text.
Play-next items get a slightly brighter title tint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:32:17 +01:00
joren
c4c3b2c19d feat: show track title and artist on separate lines in toolbar
Title shown in semi-bold, artist below in smaller grey text — matching
the previous two-line style the user preferred.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:29:31 +01:00
joren
15ba6f7a1e refactor: rewrite toolbar to match spotify-qt structure exactly
Flat QToolBar with sequential addAction/addWidget — no nested containers.
Order: [art][track] | [prev][play][next] [leftSpacer] [progress][time] [rightSpacer] [shuffle][vol][queue][search]

Centering via resizeEvent: both spacers get width/6 of total bar width,
mirroring the exact approach used in spotify-qt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:28:10 +01:00
joren
8950fd2914 fix: toolbar layout — controls inline with track info, progress bar centered
Left:   [art] [title/artist] [⏮] [⏯] [⏭]
Center: [0:00] [━━━━━━━━━━] [3:45]
Right:  [vol] [shuffle] [queue] [search]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:23:04 +01:00
joren
3182c0585c fix: move media controls next to track info, not centered
Controls (prev/play/next) and progress bar are now stacked vertically
to the right of the album art + track label, all in the left section.
Right side keeps volume, shuffle, queue, search toggles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:20:44 +01:00
joren
647054dab2 feat: spotify-style toolbar layout
Three-column layout matching spotify-qt:
- Left: album art thumbnail + track title/artist (stacked, 2 lines)
- Center: prev/play/next controls above elapsed / progress / total
- Right: volume button, shuffle, queue toggle, search toggle

Controls and toggles use QToolButton directly instead of QAction so the
layout is driven by HBoxLayout + VBoxLayout with equal stretch on left
and right, keeping the player controls perfectly centred at all widths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:16:59 +01:00
joren
373fc2b43c feat: queue panel skip-to-track and drag reorder; remove visualizer
Queue panel:
- Double-clicking an upcoming track skips to it immediately: drops all
  tracks before it from the queue and starts playback (skipToUpcoming)
- Items can be dragged to reorder; rowsMoved rebuilds the queue via
  setUpcomingOrder()
- Track JSON stored per-item so order survives drag operations
- New PlayQueue methods: skipToUpcoming(), setUpcomingOrder()
- New QueuePanel signal: skipToTrackRequested(qint64) wired to MainWindow

Remove visualizer:
- Drop VisualizerWidget, Qt6::OpenGLWidgets, projectM CMake detection
- Remove qobuz_backend_read_pcm FFI (Rust + C header + Qt wrapper)
- Remove pcm_visualizer from PlayerStatus and PCM tap from AudioOutput

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:12:30 +01:00
joren
74e43b9713 fix: store TypeRole in col 0 for search album results
When the H badge column was added, TypeRole was accidentally stored in
col 1 but the double-click handler reads it from col 0, breaking album
navigation from search. Move TypeRole back to col 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:12:22 +01:00
joren
7e2a04ecff fix: placeholder text color and H badge in search album results
- Add QPalette::PlaceholderText (#8C8C8C) to the dark palette so
  placeholder text in search box and settings fields is readable
- Add H column to search album results (same gold badge as album list view)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 01:25:42 +01:00
joren
4a9a7f9cbb feat: gold H badge on hi-res albums in album list view
Adds a narrow first column showing a small bold gold "H" for any album
with hires_streamable=true. Applies to both fav albums and artist pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 01:22:57 +01:00
joren
b9b47f80e7 feat: seeking support and Last.fm scrobbling
Seeking:
- Rust player: seek_requested/seek_target_secs atomics on PlayerStatus
- Decoder loop checks for seek each iteration, calls format.seek() and resets decoder
- New qobuz_backend_seek C FFI + QobuzBackend::seek(quint64)
- Progress slider onProgressReleased now seeks to the dragged position

Last.fm:
- LastFmScrobbler: now-playing + scrobble (50% or 240s threshold, min 30s)
- API signature follows Last.fm spec (sorted params, md5)
- Settings dialog: API key/secret, username/password, Connect button with status
- AppSettings: lastfm/enabled, api_key, api_secret, session_key
- Scrobbler wired to trackChanged, positionChanged, trackFinished in MainWindow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 01:13:06 +01:00
joren
ecaee4d907 fix: sorting never changes the queue
Removed the sortApplied→syncQueueToModel connection. Sorting the track
list only reorders the view; the queue is only set when a track is
actually played (double-click or Play Now), at which point the current
sorted order and clicked row are used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 01:01:18 +01:00
joren
d5dedacc36 feat: album list, artist list, and artist detail views
- Fav albums: now shows a sortable list (title/artist/year/tracks);
  double-click opens the album
- Fav artists: now shows a sortable list; double-click opens the artist
- Artist detail page: name, biography summary, and their album list
- Rust ArtistDto gains albums field; get_artist fixed to extra=albums only
- Volume popup minimum width set so "100%" label is never clipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:59:02 +01:00
joren
2436b53697 fix: only set queue context when a track is actually played
Opening a playlist/album no longer resets the queue. The queue context
is now set inside onDoubleClicked and the "Play Now" context menu action,
using the current (possibly sorted) model order at the moment of play.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:55:16 +01:00
joren
cb5c7471c3 fix: play next track correctly skips to queued track 2026-03-24 00:43:08 +01:00
joren
2ccd95b276 fix: play next track correctly skips to queued track
advance() was removing the playNext item then calling current(), which
fell back to m_queue[m_index] — the same track already playing. Fix:
use takeFirst() and return that item directly, only advancing m_index
when the playNext list is empty.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:43:08 +01:00
joren
2852ffb0b7 fix: make volume/progress sliders draggable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:41:10 +01:00
joren
cb2323bc32 feat: initial qobuz-qt source
Lightweight Qt6 desktop client for Qobuz with a Rust audio backend
(Symphonia/CPAL via staticlib FFI). Mirrors the spotify-qt layout:
toolbar with playback controls, library/context docks on the left,
tabbed search side panel on the right, queue panel, now-playing dock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:41:04 +01:00
joren
157d0f9ffe fix: make volume/progress sliders draggable
ClickableSlider previously accepted the mouse press without forwarding
to QSlider, so the drag mechanism never started. Now setSliderPosition
is called first (jumping the handle to the click point) and the event
is always forwarded so Qt enters normal drag mode. mouseMoveEvent is
also overridden to keep snapping to the cursor during a drag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:40:29 +01:00
joren
35ae649fc9 first commit 2026-03-24 00:39:45 +01:00
joren
9402dca7ed Initial implementation of qobuz-qt
- Rust backend (qobuz-backend static lib): Qobuz API client (reqwest/tokio),
  Symphonia audio decoder, CPAL audio output, extern "C" FFI bridge
- Qt 6 frontend mirroring spotify-qt layout: toolbar with playback controls,
  left library dock, central track list, right search panel
- Auth: email/password login with MD5-signed requests; session token persisted
  via QSettings
- Playback: double-click a track → Rust fetches stream URL → Symphonia decodes
  → CPAL outputs to default audio device
- Dark Fusion palette matching spotify-qt feel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:34:23 +01:00