053a0fd6c0
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).
93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
package stream
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/navidrome/navidrome/conf"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
)
|
|
|
|
// 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"}
|
|
|
|
// Determine target format for transcoding
|
|
var targetFormat string
|
|
switch {
|
|
case reqFormat != "":
|
|
targetFormat = reqFormat
|
|
case reqBitRate > 0 && reqBitRate < mf.BitRate && conf.Server.DefaultDownsamplingFormat != "":
|
|
targetFormat = conf.Server.DefaultDownsamplingFormat
|
|
}
|
|
|
|
if targetFormat != "" {
|
|
// 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},
|
|
}
|
|
if reqBitRate > 0 {
|
|
ci.MaxAudioBitrate = reqBitRate
|
|
ci.MaxTranscodingAudioBitrate = reqBitRate
|
|
}
|
|
} else {
|
|
// No transcoding requested — direct play everything
|
|
ci.DirectPlayProfiles = []DirectPlayProfile{
|
|
{Protocols: []string{ProtocolHTTP}},
|
|
}
|
|
}
|
|
|
|
return ci
|
|
}
|
|
|
|
// ResolveRequest uses MakeDecision to resolve legacy Subsonic stream parameters
|
|
// into a fully specified Request.
|
|
func (s *deciderService) ResolveRequest(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int, offset int) Request {
|
|
var req Request
|
|
req.Offset = offset
|
|
|
|
if reqFormat == "raw" {
|
|
req.Format = "raw"
|
|
return req
|
|
}
|
|
|
|
clientInfo := buildLegacyClientInfo(mf, reqFormat, reqBitRate)
|
|
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)
|
|
req.Format = "raw"
|
|
return req
|
|
}
|
|
|
|
if decision.CanDirectPlay {
|
|
req.Format = "raw"
|
|
return req
|
|
}
|
|
|
|
if decision.CanTranscode {
|
|
req.Format = decision.TargetFormat
|
|
req.BitRate = decision.TargetBitrate
|
|
req.SampleRate = decision.TargetSampleRate
|
|
req.BitDepth = decision.TargetBitDepth
|
|
req.Channels = decision.TargetChannels
|
|
return req
|
|
}
|
|
|
|
// No compatible profile — fallback to raw
|
|
req.Format = "raw"
|
|
return req
|
|
}
|