mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-28 03:19:38 +00:00
fix: prevent raw file being returned when explicit transcode format is requested
When a client requests transcoding with an explicit format (e.g., format=opus) but no maxBitRate, buildLegacyClientInfo was adding a direct play profile matching the source format. Since there was no bitrate constraint to block it, MakeDecision would match the source against the direct play profile and return the raw file instead of transcoding. This fix only adds the direct play profile when no explicit format was requested (bitrate-only downsampling) or when the requested format matches the source format (allowing direct play when no actual transcoding is needed).
This commit is contained in:
parent
767744a301
commit
053a0fd6c0
3 changed files with 154 additions and 5 deletions
|
|
@ -2,6 +2,7 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
|
|
@ -25,8 +26,15 @@ func buildLegacyClientInfo(mf *model.MediaFile, reqFormat string, reqBitRate int
|
|||
}
|
||||
|
||||
if targetFormat != "" {
|
||||
ci.DirectPlayProfiles = []DirectPlayProfile{
|
||||
{Containers: []string{mf.Suffix}, AudioCodecs: []string{mf.AudioCodec()}, Protocols: []string{ProtocolHTTP}},
|
||||
// Add a direct play profile for the source format when no explicit
|
||||
// format was requested (bitrate-only downsampling) or when the
|
||||
// requested format matches the source. When the client explicitly
|
||||
// requests a different format, direct play must not match the
|
||||
// source — otherwise the source is returned untranscoded.
|
||||
if reqFormat == "" || strings.EqualFold(reqFormat, mf.Suffix) {
|
||||
ci.DirectPlayProfiles = []DirectPlayProfile{
|
||||
{Containers: []string{mf.Suffix}, AudioCodecs: []string{mf.AudioCodec()}, Protocols: []string{ProtocolHTTP}},
|
||||
}
|
||||
}
|
||||
ci.TranscodingProfiles = []Profile{
|
||||
{Container: targetFormat, AudioCodec: targetFormat, Protocol: ProtocolHTTP},
|
||||
|
|
|
|||
|
|
@ -25,10 +25,25 @@ var _ = Describe("buildLegacyClientInfo", func() {
|
|||
Expect(ci.TranscodingProfiles[0].Protocol).To(Equal(ProtocolHTTP))
|
||||
Expect(ci.MaxAudioBitrate).To(BeZero())
|
||||
Expect(ci.MaxTranscodingAudioBitrate).To(BeZero())
|
||||
Expect(ci.DirectPlayProfiles).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("does not add direct play profile when explicit format differs from source (no bitrate)", func() {
|
||||
ci := buildLegacyClientInfo(mf, "opus", 0)
|
||||
|
||||
Expect(ci.TranscodingProfiles).To(HaveLen(1))
|
||||
Expect(ci.TranscodingProfiles[0].Container).To(Equal("opus"))
|
||||
Expect(ci.DirectPlayProfiles).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("adds direct play profile when explicit format matches source format", func() {
|
||||
ci := buildLegacyClientInfo(mf, "flac", 0)
|
||||
|
||||
Expect(ci.TranscodingProfiles).To(HaveLen(1))
|
||||
Expect(ci.TranscodingProfiles[0].Container).To(Equal("flac"))
|
||||
Expect(ci.DirectPlayProfiles).To(HaveLen(1))
|
||||
Expect(ci.DirectPlayProfiles[0].Containers).To(Equal([]string{"flac"}))
|
||||
Expect(ci.DirectPlayProfiles[0].AudioCodecs).To(Equal([]string{mf.AudioCodec()}))
|
||||
Expect(ci.DirectPlayProfiles[0].Protocols).To(Equal([]string{ProtocolHTTP}))
|
||||
})
|
||||
|
||||
It("sets transcoding profile and bitrate for explicit format with bitrate", func() {
|
||||
|
|
@ -39,8 +54,7 @@ var _ = Describe("buildLegacyClientInfo", func() {
|
|||
Expect(ci.TranscodingProfiles[0].AudioCodec).To(Equal("mp3"))
|
||||
Expect(ci.MaxAudioBitrate).To(Equal(192))
|
||||
Expect(ci.MaxTranscodingAudioBitrate).To(Equal(192))
|
||||
Expect(ci.DirectPlayProfiles).To(HaveLen(1))
|
||||
Expect(ci.DirectPlayProfiles[0].Containers).To(Equal([]string{"flac"}))
|
||||
Expect(ci.DirectPlayProfiles).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("returns direct play profile when no format and no bitrate", func() {
|
||||
|
|
|
|||
127
server/e2e/subsonic_stream_test.go
Normal file
127
server/e2e/subsonic_stream_test.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("stream.view (legacy streaming)", Ordered, func() {
|
||||
var (
|
||||
mp3TrackID string // Come Together (mp3, 320kbps)
|
||||
flacTrackID string // TC FLAC Standard (flac, 900kbps)
|
||||
)
|
||||
|
||||
BeforeAll(func() {
|
||||
setupTestDB()
|
||||
|
||||
songs, err := ds.MediaFile(ctx).GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
byTitle := map[string]string{}
|
||||
for _, s := range songs {
|
||||
byTitle[s.Title] = s.ID
|
||||
}
|
||||
mp3TrackID = byTitle["Come Together"]
|
||||
Expect(mp3TrackID).ToNot(BeEmpty())
|
||||
flacTrackID = byTitle["TC FLAC Standard"]
|
||||
Expect(flacTrackID).ToNot(BeEmpty())
|
||||
})
|
||||
|
||||
Describe("raw / direct play", func() {
|
||||
It("streams raw when no format or maxBitRate is specified", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(BeElementOf("raw", ""))
|
||||
})
|
||||
|
||||
It("streams raw when format=raw is explicitly requested", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "raw")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(BeElementOf("raw", ""))
|
||||
})
|
||||
|
||||
It("streams raw when maxBitRate is >= source bitrate", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "maxBitRate", "1000")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(BeElementOf("raw", ""))
|
||||
})
|
||||
|
||||
It("streams raw when format matches source and bitrate is not lower", func() {
|
||||
w := doRawReq("stream", "id", mp3TrackID, "format", "mp3", "maxBitRate", "320")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("raw"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("transcoding with explicit format", func() {
|
||||
It("transcodes to mp3 when format=mp3 is requested", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "mp3")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("mp3"))
|
||||
// Should use the mp3 default bitrate (192kbps)
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("transcodes to opus when format=opus is requested (no maxBitRate)", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "opus")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("opus"))
|
||||
// Should use the opus default bitrate (128kbps)
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(128))
|
||||
})
|
||||
|
||||
It("transcodes to opus with specified maxBitRate", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "opus", "maxBitRate", "192")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("opus"))
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("transcodes to mp3 with specified maxBitRate", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "mp3", "maxBitRate", "128")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("mp3"))
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(128))
|
||||
})
|
||||
|
||||
It("transcodes MP3 to opus when format=opus is requested", func() {
|
||||
w := doRawReq("stream", "id", mp3TrackID, "format", "opus")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("opus"))
|
||||
})
|
||||
|
||||
It("transcodes same format when maxBitRate is lower than source", func() {
|
||||
w := doRawReq("stream", "id", mp3TrackID, "format", "mp3", "maxBitRate", "128")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("mp3"))
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(128))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("downsampling with maxBitRate only", func() {
|
||||
It("transcodes using default downsampling format when maxBitRate < source bitrate", func() {
|
||||
conf.Server.DefaultDownsamplingFormat = "opus"
|
||||
w := doRawReq("stream", "id", flacTrackID, "maxBitRate", "192")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(Equal("opus"))
|
||||
Expect(spy.LastRequest.BitRate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("streams raw when maxBitRate >= source bitrate (no downsampling needed)", func() {
|
||||
conf.Server.DefaultDownsamplingFormat = "opus"
|
||||
w := doRawReq("stream", "id", mp3TrackID, "maxBitRate", "320")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Format).To(BeElementOf("raw", ""))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("timeOffset", func() {
|
||||
It("passes timeOffset to the stream request", func() {
|
||||
w := doRawReq("stream", "id", flacTrackID, "format", "mp3", "timeOffset", "30")
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(spy.LastRequest.Offset).To(Equal(30))
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue