Lyrion Music Server (LMS) provider
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 1/100
Research direction
PR #349 is already open and the provider is implemented, so this is not available as a first contribution. For orientation, read the existing remote providers and the LMS implementation under external/lyrion/; run its tests, including the env-guarded live suite with CLIAMP_LYRION_LIVE_URL set. Done is covered by the open PR’s implementation and tests.
Written by the indexing model from the issue text.
Description
Problem
cliamp supports seven self-hosted / remote music backends (Navidrome, Plex, Jellyfin, Emby, Audiobookshelf, Qobuz, Tidal) but not Lyrion Music Server (LMS, formerly Logitech Media Server) — the long-running server behind the Squeezebox ecosystem, still actively maintained and very widely deployed on home NAS boxes.
Today an LMS user has no way to reach their library from cliamp, and there's no bridge that closes the gap:
- The LMS Subsonic plugin goes the wrong direction — it lets LMS consume an external Subsonic library, it doesn't expose the LMS library over the Subsonic API. So the existing Navidrome provider can't be pointed at it.
- The LMS UPnP bridge can't adopt cliamp, since cliamp isn't a UPnP renderer (that was raised in #117, which was closed by pointing at the Plex provider).
Proposal
Add a lyrion provider that treats LMS as a library backend, exactly like the other remote providers: browse over its JSON-RPC API, play through cliamp's own engine.
This fits the existing provider seam very cleanly:
| cliamp interface | LMS query |
|---|---|
playlist.Provider |
playlists, playlists tracks |
provider.Searcher |
titles with search: |
provider.ArtistBrowser |
artists, albums artist_id: |
provider.AlbumBrowser |
albums with sort: + offset/count |
provider.AlbumTrackLoader |
titles album_id: sort:albumtrack |
Playback needs no new machinery — LMS serves the original file at /music/<track_id>/download, so tracks are ordinary Stream: true HTTP tracks that route through the existing buffered pipeline alongside Subsonic/Jellyfin/Plex. No new dependencies: it's plain JSON over HTTP.
Deliberately not proposed: SlimProto / Squeezebox player emulation. That would invert cliamp's architecture (the server would drive playback and own the queue) and is a much bigger project — anyone who wants sync groups and multi-room is better served running squeezelite.
Known limitations worth stating up front:
- LMS records play counts against a player playing a track. Since cliamp plays the file directly rather than through an LMS player, cliamp listening leaves no history on the server. There's no honest
PlaybackReportermapping, so it doesn't implement one. - A library can also contain tracks contributed by server plugins (Spotty, etc.). LMS accepts a download request for those and then never sends any bytes, so playback stalls on them. These are now hidden by default, with
show_unplayable = trueto surface them flagged-but-visible — see the update below for why the default flipped.
Mockups / references
- LMS JSON-RPC reference: https://lyrion.org/reference/cli/introduction/
- Authoritative query/tag behaviour:
Slim/Control/Queries.pmin https://github.com/LMS-Community/slimserver
I have this implemented and tested against a live LMS 9.2.0 instance (16,663 tracks) and am happy to open a PR — filing this first to check you'd want it in tree before sending the diff.
Update — findings from testing against a live server
PR #349 is open and now verified end to end against LMS 9.2.0 with a real library (16,663 tracks, 4,225 artists, 1,293 albums, 114 saved playlists). Three things came out of that which weren't obvious from the docs, and which changed the design:
1. Plugin-contributed content needed hiding, not just flagging. My first pass returned plugin tracks marked Unplayable but still visible, reasoning that hiding library content misrepresents the library. That assumed plugin content was a minority garnish. On the test server it wasn't: all 114 saved playlists were Spotty imports, every track inside them a spotify:track:… entry. The provider pane was 114 entries that all did nothing. Plugin tracks and plugin-imported playlists are now omitted by default, with show_unplayable = true restoring the old behaviour.
Filtering playlists costs nothing extra — the playlists query returns each playlist's origin URL in the same response, so one added tags:u separates spotify:playlist:… from file://…. One rough edge remains and is documented rather than fixed: an album made entirely of plugin tracks still appears and opens empty (41 of 1,293 on the test server). Unlike playlists, the albums query carries no origin field, so classifying albums would cost ~1,300 extra round trips.
2. Credentials must not go on the track. Worth flagging for anyone writing a similar provider: Track.Path is persisted verbatim to resume state and to the play history. LMS authenticates with the user's actual account password rather than a revocable token (unlike Plex/Navidrome), so putting credentials in the stream URL writes that password to disk in plaintext. Tracks now carry a bare lyrion://track/<id> and a registered SourceResolver applies credentials at play time, the same shape the Tidal provider uses.
3. Two LMS API details that are easy to get backwards. The albums query uses uppercase S for artist_id, while titles uses lowercase s for the same field (lowercase s in albums is a sort textkey). Since LMS silently omits any field whose tag letter you didn't request, getting this wrong returns albums with a blank artist and no error anywhere. Separately, LMS returns tracknum and year as quoted strings while duration and id come back as numbers, in the same response object — decoding has to accept both. Both are pinned by tests now.
The PR includes an env-guarded live test suite (following the netease precedent) so this is reproducible against your own server:
CLIAMP_LYRION_LIVE_URL=http://lms.local:9000 go test ./external/lyrion/ -run Live -v
- Dominant language
- Go
- Stars
- 4.4k
- Forks
- 307
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 34
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from bjarneo/cliamp
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Windows source build needs CGO and MinGW even without SpotifyPossibly taken @SomSamantray claimed this 5 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
Maintainers usually reply within 1 day
-
Go back to previous equalizer instead of loopingPossibly taken @SomSamantray claimed this 5 days ago. Openenhancement
Difficulty 2/5 1-3 hours Newbie friendliness 63/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
Similar issues
-
Realtime: ASR EOU language tags leak into the LLM conversation (unintended reply-language switching)Possibly taken @nexxtmobile claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
effort:high model:sonnet phase:3 type:docs
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
OffchainLabs/nitro#4771 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
BurntSushi/toml#530 ·