fix(jellyfin): populate all required MediaSourceInfo bool/array fields to match Jellyfin

This commit is contained in:
Deluan 2026-07-05 21:36:35 -04:00
commit b5158f8799
3 changed files with 50 additions and 13 deletions

View file

@ -179,20 +179,38 @@ type AuthenticationResult struct {
ServerId string `json:"ServerId"`
}
// MediaSourceInfo mirrors real Jellyfin's MediaSourceInfo object. Finamp's model declares several
// of these bools and arrays as non-nullable, so a missing field deserializes as null and throws a
// "type 'Null' is not a subtype of type 'bool'" cast error that aborts parsing of the whole item
// list. They must always be emitted (no omitempty on bools) even when false/empty.
type MediaSourceInfo struct {
Id string `json:"Id"`
Path string `json:"Path,omitempty"`
Protocol string `json:"Protocol"`
Container string `json:"Container,omitempty"`
Size int64 `json:"Size,omitempty"`
Name string `json:"Name,omitempty"`
IsRemote bool `json:"IsRemote"`
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
Bitrate int `json:"Bitrate,omitempty"`
SupportsTranscoding bool `json:"SupportsTranscoding"`
SupportsDirectStream bool `json:"SupportsDirectStream"`
SupportsDirectPlay bool `json:"SupportsDirectPlay"`
Type string `json:"Type"`
Id string `json:"Id"`
Path string `json:"Path,omitempty"`
Protocol string `json:"Protocol"`
Container string `json:"Container,omitempty"`
Size int64 `json:"Size,omitempty"`
Name string `json:"Name,omitempty"`
IsRemote bool `json:"IsRemote"`
RunTimeTicks int64 `json:"RunTimeTicks,omitempty"`
Bitrate int `json:"Bitrate,omitempty"`
SupportsTranscoding bool `json:"SupportsTranscoding"`
SupportsDirectStream bool `json:"SupportsDirectStream"`
SupportsDirectPlay bool `json:"SupportsDirectPlay"`
Type string `json:"Type"`
ReadAtNativeFramerate bool `json:"ReadAtNativeFramerate"`
IgnoreDts bool `json:"IgnoreDts"`
IgnoreIndex bool `json:"IgnoreIndex"`
GenPtsInput bool `json:"GenPtsInput"`
IsInfiniteStream bool `json:"IsInfiniteStream"`
UseMostCompatibleTranscodingProfile bool `json:"UseMostCompatibleTranscodingProfile"`
RequiresOpening bool `json:"RequiresOpening"`
RequiresClosing bool `json:"RequiresClosing"`
RequiresLooping bool `json:"RequiresLooping"`
SupportsProbing bool `json:"SupportsProbing"`
HasSegments bool `json:"HasSegments"`
MediaStreams []any `json:"MediaStreams"`
MediaAttachments []any `json:"MediaAttachments"`
Formats []string `json:"Formats"`
}
type PlaybackInfoResponse struct {

View file

@ -26,6 +26,10 @@ func MediaSourceFromMediaFile(mf model.MediaFile) MediaSourceInfo {
SupportsDirectStream: true,
SupportsTranscoding: true,
IsRemote: false,
SupportsProbing: true,
MediaStreams: []any{},
MediaAttachments: []any{},
Formats: []string{},
}
}

View file

@ -1,6 +1,7 @@
package dto
import (
"encoding/json"
"time"
"github.com/navidrome/navidrome/model"
@ -44,6 +45,20 @@ var _ = Describe("mappers", func() {
Expect(src.SupportsDirectPlay).To(BeTrue())
})
It("serializes all Finamp-required MediaSourceInfo bools and arrays, never as null", func() {
mf := model.MediaFile{ID: "s1", Size: 5242880, Suffix: "mp3", BitRate: 320, Duration: 100}
src := MediaSourceFromMediaFile(mf)
b, err := json.Marshal(src)
Expect(err).ToNot(HaveOccurred())
j := string(b)
Expect(j).To(ContainSubstring(`"SupportsProbing":true`))
Expect(j).To(ContainSubstring(`"IsInfiniteStream":false`))
Expect(j).To(ContainSubstring(`"RequiresOpening":false`))
Expect(j).To(ContainSubstring(`"MediaStreams":[]`))
Expect(j).To(ContainSubstring(`"MediaAttachments":[]`))
Expect(j).To(ContainSubstring(`"Formats":[]`))
})
It("omits IndexNumber and ParentIndexNumber when track/disc numbers are untagged", func() {
mf := model.MediaFile{
ID: "song-2", Title: "Song", Album: "Alb", AlbumID: "alb-1",