304 Commits

Author SHA1 Message Date
Bjarne Øverli c11504d1ba fix(ui): cancel stale Spotify playlist lookups 2026-08-20 23:17:18 +02:00
Bjarne Øverli 5e3fc47e58 fix(ui): cancel abandoned Spotify album loads 2026-08-20 23:17:17 +02:00
Bjarne Øverli cce9b4aaad fix(ui): keep Spotify errors within scroll budget 2026-08-20 23:17:17 +02:00
Bjarne Øverli 5b6288f1e1 fix(ui): settle paused visualizer state 2026-08-20 23:17:16 +02:00
Bjarne Øverli 86d836cffc fix(ui): keep Spotify search feedback visible 2026-08-20 23:17:15 +02:00
Bjarne Øverli 7cc5cdca4e fix(ui): ignore canceled Spotify album loads 2026-08-20 23:17:15 +02:00
Jankees van Woezik 23b3222a62 feat(spotify): search albums and open them from the results (#318)
* feat(spotify): return albums from search

/v1/search was asked for type=track,episode, so an album could never appear
in the results. Searching an artist returned whichever of their tracks
Spotify ranked highest, and there was no way to reach a record as a record.

Albums are now requested too and lead the results, as an album placeholder:
a Track carrying the album's name, artist and year, marked through
ProviderMeta so the UI can tell it apart without knowing which provider
produced it. Placeholders are not streamable, because spotify:album: URIs
are not something go-librespot can play, so SpotifyProvider now implements
provider.AlbumTrackLoader to expand a chosen one into its tracks.

/v1/albums/{id}/tracks returns simplified track objects without the album
they belong to, so the album's own metadata is fetched once and filled in
on every track for display.

* feat(ui): play a whole album from the search results

Enter, a and q on an album expand it through AlbumTrackLoader and then act
on the full record, matching what they already did for a single track:
Enter starts it now, a appends it, q queues it next. Like playTrackImmediate
they add rather than replace, so a queue built up over an evening survives
picking an album.

The overlay stays open while the expansion runs, showing "Loading album...":
closing it would bump the request generation and drop the response. The
in-flight flag is separate from the playlist fetch's so the results screen
only claims to be loading an album when it is.

p is refused on an album with an explanation. The playlist picker adds one
track, an album is many, and Spotify has no single call to add a record to
a playlist.

* feat(ui): group search results into labeled sections

With albums and tracks in one flat list an album read exactly like one of
its own tracks. The results now carry "Albums" and "Tracks" separators in
the same style the playlist already uses for album headers, and the label
repeats at the top of the viewport when it opens mid-section.

Separators take rows of their own, so scrolling counts rendered rows rather
than results, the way albumSeparatorRows does for the playlist. Without it
the cursor could sit below the bottom of the window.
2026-08-20 18:00:13 +02:00
Matt Van Horn e0328d982e fix: handle capital keybindings from enhanced keyboard events (#322)
Fixes #286

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-08-20 17:59:30 +02:00
Tharin Fernando 5ca8722bae fix(ui): decay visualizer to rest when paused (#327) 2026-08-20 17:57:40 +02:00
Cory Shaw 0c9c30d1c2 feat(ipc): report live radio metadata in status (#319)
* feat(ipc): report live radio metadata in status

A stream's playlist entry only carries the station name, so status
reported that with no artist while the TUI showed the actual song.
Stations broadcast now-playing text inline as ICY metadata (the
StreamTitle field of the SHOUTcast/Icecast protocol), which the UI polls
but never reached the IPC response.

Apply the UI's own resolution in both the TUI and daemon status handlers,
and add TrackInfo.StreamTitle (the raw StreamTitle value) plus
TrackInfo.Station so a client can show station, artist/song, and progress
together.

Resolved at the status handlers rather than in ipcTrackInfo/trackInfo,
since only the now-playing track has stream context.

* fix(ipc): keep the stored title when an ICY tag has no song

A StreamTitle of "Artist - " cuts to an empty title, which blanked the
display instead of falling back to the station name. Guard the split in
resolveTrackDisplay (fixing the TUI and Lua events too, not just status)
and in the daemon, and derive Station from the resolved title so both
paths agree.

Also handle a stream title with no artist in the docs jq example.

Addresses review feedback on #319.

* test(ipc): cover the daemon's stream-title contract

Extract the daemon's stream branch into applyStreamTitle so the test
exercises the real implementation rather than a copy of it, and cover the
"Artist - Title", "Artist - ", title-only, no-metadata, and non-stream
cases. statusResponse needs a live player, so the helper is the seam.

Also guard the docs jq example against a missing .track, which printed a
literal "null" when nothing was loaded.

Addresses review feedback on #319.
2026-08-19 20:08:47 +02:00
Bjarne Øverli b9f68568c5 perf: optimize playback and Spotify switching 2026-08-19 19:32:14 +02:00
Bjarne Øverli ea464dedc2 fix: make built-in themes accessible 2026-08-18 19:31:35 +02:00
Bjarne Øverli 548ed246f3 fix: preserve custom EQ curve
Fixes #309
2026-08-18 18:46:36 +02:00
Bjarne Øverli eee503d6e2 fix: keep live radio on current station
Fixes #305
2026-08-18 18:25:42 +02:00
Taha Sadough 78fcefa771 feat: dynamic directory playlists via [[dir]] sources (#308)
* feat(tomlutil): add ParseNamedSections for multi-section documents

* feat(resolve): add AudioFiles and TracksFromPaths helpers

* feat(playlist): add DirSourced flag to Track

* feat(local): support [[dir]] directory sources in playlists

Playlists can now reference directories with [[dir]] sections instead of
listing every track. Directory sources are scanned at load time, so new
files appear and removed files disappear automatically.

- parsePlaylistDoc keeps explicit tracks and dir sources in document order
- expand resolves dirs into tracks, marking them DirSourced; explicit
  [[track]] entries always shadow a directory scan of the same path
- savePlaylist preserves [[dir]] sections and skips DirSourced tracks
- bookmarking a dir-sourced track materializes it as an explicit entry so
  the bookmark persists
- RemoveTrack refuses dir-sourced tracks; AddTracks dedupes against them
- Playlists()/SearchTracks operate on the expanded view
- CreateDirPlaylist, AddDirSource (deduped), DirSources added

* feat(cli): add --dir flags and playlist dirs subcommand

playlist create and add accept repeatable --dir flags that reference a
directory as a [[dir]] source, and a new 'playlist dirs' subcommand lists
them. --dir cannot be combined with --ssh. enrich skips dir-sourced tracks
and sort notes that they reload in scan order.

* feat(ui): guard edits on dir-sourced playlist tracks

* docs: document [[dir]] directory sources

* docs: show playlist file layout and multi-file pickup

* docs: show adding files/directories across one or many playlists

* fix: address code review findings for directory playlists

- Save playlists with interleaved [[track]]/[[dir]] section order instead of
  flattening dirs first, so removals, reorders, enrichment, and bookmark
  materialization keep each section's original position.
- Remove UI tracks by matching the persisted explicit track by path, so a
  rescan between load and save cannot remove the wrong track.
- Render playlist documents in memory before the atomic rename so a short
  write can never truncate an existing playlist.
- Validate all inputs (audio paths, directory sources) before persisting:
  create and add fail without leaving partially-written playlists behind.
- Persist directory sources as one atomic batch (AddDirSources).
- Skip unreadable entries during recursive directory scans instead of
  aborting the whole scan.
- Wrap directory operations with contextual errors; document directory
  sources on the site.
- Regression tests for section-order preservation, atomic batch validation,
  no-partial-playlist-on-failure, and unreadable-subdir scans.

* fix: resolve remaining code review findings

- tomlutil: flush and clear state on unrecognized array-table headers so
  fields cannot leak into the previous section
- local: propagate playlist read errors instead of rewriting the file
  without its [[dir]] sections
- cmd: use plural helpers for the created-playlist message and wrap
  playlist load errors in playlist bookmark
- resolve: wrap filesystem errors with operation context
- docs: describe the .toml discovery rule accurately and label the
  directory-tree fence

* fix: keep leftover track insertion positions aligned

Two leftovers materialized in one save could land in the wrong slot:
each insertion shifts later sections, so directory positions tracked in
dirPos must be re-aligned after every insertion. Replace the supplier
scan with a pure path check so saves never re-walk the filesystem, and
skip the unreadable-subdir test on Windows where os.Chmod maps to the
read-only attribute instead of Unix permissions.

* fix: persist cross-playlist tracks as explicit entries

A track added from a directory-backed playlist carried its DirSourced
flag into the destination playlist. savePlaylist then dropped it (the
destination has no owning [[dir]] section), so the track was reported as
added but silently lost. Clear the flag on incoming tracks in the
AddTracks merge. Clarify that the playlist listing omits unknown
durations (browser already hides them) and still walks directory sources
to count files.

* fix: only treat supported audio files as dir-supplied

dirSuppliesFile now validates the candidate extension against
player.SupportedExts before the path-containment checks, so non-audio
files added as explicit tracks (e.g. cover.jpg under a [[dir]]) are
appended at the end instead of being inserted before the directory
section.

* test: table-driven coverage for dirSuppliesFile predicate

* test: fix Windows path assertions in dir tests

- Normalize ExpandPath's env-expanded result with filepath.Clean before
  comparing: on Windows the raw expansion mixes / and \ separators.
- Assert TestSavePlaylistPreservesDirsAndSkipsDirTracks against the parsed
  document instead of raw text: the writer escapes backslashes via %q, so
  substring matching of a Windows temp path never matched.
2026-08-18 17:53:14 +02:00
coryshaw1 70dcc75035 refactor(provider): return reporting failures instead of discarding them
PlaybackReporter and ProgressReporter now return an error. The UI logs it at
the three fire-and-forget call sites, so jellyfin, emby, and navidrome gain
the observability audiobookshelf had — they were dropping their client errors
silently. Player state is read on the UI goroutine and passed into the
closures, so the reports stay race-free.
2026-08-17 21:06:10 -04:00
coryshaw1 89c184fa7c fix(audiobookshelf): honor the search context, sync docs and site
SearchTracks now returns early on a cancelled context instead of running
one request per library and per hit. Adds Audiobookshelf to the Ctrl+F,
N-browse, and setup-wizard provider lists, documents the three new
capabilities in the automatic-UI list, and asserts the keymap entry's
label and flag.
2026-08-17 12:37:04 -04:00
coryshaw1 b7042feef2 fix(audiobookshelf): fix review findings from the fix wave
- ReportNowPlaying no longer posts a zero position, which previously
  clobbered the server's stored resume point the instant a track
  started before the stream actually began.
- Clear the armed provider resume state when the newly loaded track
  list no longer contains the armed path, so finishing then reloading
  a book doesn't seek back into the middle of it.
- Tracks() builds only the book or episode track list it needs instead
  of discarding one unconditionally.
- ResumeTarget skips arming a seek for a podcast episode whose
  duration is unknown, since the player would compute a zero byte
  offset and restart it from the beginning anyway.
- Items() caps pagination at 1000 pages as insurance against a server
  reporting total: 0 while still returning full pages.
2026-08-17 11:31:10 -04:00
coryshaw1 375873be9c feat(ui): let providers name their browse levels 2026-08-17 11:06:30 -04:00
coryshaw1 8979c60863 feat: register the audiobookshelf provider 2026-08-17 11:02:26 -04:00
coryshaw1 872afc4803 feat(ui): resume provider playback position and report progress 2026-08-17 10:56:00 -04:00
82Sam 53b2240a7b feat: Progressive playlist loading for youtube music (#287)
Fetches first 20 tracks from YT music when list=URL is parsed and
plays,remaining tracks are fetched in the background and added in
batches of 20.
Added the --expand-playlist/--no-expand-playlist CLI flags and the
expand_playlist key in the configs. This is switched on by default
2026-07-27 22:43:31 +02:00
Bjarne Øverli e6c019e554 test(ui): mirror view layout in centering test 2026-07-25 10:39:28 +02:00
Bjarne Øverli 7e74525b71 fix(ui): restore playlist expansion layout 2026-07-25 10:35:19 +02:00
Bjarne Øverli 7404285d1f fix(ci): restore cross-platform checks
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
Release / build (amd64, darwin, macos-latest) (push) Has been cancelled
Release / build (amd64, linux, ubuntu-latest) (push) Has been cancelled
Release / build (amd64, windows, windows-latest) (push) Has been cancelled
Release / build (arm64, darwin, macos-latest) (push) Has been cancelled
Release / build (arm64, linux, ubuntu-latest) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
2026-07-21 23:15:49 +02:00
Bjarne Øverli bac2475088 feat(ui): add stereo peak visualizer 2026-07-21 23:09:30 +02:00
Bjarne Øverli 605c80da4d feat(ui): prioritize browse content 2026-07-21 22:46:40 +02:00
Bjarne Øverli 336b6a6d29 test(ui): cover terminal boundaries 2026-07-21 21:44:03 +02:00
Bjarne Øverli 6eae1f001f feat(ui): improve hierarchy and theme safety 2026-07-21 21:40:59 +02:00
Bjarne Øverli 9888e6a18f fix(ui): space contextual help hints 2026-07-21 21:28:52 +02:00
Bjarne Øverli 21962cc765 feat(ui): unify interaction controls 2026-07-21 21:25:27 +02:00
Bjarne Øverli 46b903a4d3 feat(ui): add undo for playlist edits 2026-07-21 21:02:43 +02:00
Bjarne Øverli b8efea7efb fix(ui): retry failed lyrics lookups 2026-07-20 23:48:01 +02:00
Bjarne Øverli 55323e32b6 feat(ui): add editable text inputs 2026-07-20 23:45:41 +02:00
Bjarne Øverli 94e2e6ace7 fix(ui): open help before modal dispatch 2026-07-20 23:40:46 +02:00
Bjarne Øverli 09b738eed6 feat(ui): add responsive terminal layouts 2026-07-20 23:30:40 +02:00
Bjarne Øverli 853d40e479 fix(ui): complete phase 0 interaction safety 2026-07-20 23:04:59 +02:00
Bjarne Øverli 02a95fb458 feat(ipc): expand remote player controls 2026-07-20 22:11:17 +02:00
Bjarne Øverli c110b228cd Perf 2026-07-10 21:46:50 +02:00
Bjarne Øverli a5922d072e feat: improve local playlist management 2026-07-06 17:39:27 +02:00
Bjarne Øverli 8dad762349 fix: support go install module path 2026-07-05 13:59:18 +02:00
Bjarne Øverli 5cf895eeb1 test: isolate appdir environment 2026-07-05 13:50:51 +02:00
Bjarne Øverli 40ac05da55 fix(lyrics): harden embedded metadata refresh 2026-07-05 13:44:47 +02:00
lentikr aca9577857 Merge branch 'main' into embedded-lyrics-mpris-art 2026-07-01 20:41:57 +08:00
Bjarne Øverli 494ba35691 fix(ui): reconnect long-paused yt-dlp streams 2026-06-30 21:02:20 +02:00
Nicholas Zambetti 58fc46ea9e Fix vertical padding for overlays in expanded-height mode (#270) 2026-06-30 19:11:16 +02:00
Sergio Rubio 82f228cb92 feat(qobuz): add external provider 2026-06-30 19:06:35 +02:00
lentikr 184a67e519 feat(lyrics,mediactl): embed local lyrics and album art in MPRIS/NowPlaying
Read embedded lyrics (LRC or plain text) and cover art from local file
tags at play time. Lyrics are preferred over network fetch when present;
album art is cached by content hash under ~/.local/share/cliamp/album-art/
and published via mpris:artUrl (Linux) and MPNowPlayingInfoCenter (macOS).
2026-06-29 20:17:41 +08:00
Evan Tetzlaff aecf2e0bf1 feat(ui): add search result navigation keys
Adds Ctrl+N/Ctrl+P single-step navigation and Ctrl+U/Ctrl+D page navigation in search result lists. Updates docs and site keybinding references.
2026-06-15 20:25:24 +02:00
Bjarne Øverli 5fcd5aaabe fix(ui): keep now playing stable when browsing playlists
Release / build (amd64, darwin, macos-latest) (push) Has been cancelled
Release / build (amd64, linux, ubuntu-latest) (push) Has been cancelled
Release / build (amd64, windows, windows-latest) (push) Has been cancelled
Release / build (arm64, darwin, macos-latest) (push) Has been cancelled
Release / build (arm64, linux, ubuntu-latest) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
2026-06-14 18:30:02 +02:00