mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix(transcoding): don't apply server-side override on getTranscodeDecision (#5473)
* fix(transcoding): don't apply server-side transcoding override on getTranscodeDecision The getTranscodeDecision endpoint was incorrectly applying server-side player transcoding overrides (forced format and MaxBitRate cap), which replaced the client's declared capabilities with synthetic profiles. This caused the endpoint to ignore what the client can actually play and return decisions for formats the client never requested (e.g. AAC when the client only supports FLAC/opus/mp3). The override is now gated behind an ApplyServerOverride flag in TranscodeOptions, which is only set by the legacy stream endpoint where this behavior is expected. Signed-off-by: Deluan <deluan@navidrome.org> * refactor: move server-side transcoding override to ResolveRequest Moved the server-side player transcoding override logic (forced format and MaxBitRate cap) from MakeDecision into ResolveRequest, where the legacy stream context is handled. This makes MakeDecision a pure function that only operates on the ClientInfo it receives, removing the ApplyServerOverride flag and all context-sniffing from the decision engine. Tests moved accordingly to legacy_client_test.go. * test(e2e): update transcode decision tests for server override removal Updated e2e tests to reflect that getTranscodeDecision no longer applies server-side player overrides (MaxBitRate cap and forced transcoding profile). The player MaxBitRate tests now verify the endpoint ignores the player cap and relies solely on client-declared capabilities. * test(e2e): assert opus default bitrate when player cap is ignored Added bitrate assertion to verify the player MaxBitRate cap is truly ignored: the target bitrate should be the opus format default (128kbps), not the player cap (320kbps). --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
0cb476266e
commit
6766d60bee
@ -59,18 +59,6 @@ func (s *deciderService) MakeDecision(ctx context.Context, mf *model.MediaFile,
|
||||
decision.SourceStream = buildSourceStream(mf, probe)
|
||||
src := &decision.SourceStream
|
||||
|
||||
// Check for server-side player transcoding override
|
||||
if trc, ok := request.TranscodingFrom(ctx); ok && trc.TargetFormat != "" {
|
||||
clientInfo = applyServerOverride(ctx, clientInfo, &trc)
|
||||
} else if player, ok := request.PlayerFrom(ctx); ok && player.MaxBitRate > 0 {
|
||||
if clientInfo.MaxAudioBitrate == 0 || player.MaxBitRate < clientInfo.MaxAudioBitrate {
|
||||
modified := *clientInfo
|
||||
modified.MaxAudioBitrate = player.MaxBitRate
|
||||
clientInfo = &modified
|
||||
log.Debug(ctx, "Applied player MaxBitRate cap", "playerMaxBitRate", player.MaxBitRate, "client", clientInfo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace(ctx, "Making transcode decision", "mediaID", mf.ID, "container", src.Container,
|
||||
"codec", src.Codec, "bitrate", src.Bitrate, "channels", src.Channels,
|
||||
"sampleRate", src.SampleRate, "lossless", src.IsLossless, "client", clientInfo.Name)
|
||||
|
||||
@ -1042,8 +1042,8 @@ var _ = Describe("Decider", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Context("Server-side player transcoding override", func() {
|
||||
It("forces transcoding when override targets a different format", func() {
|
||||
Context("Server-side context is ignored by MakeDecision", func() {
|
||||
It("ignores transcoding override in context", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
@ -1051,148 +1051,21 @@ var _ = Describe("Decider", func() {
|
||||
{Containers: []string{"flac"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
}
|
||||
// Set server override in context
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decision, err := svc.MakeDecision(overrideCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanDirectPlay).To(BeFalse())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
Expect(decision.TargetFormat).To(Equal("mp3"))
|
||||
Expect(decision.TargetBitrate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("allows direct play when source matches forced format and bitrate is within cap", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 128, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
DirectPlayProfiles: []DirectPlayProfile{
|
||||
{Containers: []string{"flac"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
}
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 256})
|
||||
|
||||
decision, err := svc.MakeDecision(overrideCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanDirectPlay).To(BeTrue())
|
||||
Expect(decision.CanTranscode).To(BeFalse())
|
||||
})
|
||||
|
||||
It("transcodes when source bitrate exceeds the forced cap", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
}
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
|
||||
decision, err := svc.MakeDecision(overrideCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanDirectPlay).To(BeFalse())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
Expect(decision.TargetFormat).To(Equal("mp3"))
|
||||
Expect(decision.TargetBitrate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("uses player MaxBitRate over transcoding DefaultBitRate", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
}
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 320})
|
||||
|
||||
decision, err := svc.MakeDecision(overrideCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
Expect(decision.TargetFormat).To(Equal("mp3"))
|
||||
Expect(decision.TargetBitrate).To(Equal(320))
|
||||
})
|
||||
|
||||
It("applies no bitrate cap when both MaxBitRate and DefaultBitRate are 0", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
}
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 0})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decision, err := svc.MakeDecision(overrideCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
Expect(decision.TargetFormat).To(Equal("mp3"))
|
||||
// With no cap, lossless→lossy uses format default bitrate (160 for mp3 from mock)
|
||||
Expect(decision.TargetBitrate).To(Equal(160))
|
||||
})
|
||||
|
||||
It("does not apply override when no transcoding is in context", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
DirectPlayProfiles: []DirectPlayProfile{
|
||||
{Containers: []string{"flac"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
}
|
||||
// No override in context — client profiles used as-is
|
||||
decision, err := svc.MakeDecision(ctx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanDirectPlay).To(BeTrue())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Context("Player MaxBitRate cap", func() {
|
||||
It("applies player MaxBitRate cap when client has no limit", func() {
|
||||
It("ignores player MaxBitRate in context", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100, BitDepth: 16})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
DirectPlayProfiles: []DirectPlayProfile{
|
||||
{Containers: []string{"flac", "mp3"}, AudioCodecs: []string{"flac", "mp3"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
TranscodingProfiles: []Profile{
|
||||
{Container: "mp3", AudioCodec: "mp3", Protocol: ProtocolHTTP},
|
||||
{Containers: []string{"flac"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
}
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 320})
|
||||
|
||||
decision, err := svc.MakeDecision(playerCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// Source bitrate 1000 > player cap 320, so direct play is not possible
|
||||
Expect(decision.CanDirectPlay).To(BeFalse())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
// Lossless→lossy should use MaxAudioBitrate (320) as target, not format default
|
||||
Expect(decision.TargetBitrate).To(Equal(320))
|
||||
})
|
||||
|
||||
It("uses client limit when it is more restrictive than player MaxBitRate", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100, BitDepth: 16})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
MaxAudioBitrate: 256,
|
||||
MaxTranscodingAudioBitrate: 256,
|
||||
TranscodingProfiles: []Profile{
|
||||
{Container: "mp3", AudioCodec: "mp3", Protocol: ProtocolHTTP},
|
||||
},
|
||||
}
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 500})
|
||||
|
||||
decision, err := svc.MakeDecision(playerCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanTranscode).To(BeTrue())
|
||||
// Client limit 256 < player cap 500, so player cap doesn't apply; client limit wins
|
||||
Expect(decision.TargetBitrate).To(Equal(256))
|
||||
})
|
||||
|
||||
It("does not cap when player MaxBitRate is 0", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100})
|
||||
ci := &ClientInfo{
|
||||
Name: "TestClient",
|
||||
DirectPlayProfiles: []DirectPlayProfile{
|
||||
{Containers: []string{"mp3"}, AudioCodecs: []string{"mp3"}, Protocols: []string{ProtocolHTTP}},
|
||||
},
|
||||
}
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decision, err := svc.MakeDecision(playerCtx, mf, ci, TranscodeOptions{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(decision.CanDirectPlay).To(BeTrue())
|
||||
|
||||
@ -7,12 +7,11 @@ import (
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
)
|
||||
|
||||
// buildLegacyClientInfo translates legacy Subsonic stream/download parameters
|
||||
// into a ClientInfo for use with MakeDecision.
|
||||
// It does NOT read request.TranscodingFrom(ctx) — that is handled by
|
||||
// MakeDecision's applyServerOverride.
|
||||
func buildLegacyClientInfo(mf *model.MediaFile, reqFormat string, reqBitRate int) *ClientInfo {
|
||||
ci := &ClientInfo{Name: "legacy"}
|
||||
|
||||
@ -65,6 +64,19 @@ func (s *deciderService) ResolveRequest(ctx context.Context, mf *model.MediaFile
|
||||
}
|
||||
|
||||
clientInfo := buildLegacyClientInfo(mf, reqFormat, reqBitRate)
|
||||
|
||||
// Apply server-side player transcoding override before making the decision
|
||||
if trc, ok := request.TranscodingFrom(ctx); ok && trc.TargetFormat != "" {
|
||||
clientInfo = applyServerOverride(ctx, clientInfo, &trc)
|
||||
} else if player, ok := request.PlayerFrom(ctx); ok && player.MaxBitRate > 0 {
|
||||
if clientInfo.MaxAudioBitrate == 0 || player.MaxBitRate < clientInfo.MaxAudioBitrate {
|
||||
modified := *clientInfo
|
||||
modified.MaxAudioBitrate = player.MaxBitRate
|
||||
clientInfo = &modified
|
||||
log.Debug(ctx, "Applied player MaxBitRate cap", "playerMaxBitRate", player.MaxBitRate, "client", clientInfo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
decision, err := s.MakeDecision(ctx, mf, clientInfo, TranscodeOptions{SkipProbe: true})
|
||||
if err != nil {
|
||||
log.Error(ctx, "Error making transcode decision, falling back to raw", "id", mf.ID, err)
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -187,6 +188,109 @@ var _ = Describe("ResolveRequest", func() {
|
||||
Expect(req.Offset).To(Equal(30))
|
||||
})
|
||||
|
||||
Context("Server-side player transcoding override", func() {
|
||||
It("forces transcoding when override targets a different format", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(overrideCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
Expect(req.BitRate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("allows direct play when source matches forced format and bitrate is within cap", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 128, Channels: 2, SampleRate: 44100})
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 256})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(overrideCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("raw"))
|
||||
})
|
||||
|
||||
It("transcodes when source bitrate exceeds the forced cap", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100})
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(overrideCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
Expect(req.BitRate).To(Equal(192))
|
||||
})
|
||||
|
||||
It("uses player MaxBitRate over transcoding DefaultBitRate", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 192})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 320})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(overrideCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
Expect(req.BitRate).To(Equal(320))
|
||||
})
|
||||
|
||||
It("applies no bitrate cap when both MaxBitRate and DefaultBitRate are 0", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
overrideCtx := request.WithTranscoding(ctx, model.Transcoding{TargetFormat: "mp3", DefaultBitRate: 0})
|
||||
overrideCtx = request.WithPlayer(overrideCtx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(overrideCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
// With no cap, lossless→lossy uses format default bitrate (160 for mp3 from mock)
|
||||
Expect(req.BitRate).To(Equal(160))
|
||||
})
|
||||
|
||||
It("does not apply override when no transcoding is in context", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(ctx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("raw"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Player MaxBitRate cap", func() {
|
||||
It("applies player MaxBitRate cap when client has no limit", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100, BitDepth: 16})
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 320})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(playerCtx, mf, "mp3", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
Expect(req.BitRate).To(Equal(320))
|
||||
})
|
||||
|
||||
It("uses client limit when it is more restrictive than player MaxBitRate", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "flac", Codec: "FLAC", BitRate: 1000, Channels: 2, SampleRate: 44100, BitDepth: 16})
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 500})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(playerCtx, mf, "mp3", 256, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("mp3"))
|
||||
Expect(req.BitRate).To(Equal(256))
|
||||
})
|
||||
|
||||
It("does not cap when player MaxBitRate is 0", func() {
|
||||
mf := withProbe(&model.MediaFile{ID: "1", Suffix: "mp3", Codec: "MP3", BitRate: 320, Channels: 2, SampleRate: 44100})
|
||||
playerCtx := request.WithPlayer(ctx, model.Player{MaxBitRate: 0})
|
||||
|
||||
decider := svc.(*deciderService)
|
||||
req := decider.ResolveRequest(playerCtx, mf, "", 0, 0)
|
||||
|
||||
Expect(req.Format).To(Equal("raw"))
|
||||
})
|
||||
})
|
||||
|
||||
Context("fallback for unknown format", func() {
|
||||
It("falls back to DefaultDownsamplingFormat", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
|
||||
@ -396,68 +396,30 @@ var _ = Describe("Transcode Endpoints", Ordered, func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("player MaxBitRate cap", func() {
|
||||
It("forces transcode when source bitrate exceeds player MaxBitRate", func() {
|
||||
Describe("player MaxBitRate cap is ignored", func() {
|
||||
It("allows direct play even when source bitrate exceeds player MaxBitRate", func() {
|
||||
setPlayerMaxBitRate(320) // 320 kbps cap
|
||||
|
||||
// FLAC is 900kbps, client has no bitrate limit but player cap is 320
|
||||
// FLAC is 900kbps, player cap is 320, but getTranscodeDecision
|
||||
// ignores server-side overrides — client profiles are used as-is
|
||||
resp := doPostReq("getTranscodeDecision", flacAndMp3Client, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanDirectPlay).To(BeFalse())
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.Container).To(Equal("mp3"))
|
||||
// Target bitrate should be capped at player's 320kbps = 320000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
|
||||
})
|
||||
|
||||
It("does not affect direct play when source bitrate is under player MaxBitRate", func() {
|
||||
setPlayerMaxBitRate(500) // 500 kbps cap
|
||||
|
||||
// MP3 is 320kbps, under the 500kbps player cap → direct play
|
||||
resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", mp3TrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
|
||||
})
|
||||
|
||||
It("uses client limit when more restrictive than player MaxBitRate", func() {
|
||||
setPlayerMaxBitRate(500) // 500 kbps player cap
|
||||
|
||||
// Client caps at 320kbps (bitrateCapClient), which is more restrictive than 500
|
||||
// FLAC is 900kbps → exceeds both limits → transcode
|
||||
resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
|
||||
// Client limit (320kbps) is more restrictive → 320000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
|
||||
})
|
||||
|
||||
It("uses player MaxBitRate when more restrictive than client limit", func() {
|
||||
It("uses only client limit, not player MaxBitRate", func() {
|
||||
setPlayerMaxBitRate(192) // 192 kbps player cap
|
||||
|
||||
// Client caps at 320kbps (bitrateCapClient), player is more restrictive at 192
|
||||
// FLAC is 900kbps → transcode at 192kbps
|
||||
// but getTranscodeDecision ignores player cap → client limit (320kbps) applies
|
||||
resp := doPostReq("getTranscodeDecision", bitrateCapClient, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
|
||||
// Player limit (192kbps) is more restrictive → 192000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
|
||||
})
|
||||
|
||||
It("has no effect when player MaxBitRate is 0", func() {
|
||||
setPlayerMaxBitRate(0) // No player cap
|
||||
|
||||
// FLAC with flac+mp3 client → direct play (no bitrate constraint)
|
||||
resp := doPostReq("getTranscodeDecision", flacAndMp3Client, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanDirectPlay).To(BeTrue())
|
||||
// Only client limit (320kbps) applies → 320000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
|
||||
})
|
||||
})
|
||||
|
||||
@ -513,56 +475,37 @@ var _ = Describe("Transcode Endpoints", Ordered, func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("player MaxBitRate + client limits combined", func() {
|
||||
It("player MaxBitRate injects maxAudioBitrate, format default used for transcode target", func() {
|
||||
Describe("player MaxBitRate is ignored by getTranscodeDecision", func() {
|
||||
It("does not inject maxAudioBitrate from player cap", func() {
|
||||
setPlayerMaxBitRate(320)
|
||||
|
||||
// opusTranscodeClient has no client bitrate limits
|
||||
// Player cap injects maxAudioBitrate=320
|
||||
// FLAC (900kbps) → exceeds 320 → transcode to opus
|
||||
// Lossless→lossy: maxTranscodingAudioBitrate=0, so falls back to maxAudioBitrate=320
|
||||
// Player cap is 320, but getTranscodeDecision ignores it
|
||||
// FLAC (900kbps) → can't direct play → transcode to opus using format default
|
||||
resp := doPostReq("getTranscodeDecision", opusTranscodeClient, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.Codec).To(Equal("opus"))
|
||||
// maxAudioBitrate=320 used as fallback → 320000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(320000)))
|
||||
// Bitrate should be opus format default (128kbps), not player cap (320kbps)
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(128000)))
|
||||
})
|
||||
|
||||
It("player MaxBitRate + client maxTranscodingAudioBitrate work together", func() {
|
||||
It("uses only client maxTranscodingAudioBitrate, ignoring player cap", func() {
|
||||
setPlayerMaxBitRate(320)
|
||||
|
||||
// maxTranscodeBitrateClient: maxTranscodingAudioBitrate=192000 (192kbps), no maxAudioBitrate
|
||||
// Player cap injects maxAudioBitrate=320
|
||||
// FLAC (900kbps) → exceeds 320 → transcode to mp3
|
||||
// Lossless→lossy: maxTranscodingAudioBitrate=192 takes priority
|
||||
// maxTranscodeBitrateClient: maxTranscodingAudioBitrate=192000 (192kbps)
|
||||
// Player cap is 320, but getTranscodeDecision ignores it
|
||||
// Only client maxTranscodingAudioBitrate=192 applies
|
||||
resp := doPostReq("getTranscodeDecision", maxTranscodeBitrateClient, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision).ToNot(BeNil())
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
Expect(resp.TranscodeDecision.TranscodeStream).ToNot(BeNil())
|
||||
// maxTranscodingAudioBitrate=192 is preferred → 192000 bps
|
||||
// maxTranscodingAudioBitrate=192 → 192000 bps
|
||||
Expect(resp.TranscodeDecision.TranscodeStream.AudioBitrate).To(Equal(int32(192000)))
|
||||
})
|
||||
|
||||
It("streams with correct bitrate after player MaxBitRate-triggered transcode", func() {
|
||||
setPlayerMaxBitRate(128)
|
||||
|
||||
// Get decision: FLAC (900kbps) with player cap 128 → transcode
|
||||
resp := doPostReq("getTranscodeDecision", mp3OnlyClient, "mediaId", flacTrackID, "mediaType", "song")
|
||||
Expect(resp.Status).To(Equal(responses.StatusOK))
|
||||
Expect(resp.TranscodeDecision.CanTranscode).To(BeTrue())
|
||||
token := resp.TranscodeDecision.TranscodeParams
|
||||
Expect(token).ToNot(BeEmpty())
|
||||
|
||||
// Stream using the token
|
||||
w := doRawReq("getTranscodeStream", "mediaId", flacTrackID, "mediaType", "song", "transcodeParams", token)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(streamerSpy.LastRequest.Format).To(Equal("mp3"))
|
||||
Expect(streamerSpy.LastRequest.BitRate).To(Equal(128))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user