feat: new "Subsonic Minimal Clients" configuration option (#4850)

* Add `.editorconfig` file

Hints to users how to properly indent Go files (my setup was defaulting
to 2 spaces).

* Add Subsonic API minimal config option

This will allow users to specify clients which can operate with or need
the minimum required fields as per the [SubSonic API
spec](https://subsonic.org/pages/api.jsp).

* Return only required fields for Child Objects

For a minimal client, only return the required fields for Child Objects.

* Return only required fields for Playlist objects

* refactor: simplify client list checks and improve playlist response handling

Signed-off-by: Deluan <deluan@navidrome.org>

* test: add unit tests for client list checks and playlist building logic

Signed-off-by: Deluan <deluan@navidrome.org>

* fix: revert Child.IsVideo and Playlist.Public fields from pointer to boolean, and add omitempty to XML tag

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
This commit is contained in:
Matthew Simpson
2026-01-16 10:55:21 +00:00
committed by GitHub
parent 032cfa2a4d
commit 9ab0c2dc67
36 changed files with 360 additions and 60 deletions
+22 -3
View File
@@ -166,11 +166,30 @@ func getTranscoding(ctx context.Context) (format string, bitRate int) {
return
}
func isClientInList(clientList, client string) bool {
if clientList == "" || client == "" {
return false
}
clients := strings.Split(clientList, ",")
for _, c := range clients {
if strings.TrimSpace(c) == client {
return true
}
}
return false
}
func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child {
child := responses.Child{}
child.Id = mf.ID
child.Title = mf.FullTitle()
child.IsDir = false
player, ok := request.PlayerFrom(ctx)
if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {
return child
}
child.Parent = mf.AlbumID
child.Album = mf.Album
child.Year = int32(mf.Year)
@@ -183,7 +202,7 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
child.BitRate = int32(mf.BitRate)
child.CoverArt = mf.CoverArtID().String()
child.ContentType = mf.ContentType()
player, ok := request.PlayerFrom(ctx)
if ok && player.ReportRealPath {
child.Path = mf.AbsolutePath()
} else {
@@ -211,8 +230,8 @@ func childFromMediaFile(ctx context.Context, mf model.MediaFile) responses.Child
}
func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.OpenSubsonicChild {
player, _ := request.PlayerFrom(ctx)
if strings.Contains(conf.Server.Subsonic.LegacyClients, player.Client) {
player, ok := request.PlayerFrom(ctx)
if ok && isClientInList(conf.Server.Subsonic.MinimalClients, player.Client) {
return nil
}
child := responses.OpenSubsonicChild{}