Merge remote-tracking branch 'origin/master' into pr5949

# Conflicts:
#	cmd/wire_gen.go
This commit is contained in:
Jiho Andrew Lee 2026-10-06 09:34:16 +09:00
commit bd6a3fb16e
No known key found for this signature in database
GPG key ID: 399C495F019E884F
58 changed files with 1732 additions and 543 deletions

View file

@ -22,7 +22,12 @@ func doInspect(ctx context.Context, ds model.DataStore, id string) (*core.Inspec
return nil, model.ErrNotFound
}
return core.Inspect(file.AbsolutePath(), file.LibraryID, file.FolderID)
lib, err := ds.Library().Get(ctx, file.LibraryID)
if err != nil {
return nil, err
}
return core.Inspect(file.AbsolutePath(), *lib, file.FolderID)
}
func inspect(ds model.DataStore) http.HandlerFunc {

View file

@ -60,9 +60,8 @@ func (pub *Router) handleStream(w http.ResponseWriter, r *http.Request) {
return
}
stream, err := pub.streamer.NewStream(ctx, mf, streampkg.Request{
Format: info.format, BitRate: info.bitrate,
})
streamReq := pub.decider.ResolveRequest(ctx, mf, info.format, info.bitrate, 0)
stream, err := pub.streamer.NewStream(ctx, mf, streamReq)
if err != nil {
if errors.Is(err, streampkg.ErrTooManyTranscodes) {
w.Header().Set("Retry-After", strconv.Itoa(streampkg.RetryAfterSeconds))

View file

@ -116,11 +116,11 @@ var _ = Describe("handleStream", func() {
BeforeEach(func() {
ctx = GinkgoT().Context()
auth.PublicTokenAuth = jwtauth.New("HS256", []byte("test-secret"), nil)
ds = &tests.MockDataStore{}
ds = &tests.MockDataStore{MockedTranscoding: &tests.MockTranscodingRepo{}}
shareRepo = &tests.MockShareRepo{}
ds.MockedShare = shareRepo
streamer = &mockStreamer{}
pub = &Router{ds: ds, streamer: streamer}
pub = &Router{ds: ds, streamer: streamer, decider: stream.NewTranscodeDecider(ds, tests.NewMockFFmpeg(""))}
})
makeRequest := func(token string) *httptest.ResponseRecorder {
@ -152,8 +152,17 @@ var _ = Describe("handleStream", func() {
makeRequest(token)
Expect(streamer.called).To(BeTrue())
Expect(streamer.req.Format).To(Equal("mp3"))
Expect(streamer.req.BitRate).To(Equal(192))
})
It("resolves the full stream request like the Subsonic endpoint, so transcodes share the cache", func() {
mf := model.MediaFile{ID: "mf-123", Suffix: "flac", BitRate: 1500, SampleRate: 44100, BitDepth: new(24), Channels: 2}
shareOwnedBy(model.User{ID: "owner1", UserName: "owner1", IsAdmin: true}, mf)
claims := auth.Claims{ID: "mf-123", Format: "opus", BitRate: 128, ShareID: "share123"}
token, _ := auth.CreateExpiringPublicToken(time.Now().Add(time.Hour), claims)
makeRequest(token)
Expect(streamer.req).To(Equal(stream.Request{Format: "opus", BitRate: 128, SampleRate: 48000, Channels: 2}))
})
It("returns 404 when the track is outside the share owner's libraries", func() {

View file

@ -21,14 +21,15 @@ type Router struct {
http.Handler
artwork artwork.Artwork
streamer stream.MediaStreamer
decider stream.TranscodeDecider
archiver core.Archiver
share core.Share
assetsHandler http.Handler
ds model.DataStore
}
func New(ds model.DataStore, artwork artwork.Artwork, streamer stream.MediaStreamer, share core.Share, archiver core.Archiver) *Router {
p := &Router{ds: ds, artwork: artwork, streamer: streamer, share: share, archiver: archiver}
func New(ds model.DataStore, artwork artwork.Artwork, streamer stream.MediaStreamer, decider stream.TranscodeDecider, share core.Share, archiver core.Archiver) *Router {
p := &Router{ds: ds, artwork: artwork, streamer: streamer, decider: decider, share: share, archiver: archiver}
shareRoot := path.Join(conf.Server.BasePath, consts.URLPathPublic)
p.assetsHandler = http.StripPrefix(shareRoot, http.FileServer(http.FS(ui.BuildAssets())))
p.Handler = p.routes()

View file

@ -58,6 +58,8 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
"uiSearchDebounceMs": conf.Server.UISearchDebounceMs,
"uiCoverArtSize": conf.Server.UICoverArtSize,
"enableCoverAnimation": conf.Server.EnableCoverAnimation,
"pidAlbum": conf.Server.PID.Album,
"pidTrack": conf.Server.PID.Track,
"enableNowPlaying": conf.Server.EnableNowPlaying,
"playbackReportIntervalMs": conf.Server.UIPlaybackReportInterval.Milliseconds(),
"gaTrackingId": conf.Server.GATrackingID,

View file

@ -89,6 +89,8 @@ var _ = Describe("serveIndex", func() {
Entry("uiSearchDebounceMs", func() { conf.Server.UISearchDebounceMs = 500 }, "uiSearchDebounceMs", float64(500)),
Entry("uiCoverArtSize", func() { conf.Server.UICoverArtSize = 300 }, "uiCoverArtSize", float64(300)),
Entry("enableCoverAnimation", func() { conf.Server.EnableCoverAnimation = true }, "enableCoverAnimation", true),
Entry("pidAlbum", func() { conf.Server.PID.Album = "folder" }, "pidAlbum", "folder"),
Entry("pidTrack", func() { conf.Server.PID.Track = "title" }, "pidTrack", "title"),
Entry("enableNowPlaying", func() { conf.Server.EnableNowPlaying = true }, "enableNowPlaying", true),
Entry("gaTrackingId", func() { conf.Server.GATrackingID = "UA-12345" }, "gaTrackingId", "UA-12345"),
Entry("defaultDownloadableShare", func() { conf.Server.DefaultDownloadableShare = true }, "defaultDownloadableShare", true),

View file

@ -137,7 +137,7 @@ var _ = Describe("Artwork Serving", Ordered, func() {
artRouter = buildArtworkRouter(artSvc)
router = artRouter // so the shared doReq/doRawReq helpers hit the artwork-wired router
pubRouter = public.New(ds, artSvc, streamerSpy, core.NewShare(ds), noopArchiver{})
pubRouter = public.New(ds, artSvc, streamerSpy, stream.NewTranscodeDecider(ds, ffm), core.NewShare(ds), noopArchiver{})
})
It("emits a bare optimistic coverArt id before the queue is drained", func() {