165 Commits

Author SHA1 Message Date
Bjarne Øverli 724537d3b4 fix(ytmusic): isolate cache data by account 2026-08-20 23:17:16 +02:00
Praveen Raj 23685fc568 feat(ytmusic): support cookie-backed zero-oauth playlist browsing (#314) 2026-08-20 18:00:30 +02:00
Impure6529 f05bff18ed feat(plex): browse server playlists alongside albums (#321)
* feat(plex): browse server playlists alongside albums

Expose Plex audio playlists (smart and user-created) in the provider
browse view under a "Playlists" section, ahead of the existing album
list which now sits under an "Albums" section.

Playlist IDs are prefixed with "pl:" so Tracks() can route them to the
server playlists items endpoint instead of the album children endpoint.
Playlist items are paginated (1000 per page) to handle smart playlists
with tens of thousands of tracks. Removes the documented "No Plex
playlists" limitation.

* fix(plex): address review feedback

- Do not cache playlist track results: smart playlists change server-side,
  so Tracks() now bypasses the track cache for "pl:" prefixed IDs and
  always reflects current contents (albums keep caching).
- Wrap new playlist-list and playlist-items errors with operation context.
- Add language identifier to the docs example block (markdownlint MD040).
- Add test asserting playlist tracks are refetched on every load.

---------

Co-authored-by: Petar Salinovic <5ar.salinovic@gmail.com>
2026-08-20 17:59:49 +02:00
Luguin 49c2cc7983 Enable Spotify support on Windows (#323)
go-librespot gained first-party WASAPI output for Windows in v0.9.0
(cliamp was pinned to v0.7.1, which doesn't compile on Windows). Bump
the dependency and remove the Windows CGO stub so the real provider
builds on all platforms.

CI now installs a MinGW toolchain via MSYS2 and builds/tests with
CGO_ENABLED=1 on windows-2025, including a workaround for an MSYS2
libogg packaging issue where libogg-0.dll's export table is missing
ogg_stream_iovecin even though it's present in the static libogg.a.

Verified locally end-to-end on Windows: native CGO build, full test
suite, and real Spotify Premium playback.

Fixes #299

Not in scope for this PR: release.yml still builds Windows with
CGO_ENABLED=0, so Releases binaries won't include Spotify until that
pipeline is updated separately (needs a packaging decision: bundle the
MSYS2 DLLs or pursue a fully static build).
2026-08-20 17:57:01 +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
Cory Shaw cfdcb949dd docs: add herdr-cliamp to community plugins (#320)
* docs: add herdr-cliamp to community plugins

A herdr plugin rather than a Lua one: it floats cliamp in a detached herdr
session so closing the float only detaches, and adds now-playing and
transport keybinds that work from any workspace.

* docs: match the site description to the docs entry

Addresses review feedback on #320.
2026-08-19 20:08:24 +02:00
Bjarne Øverli a491ce2b5b fix(spotify): propagate paged search failures 2026-08-19 07:40:18 +02:00
Jankees van Woezik dc5eaee63d fix(spotify): page search around the Development Mode limit (#315)
/v1/search accepts at most 10 results per request for an app in
Development Mode. Asking for more returns 400 "Invalid limit".
SearchTracks asked for up to 50, and friendlySearchError reported that
400 as "search blocked, your client_id is too new", pointing users at the
shared built-in client_id instead. Search is not blocked for these apps,
only paged smaller.

Measured against a Development Mode app (client credentials token):

  limit=10                          200, 10 tracks
  limit=11 / 20 / 50                400 "Invalid limit"
  no limit parameter                200, default page
  type=track,episode&limit=10       200, 10 tracks + 10 episodes
  limit=10&offset=0/10/20/40        200 for every offset
  GET /v1/tracks/{id}               200
  GET /v1/browse/new-releases       403

So the cap is real but narrow: offset paging past it works, and neither
the episode type nor a missing market parameter is involved. Other
catalog endpoints are restricted for these apps, which is likely where
the "search is blocked" theory came from, but /v1/search is not one of
them and Extended Quota Mode is not needed for it.

Try the requested limit first, so an app with Extended Quota Mode keeps
its single request, and fall back to pages of 10 only after Spotify
rejects the limit. Rewrite the error message to describe the cap, and
correct the claim in docs/spotify.md.

Fixes #205
2026-08-19 07:37:04 +02:00
Faza Iman Imron b5478b926f docs(site): add Discord RPC community plugin (#316)
* docs(site): add Discord RPC community plugin

* docs(plugins): sync Discord RPC community listing
2026-08-19 07:35:05 +02:00
Bjarne Øverli ea464dedc2 fix: make built-in themes accessible 2026-08-18 19:31:35 +02:00
Bjarne Øverli 5a588f5d7d feat: add classic Winamp theme 2026-08-18 18:54:24 +02:00
Bjarne Øverli 548ed246f3 fix: preserve custom EQ curve
Fixes #309
2026-08-18 18:46:36 +02:00
RGR09 7ce3dc47d5 feat: enrich playlist album via file metadata (#307)
* feat: enrich playlist album via file metadata

* Refactor enrich command to use normalized source key

- Rename 'from' option to 'source' in command documentation- Update PlaylistEnrich function to correctly handle normalized source keys- Improve source key handling logic for album metadata extraction- Clarify source usage in the 'cliamp playlist enrich' command description

* feat: normalize song path

Replacing forward-slash instead of backslash to have less interference when sorting playlist by path

* feat: enrich elements year via file metadata
2026-08-18 18:11:54 +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
bjarneo 385b227b24 Merge pull request #310 from coryshaw1/feat/audiobookshelf
feat: add Audiobookshelf provider, with resume, progress, and browse-label capabilities
2026-08-18 17:31:26 +02:00
Faza Iman Imron f373776d4c feat(plugins): add retained event pub/sub over IPC (#303)
* feat(ipc): add retained event subscriptions

* feat(plugins): expose namespaced event publishing

* feat(app): wire plugin events to IPC

* docs(plugins): document local event pubsub

* fix(ipc): clear request deadline for subscriptions

* fix(luaplugin): install event publisher before plugins load

* fix(luaplugin): clear retained events after publishers stop

* fix(ipc): keep event sequence gap-free when retention is rejected

* fix(luaplugin): break table cycles and cap depth in Lua value conversion

* docs(plugins): document conversion limits and subscription stream rules

* refactor(ipc): report a missing server with a sentinel error

* fix(luaplugin): fold event namespace into one topic segment

* docs(site): mention plugin event publishing and subscriptions

* fix(luaplugin): keep event namespaces unique across plugins

* fix(luaplugin): release event namespace by installed filename

* refactor(ipc): render the not-running message in the command layer

* docs(plugins): clarify namespace prefixes and JSON conversion limits
2026-08-18 17:19:28 +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 38948f9f94 docs: note buffered-download memory/seek caveat and narrow resume scope
Document that a single large book file is buffered in memory as it
downloads, so a single-file book costs memory proportional to its size
and resuming far into it waits for the download to catch up; per-file
chaptered books avoid both. Also correct the resume promise: it applies
when loading from the provider pane, not through the N browse overlay,
which does not arm resume today.
2026-08-17 11:31:16 -04:00
coryshaw1 28467a171d docs: document the audiobookshelf provider 2026-08-17 11:12:49 -04:00
Bjarne Øverli 05b6986ac0 fix: increase radio playback buffer 2026-08-13 19:32:50 +02:00
Bjarne Øverli 167ab250d6 fix(spotify): restore playback authentication 2026-08-11 19:20:40 +02:00
Salomon May 759c5da81f docs: add cliamp-plugin-gpodder-sync to community plugins (#294) 2026-08-10 20:37:59 +02:00
Sergey Savelev 164f5f8aec feat: the ability to generate bash autocomplete has been enabled (#288)
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled
* feat: the ability to generate bash autocomplete has been enabled

* docs(cli): document shell completion support

* docs(site): add shell completion feature
2026-07-29 19:02:49 +02: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 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 6eae1f001f feat(ui): improve hierarchy and theme safety 2026-07-21 21:40:59 +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 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 bb5f518cc0 fix(plex): explain macOS Local Network denial behind no-route-to-host
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
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 / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
macOS's Local Network privacy blocks LAN dials in the kernel and
reports EHOSTUNREACH before any packet is sent, which reads as a
routing failure. Detect darwin + EHOSTUNREACH + private/link-local
target and append remediation steps to the error.

Fixes #281
2026-07-16 19:16:21 +02:00
Bjarne Øverli dcbac8e19b fix(player): fall back to muxed format for YouTube live streams
Live streams expose no audio-only formats, only muxed video+audio over
HLS, so every branch of the yt-dlp format selector failed with
"Requested format is not available". Append /best as a final fallback;
ffmpeg extracts the audio track and drops the video.

Fixes #280
2026-07-16 19:16:17 +02:00
Bjarne Øverli c110b228cd Perf 2026-07-10 21:46:50 +02:00
mikkel-bergmann 5e16bb7c8c Add cliamp-plugin-mandelbrot to community plugins (#279)
Co-authored-by: Mikkel Bergmann <mikkelbergmann@Mikkels-MacBook-Pro.local>
2026-07-10 10:29:02 +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
lentikr aca9577857 Merge branch 'main' into embedded-lyrics-mpris-art 2026-07-01 20:41:57 +08:00
Bjarne Øverli 0125270cc5 Merge remote-tracking branch 'origin/main' 2026-06-30 19:08:03 +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
8bit64k d328f802ed docs: add cliamp-plugin-nova to community plugins (#269)
Co-authored-by: 8bit64k <8bit64k@users.noreply.github.com>
2026-06-24 23:15:47 +02: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
Sergio Rubio 0b7fe5e562 Add Qobuz to the remaining provider lists in keybindings docs
Qobuz implements search and album/artist browse, but the Ctrl+F search list,
the provider-browser drill-in list, and the "other Ctrl+F providers" note all
predated it. Add it to each. The site's Ctrl+F list already had Qobuz, so this
closes the last docs/site gap for it.
2026-06-13 22:58:28 +02:00
Sergio Rubio 4f1ea74647 Add Q to the Qobuz quick-switch keybinding rows in docs
The main provider table listed Q for Qobuz but the two quick-switch rows
(browse + playlist panes) still showed S/N/P/J/E/Y/C/M/L/R without it. The site
already had Q in both, so the docs were the ones out of sync. Slot it in.
2026-06-13 22:58:28 +02:00
Sergio Rubio 09240a0c36 Add a Random Tracks entry to the Qobuz library
A Library entry next to Favorite Tracks that plays a random sample of up to
500 tracks from across all your playlists, deduped by track ID so songs in
multiple playlists only show up once. Ctrl+R reshuffles.

It's a synthetic playlist (playlists/random) like Favorite Tracks, so it rides
the same Tracks() resolve + cache path. Capped at 500 because each track costs
a track/getFileUrl call to resolve a short-lived stream URL — resolving an
unbounded library would be slow, wasteful, and timeout-prone. Dedupe and
shuffle are pulled into helpers so they're actually testable.
2026-06-13 22:58:28 +02:00
Sergio Rubio e2d4cbfee8 Document qobuz as a valid --provider value
commands.go has accepted "qobuz" since the provider landed, but the docs
valid-values list never got the memo. Add it.
2026-06-13 22:58:28 +02:00