diff options
Diffstat (limited to 'MediaBrowser.Model/Dlna/StreamBuilder.cs')
| -rw-r--r-- | MediaBrowser.Model/Dlna/StreamBuilder.cs | 660 |
1 files changed, 362 insertions, 298 deletions
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 6e9b943f7..0a955e917 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1,6 +1,3 @@ -#nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -13,6 +10,9 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Model.Dlna { + /// <summary> + /// Class StreamBuilder. + /// </summary> public class StreamBuilder { // Aliases @@ -23,76 +23,182 @@ namespace MediaBrowser.Model.Dlna private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; - + private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" }; + private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; + private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; + + /// <summary> + /// Initializes a new instance of the <see cref="StreamBuilder"/> class. + /// </summary> + /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/> object.</param> + /// <param name="logger">The <see cref="ILogger"/> object.</param> public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) { _transcoderSupport = transcoderSupport; _logger = logger; } - public StreamBuilder(ILogger<StreamBuilder> logger) - : this(new FullTranscoderSupport(), logger) + /// <summary> + /// Gets the optimal audio stream. + /// </summary> + /// <param name="options">The <see cref="MediaOptions"/> object to get the audio stream from.</param> + /// <returns>The <see cref="StreamInfo"/> of the optimal audio stream.</returns> + public StreamInfo? GetOptimalAudioStream(MediaOptions options) { + ValidateMediaOptions(options, false); + + var streams = new List<StreamInfo>(); + foreach (var mediaSource in options.MediaSources) + { + if (!(string.IsNullOrEmpty(options.MediaSourceId) + || string.Equals(mediaSource.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + StreamInfo? streamInfo = GetOptimalAudioStream(mediaSource, options); + if (streamInfo is not null) + { + streamInfo.DeviceId = options.DeviceId; + streamInfo.DeviceProfileId = options.Profile.Id; + streams.Add(streamInfo); + } + } + + return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0); } - public StreamInfo BuildAudioItem(AudioOptions options) + private StreamInfo? GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options) { - ValidateAudioInput(options); + var playlistItem = new StreamInfo + { + ItemId = options.ItemId, + MediaType = DlnaProfileType.Audio, + MediaSource = item, + RunTimeTicks = item.RunTimeTicks, + Context = options.Context, + DeviceProfile = options.Profile + }; - var mediaSources = new List<MediaSourceInfo>(); - foreach (MediaSourceInfo i in options.MediaSources) + if (options.ForceDirectPlay) + { + playlistItem.PlayMethod = PlayMethod.DirectPlay; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); + return playlistItem; + } + + if (options.ForceDirectStream) { - if (string.IsNullOrEmpty(options.MediaSourceId) || - string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + playlistItem.PlayMethod = PlayMethod.DirectStream; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); + return playlistItem; + } + + MediaStream audioStream = item.GetDefaultAudioStream(null); + + var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options); + + var directPlayMethod = directPlayInfo.PlayMethod; + var transcodeReasons = directPlayInfo.TranscodeReasons; + + var inputAudioChannels = audioStream?.Channels; + var inputAudioBitrate = audioStream?.BitRate; + var inputAudioSampleRate = audioStream?.SampleRate; + var inputAudioBitDepth = audioStream?.BitDepth; + + if (directPlayMethod.HasValue) + { + var profile = options.Profile; + var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); + var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions); + transcodeReasons |= audioFailureReasons; + + if (audioFailureReasons == 0) { - mediaSources.Add(i); + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); + + return playlistItem; } } - var streams = new List<StreamInfo>(); - foreach (MediaSourceInfo i in mediaSources) + TranscodingProfile? transcodingProfile = null; + foreach (var tcProfile in options.Profile.TranscodingProfiles) { - StreamInfo streamInfo = BuildAudioItem(i, options); - if (streamInfo != null) + if (tcProfile.Type == playlistItem.MediaType + && tcProfile.Context == options.Context + && _transcoderSupport.CanEncodeToAudioCodec(tcProfile.AudioCodec ?? tcProfile.Container)) { - streams.Add(streamInfo); + transcodingProfile = tcProfile; + break; } } - foreach (StreamInfo stream in streams) + if (transcodingProfile != null) { - stream.DeviceId = options.DeviceId; - stream.DeviceProfileId = options.Profile.Id; + if (!item.SupportsTranscoding) + { + return null; + } + + SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); + + var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); + ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); + + // Honor requested max channels + playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; + + var configuredBitrate = options.GetMaxBitrate(true); + + long transcodingBitrate = options.AudioTranscodingBitrate + ?? (options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) + ?? configuredBitrate + ?? 128000; + + if (configuredBitrate.HasValue) + { + transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate); + } + + var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); + playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); } - return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0); + playlistItem.TranscodeReasons = transcodeReasons; + return playlistItem; } - public StreamInfo BuildVideoItem(VideoOptions options) + /// <summary> + /// Gets the optimal video stream. + /// </summary> + /// <param name="options">The <see cref="MediaOptions"/> object to get the video stream from.</param> + /// <returns>The <see cref="StreamInfo"/> of the optimal video stream.</returns> + public StreamInfo? GetOptimalVideoStream(MediaOptions options) { - ValidateInput(options); + ValidateMediaOptions(options, true); var mediaSources = new List<MediaSourceInfo>(); - foreach (MediaSourceInfo i in options.MediaSources) + foreach (var mediaSourceInfo in options.MediaSources) { - if (string.IsNullOrEmpty(options.MediaSourceId) || - string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(options.MediaSourceId) + || string.Equals(mediaSourceInfo.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase)) { - mediaSources.Add(i); + mediaSources.Add(mediaSourceInfo); } } var streams = new List<StreamInfo>(); - foreach (MediaSourceInfo i in mediaSources) + foreach (var mediaSourceInfo in mediaSources) { - var streamInfo = BuildVideoItem(i, options); - if (streamInfo != null) + var streamInfo = BuildVideoItem(mediaSourceInfo, options); + if (streamInfo is not null) { streams.Add(streamInfo); } } - foreach (StreamInfo stream in streams) + foreach (var stream in streams) { stream.DeviceId = options.DeviceId; stream.DeviceProfileId = options.Profile.Id; @@ -101,7 +207,7 @@ namespace MediaBrowser.Model.Dlna return GetOptimalStream(streams, options.GetMaxBitrate(false) ?? 0); } - private static StreamInfo GetOptimalStream(List<StreamInfo> streams, long maxBitrate) + private static StreamInfo? GetOptimalStream(List<StreamInfo> streams, long maxBitrate) => SortMediaSources(streams, maxBitrate).FirstOrDefault(); private static IOrderedEnumerable<StreamInfo> SortMediaSources(List<StreamInfo> streams, long maxBitrate) @@ -236,7 +342,15 @@ namespace MediaBrowser.Model.Dlna } } - public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null) + /// <summary> + /// Normalizes input container. + /// </summary> + /// <param name="inputContainer">The input container.</param> + /// <param name="profile">The <see cref="DeviceProfile"/>.</param> + /// <param name="type">The <see cref="DlnaProfileType"/>.</param> + /// <param name="playProfile">The <see cref="DirectPlayProfile"/> object to get the video stream from.</param> + /// <returns>The the normalized input container.</returns> + public static string? NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile? profile, DlnaProfileType type, DirectPlayProfile? playProfile = null) { if (string.IsNullOrEmpty(inputContainer)) { @@ -245,9 +359,9 @@ namespace MediaBrowser.Model.Dlna var formats = ContainerProfile.SplitValue(inputContainer); - if (profile != null) + if (profile is not null) { - var playProfiles = playProfile == null ? profile.DirectPlayProfiles : new[] { playProfile }; + var playProfiles = playProfile is null ? profile.DirectPlayProfiles : new[] { playProfile }; foreach (var format in formats) { foreach (var directPlayProfile in playProfiles) @@ -264,113 +378,12 @@ namespace MediaBrowser.Model.Dlna return formats[0]; } - private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options) - { - StreamInfo playlistItem = new StreamInfo - { - ItemId = options.ItemId, - MediaType = DlnaProfileType.Audio, - MediaSource = item, - RunTimeTicks = item.RunTimeTicks, - Context = options.Context, - DeviceProfile = options.Profile - }; - - if (options.ForceDirectPlay) - { - playlistItem.PlayMethod = PlayMethod.DirectPlay; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); - return playlistItem; - } - - if (options.ForceDirectStream) - { - playlistItem.PlayMethod = PlayMethod.DirectStream; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); - return playlistItem; - } - - var audioStream = item.GetDefaultAudioStream(null); - - var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options); - - var directPlayMethod = directPlayInfo.PlayMethod; - var transcodeReasons = directPlayInfo.TranscodeReasons; - - int? inputAudioChannels = audioStream?.Channels; - int? inputAudioBitrate = audioStream?.BitDepth; - int? inputAudioSampleRate = audioStream?.SampleRate; - int? inputAudioBitDepth = audioStream?.BitDepth; - - if (directPlayMethod.HasValue) - { - var profile = options.Profile; - var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); - var audioFailureReasons = AggregateFailureConditions(item, profile, "AudioCodecProfile", audioFailureConditions); - transcodeReasons |= audioFailureReasons; - - if (audioFailureReasons == 0) - { - playlistItem.PlayMethod = directPlayMethod.Value; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile); - - return playlistItem; - } - } - - TranscodingProfile transcodingProfile = null; - foreach (var i in options.Profile.TranscodingProfiles) - { - if (i.Type == playlistItem.MediaType - && i.Context == options.Context - && _transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container)) - { - transcodingProfile = i; - break; - } - } - - if (transcodingProfile != null) - { - if (!item.SupportsTranscoding) - { - return null; - } - - SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); - - var audioTranscodingConditions = GetProfileConditionsForAudio(options.Profile.CodecProfiles, transcodingProfile.Container, transcodingProfile.AudioCodec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, false).ToArray(); - ApplyTranscodingConditions(playlistItem, audioTranscodingConditions, null, true, true); - - // Honor requested max channels - playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - - var configuredBitrate = options.GetMaxBitrate(true); - - long transcodingBitrate = options.AudioTranscodingBitrate ?? - (options.Context == EncodingContext.Streaming ? options.Profile.MusicStreamingTranscodingBitrate : null) ?? - configuredBitrate ?? - 128000; - - if (configuredBitrate.HasValue) - { - transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate); - } - - var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate); - playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate); - } - - playlistItem.TranscodeReasons = transcodeReasons; - return playlistItem; - } - - private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, AudioOptions options) + private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, MediaOptions options) { var directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); - if (directPlayProfile == null) + if (directPlayProfile is null) { _logger.LogDebug( "Profile: {0}, No audio direct play profiles found for {1} with codec {2}", @@ -381,14 +394,13 @@ namespace MediaBrowser.Model.Dlna return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } - var playMethods = new List<PlayMethod>(); TranscodeReason transcodeReasons = 0; // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay) { - if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) + if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { if (options.EnableDirectPlay) { @@ -404,7 +416,7 @@ namespace MediaBrowser.Model.Dlna // While options takes the network and other factors into account. Only applies to direct stream if (item.SupportsDirectStream) { - if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) + if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { if (options.EnableDirectStream) { @@ -420,14 +432,13 @@ namespace MediaBrowser.Model.Dlna return (directPlayProfile, null, transcodeReasons); } - private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles) + private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream? videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles) { - var mediaType = videoStream == null ? DlnaProfileType.Audio : DlnaProfileType.Video; + var mediaType = videoStream is null ? DlnaProfileType.Audio : DlnaProfileType.Video; var containerSupported = false; var audioSupported = false; var videoSupported = false; - TranscodeReason reasons = 0; foreach (var profile in directPlayProfiles) { @@ -436,9 +447,9 @@ namespace MediaBrowser.Model.Dlna { containerSupported = true; - videoSupported = videoStream == null || profile.SupportsVideoCodec(videoStream.Codec); + videoSupported = videoStream is null || profile.SupportsVideoCodec(videoStream.Codec); - audioSupported = audioStream == null || profile.SupportsAudioCodec(audioStream.Codec); + audioSupported = audioStream is null || profile.SupportsAudioCodec(audioStream.Codec); if (videoSupported && audioSupported) { @@ -447,6 +458,7 @@ namespace MediaBrowser.Model.Dlna } } + TranscodeReason reasons = 0; if (!containerSupported) { reasons |= TranscodeReason.ContainerNotSupported; @@ -522,8 +534,7 @@ namespace MediaBrowser.Model.Dlna } playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels) - && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) + if (int.TryParse(transcodingProfile.MaxAudioChannels, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels)) { playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels; } @@ -547,7 +558,7 @@ namespace MediaBrowser.Model.Dlna } } - private static void SetStreamInfoOptionsFromDirectPlayProfile(VideoOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile) + private static void SetStreamInfoOptionsFromDirectPlayProfile(MediaOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile? directPlayProfile) { var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile); var protocol = "http"; @@ -559,10 +570,10 @@ namespace MediaBrowser.Model.Dlna playlistItem.SubProtocol = protocol; playlistItem.VideoCodecs = new[] { item.VideoStream.Codec }; - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); } - private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options) + private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options) { ArgumentNullException.ThrowIfNull(item); @@ -580,13 +591,13 @@ namespace MediaBrowser.Model.Dlna var subtitleStream = playlistItem.SubtitleStreamIndex.HasValue ? item.GetMediaStream(MediaStreamType.Subtitle, playlistItem.SubtitleStreamIndex.Value) : null; var audioStream = item.GetDefaultAudioStream(options.AudioStreamIndex ?? item.DefaultAudioStreamIndex); - if (audioStream != null) + if (audioStream is not null) { playlistItem.AudioStreamIndex = audioStream.Index; } // Collect candidate audio streams - ICollection<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream }; + ICollection<MediaStream> candidateAudioStreams = audioStream is null ? Array.Empty<MediaStream>() : new[] { audioStream }; if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) { if (audioStream?.IsDefault == true) @@ -601,11 +612,21 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; - var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); - var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); - bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0); - bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0); - var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility; + var bitrateLimitExceeded = IsBitrateLimitExceeded(item, options.GetMaxBitrate(false) ?? 0); + var isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || !bitrateLimitExceeded); + var isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || !bitrateLimitExceeded); + TranscodeReason transcodeReasons = 0; + + // Force transcode or remux for BD/DVD folders + if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay) + { + isEligibleForDirectPlay = false; + } + + if (bitrateLimitExceeded) + { + transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit; + } _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", @@ -614,7 +635,7 @@ namespace MediaBrowser.Model.Dlna isEligibleForDirectPlay, isEligibleForDirectStream); - DirectPlayProfile directPlayProfile = null; + DirectPlayProfile? directPlayProfile = null; if (isEligibleForDirectPlay || isEligibleForDirectStream) { // See if it can be direct played @@ -643,18 +664,18 @@ namespace MediaBrowser.Model.Dlna else if (directPlay == PlayMethod.DirectStream) { playlistItem.AudioStreamIndex = audioStream?.Index; - if (audioStream != null) + if (audioStream is not null) { - playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec); + playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile?.AudioCodec); } SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile); - BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile.Container, directPlayProfile.VideoCodec, directPlayProfile.AudioCodec); + BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile?.Container, directPlayProfile?.VideoCodec, directPlayProfile?.AudioCodec); } - if (subtitleStream != null) + if (subtitleStream is not null) { - var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile.Container, null); + var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile?.Container, null); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; @@ -678,7 +699,7 @@ namespace MediaBrowser.Model.Dlna // Can't direct play, find the transcoding profile // If we do this for direct-stream we will overwrite the info var transcodingProfile = GetVideoTranscodeProfile(item, options, videoStream, audioStream, candidateAudioStreams, subtitleStream, playlistItem); - if (transcodingProfile != null) + if (transcodingProfile is not null) { SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile); @@ -686,7 +707,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.PlayMethod = PlayMethod.Transcode; - if (subtitleStream != null) + if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); @@ -702,7 +723,7 @@ namespace MediaBrowser.Model.Dlna } } - _logger.LogInformation( + _logger.LogDebug( "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}", options.Profile.Name ?? "Anonymous Profile", item.Path ?? "Unknown path", @@ -716,7 +737,14 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem) + private TranscodingProfile? GetVideoTranscodeProfile( + MediaSourceInfo item, + MediaOptions options, + MediaStream? videoStream, + MediaStream? audioStream, + IEnumerable<MediaStream> candidateAudioStreams, + MediaStream? subtitleStream, + StreamInfo playlistItem) { if (!(item.SupportsTranscoding || item.SupportsDirectStream)) { @@ -729,7 +757,7 @@ namespace MediaBrowser.Model.Dlna if (options.AllowVideoStreamCopy) { // prefer direct copy profile - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + float videoFramerate = videoStream?.AverageFrameRate ?? videoStream?.RealFrameRate ?? 0; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); @@ -740,7 +768,7 @@ namespace MediaBrowser.Model.Dlna if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec)) { - var videoCodec = transcodingProfile.VideoCodec; + var videoCodec = videoStream?.Codec; var container = transcodingProfile.Container; var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && @@ -763,12 +791,28 @@ namespace MediaBrowser.Model.Dlna return transcodingProfiles.FirstOrDefault(); } - private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec) + private void BuildStreamVideoItem( + StreamInfo playlistItem, + MediaOptions options, + MediaSourceInfo item, + MediaStream? videoStream, + MediaStream? audioStream, + IEnumerable<MediaStream> candidateAudioStreams, + string? container, + string? videoCodec, + string? audioCodec) { // Prefer matching video codecs var videoCodecs = ContainerProfile.SplitValue(videoCodec); + + // Enforce HLS video codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray(); + } + var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; - if (directVideoCodec != null) + if (directVideoCodec is not null) { // merge directVideoCodec to videoCodecs Array.Resize(ref videoCodecs, videoCodecs.Length + 1); @@ -780,12 +824,12 @@ namespace MediaBrowser.Model.Dlna // Copy video codec options as a starting point, this applies to transcode and direct-stream playlistItem.MaxFramerate = videoStream?.AverageFrameRate; var qualifier = videoStream?.Codec; - if (videoStream?.Level != null) + if (videoStream?.Level is not null) { playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture)); } - if (videoStream?.BitDepth != null) + if (videoStream?.BitDepth is not null) { playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture)); } @@ -795,16 +839,30 @@ namespace MediaBrowser.Model.Dlna playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant()); } - if (videoStream != null && videoStream.Level != 0) + if (videoStream is not null && videoStream.Level != 0) { playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString()); } // Prefer matching audio codecs, could do better here var audioCodecs = ContainerProfile.SplitValue(audioCodec); + + // Enforce HLS audio codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) + { + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray(); + } + else + { + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray(); + } + } + var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); playlistItem.AudioCodecs = audioCodecs; - if (directAudioStream != null) + if (directAudioStream is not null) { audioStream = directAudioStream; playlistItem.AudioStreamIndex = audioStream.Index; @@ -830,15 +888,15 @@ namespace MediaBrowser.Model.Dlna int? bitDepth = videoStream?.BitDepth; int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; - string videoProfile = videoStream?.Profile; - string videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + string? videoProfile = videoStream?.Profile; + string? videoRangeType = videoStream?.VideoRangeType; + float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; - string videoCodecTag = videoStream?.CodecTag; + string? videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : item.Timestamp; int? packetLength = videoStream?.PacketLength; int? refFrames = videoStream?.RefFrames; @@ -847,7 +905,7 @@ namespace MediaBrowser.Model.Dlna var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.Video && - i.ContainsAnyCodec(videoCodec, container) && + i.ContainsAnyCodec(videoStream?.Codec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))); var isFirstAppliedCodecProfile = true; foreach (var i in appliedVideoConditions) @@ -867,29 +925,29 @@ namespace MediaBrowser.Model.Dlna // Honor requested max channels playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels; - int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); + int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(true) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem); playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate); - bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream); - int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate; - int? audioChannels = audioStream == null ? null : audioStream.Channels; - string audioProfile = audioStream == null ? null : audioStream.Profile; - int? inputAudioSampleRate = audioStream == null ? null : audioStream.SampleRate; - int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth; + bool? isSecondaryAudio = audioStream is null ? null : item.IsSecondaryAudio(audioStream); + int? inputAudioBitrate = audioStream?.BitRate; + int? audioChannels = audioStream?.Channels; + string? audioProfile = audioStream?.Profile; + int? inputAudioSampleRate = audioStream?.SampleRate; + int? inputAudioBitDepth = audioStream?.BitDepth; var appliedAudioConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioCodec, container) && + i.ContainsAnyCodec(audioStream?.Codec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); isFirstAppliedCodecProfile = true; - foreach (var i in appliedAudioConditions) + foreach (var codecProfile in appliedAudioConditions) { var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec); foreach (var transcodingAudioCodec in transcodingAudioCodecs) { - if (i.ContainsAnyCodec(transcodingAudioCodec, container)) + if (codecProfile.ContainsAnyCodec(transcodingAudioCodec, container)) { - ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); + ApplyTranscodingConditions(playlistItem, codecProfile.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile); isFirstAppliedCodecProfile = false; break; } @@ -923,7 +981,7 @@ namespace MediaBrowser.Model.Dlna playlistItem?.TranscodeReasons); } - private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels) + private static int GetDefaultAudioBitrate(string? audioCodec, int? audioChannels) { if (!string.IsNullOrEmpty(audioCodec)) { @@ -956,16 +1014,16 @@ namespace MediaBrowser.Model.Dlna return 192000; } - private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream audioStream, StreamInfo item) + private static int GetAudioBitrate(long maxTotalBitrate, string[] targetAudioCodecs, MediaStream? audioStream, StreamInfo item) { - string targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; + string? targetAudioCodec = targetAudioCodecs.Length == 0 ? null : targetAudioCodecs[0]; int? targetAudioChannels = item.GetTargetAudioChannels(targetAudioCodec); int defaultBitrate; int encoderAudioBitrateLimit = int.MaxValue; - if (audioStream == null) + if (audioStream is null) { defaultBitrate = 192000; } @@ -982,7 +1040,7 @@ namespace MediaBrowser.Model.Dlna && audioStream.Channels.HasValue && audioStream.Channels.Value <= targetAudioChannels.Value && !string.IsNullOrEmpty(audioStream.Codec) - && targetAudioCodecs != null + && targetAudioCodecs is not null && targetAudioCodecs.Length > 0 && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) { @@ -1017,31 +1075,38 @@ namespace MediaBrowser.Model.Dlna { return 128000; } - else if (totalBitrate <= 2000000) + + if (totalBitrate <= 2000000) { return 384000; } - else if (totalBitrate <= 3000000) + + if (totalBitrate <= 3000000) { return 448000; } - else if (totalBitrate <= 4000000) + + if (totalBitrate <= 4000000) { return 640000; } - else if (totalBitrate <= 5000000) + + if (totalBitrate <= 5000000) { return 768000; } - else if (totalBitrate <= 10000000) + + if (totalBitrate <= 10000000) { return 1536000; } - else if (totalBitrate <= 15000000) + + if (totalBitrate <= 15000000) { return 2304000; } - else if (totalBitrate <= 20000000) + + if (totalBitrate <= 20000000) { return 3584000; } @@ -1049,13 +1114,13 @@ namespace MediaBrowser.Model.Dlna return 7168000; } - private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( - VideoOptions options, + private (DirectPlayProfile? Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile( + MediaOptions options, MediaSourceInfo mediaSource, - MediaStream videoStream, - MediaStream audioStream, + MediaStream? videoStream, + MediaStream? audioStream, ICollection<MediaStream> candidateAudioStreams, - MediaStream subtitleStream, + MediaStream? subtitleStream, bool isEligibleForDirectPlay, bool isEligibleForDirectStream) { @@ -1078,15 +1143,15 @@ namespace MediaBrowser.Model.Dlna int? bitDepth = videoStream?.BitDepth; int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; - string videoProfile = videoStream?.Profile; - string videoRangeType = videoStream?.VideoRangeType; - float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; + string? videoProfile = videoStream?.Profile; + string? videoRangeType = videoStream?.VideoRangeType; + float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; - string videoCodecTag = videoStream?.CodecTag; + string? videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp; + TransportStreamTimestamp? timestamp = videoStream is null ? TransportStreamTimestamp.None : mediaSource.Timestamp; int? packetLength = videoStream?.PacketLength; int? refFrames = videoStream?.RefFrames; @@ -1111,7 +1176,8 @@ namespace MediaBrowser.Model.Dlna profile, "VideoCodecProfile", profile.CodecProfiles - .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) && + .Where(codecProfile => codecProfile.Type == CodecType.Video && + codecProfile.ContainsAnyCodec(videoStream?.Codec, container) && !checkVideoConditions(codecProfile.ApplyConditions).Any()) .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions))); @@ -1119,7 +1185,7 @@ namespace MediaBrowser.Model.Dlna var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream)); TranscodeReason subtitleProfileReasons = 0; - if (subtitleStream != null) + if (subtitleStream is not null) { var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null); @@ -1141,7 +1207,6 @@ namespace MediaBrowser.Model.Dlna var reason = a & flag; if (reason != 0) { - a = reason; return index; } @@ -1151,6 +1216,8 @@ namespace MediaBrowser.Model.Dlna return index; }; + var containerSupported = false; + // Check DirectPlay profiles to see if it can be direct played var analyzedProfiles = profile.DirectPlayProfiles .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) @@ -1164,20 +1231,24 @@ namespace MediaBrowser.Model.Dlna { directPlayProfileReasons |= TranscodeReason.ContainerNotSupported; } + else + { + containerSupported = true; + } // Check video codec - string videoCodec = videoStream?.Codec; + string? videoCodec = videoStream?.Codec; if (!directPlayProfile.SupportsVideoCodec(videoCodec)) { directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported; } // Check audio codec - MediaStream selectedAudioStream = null; + MediaStream? selectedAudioStream = null; if (candidateAudioStreams.Any()) { selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec)); - if (selectedAudioStream == null) + if (selectedAudioStream is null) { directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported; } @@ -1206,7 +1277,7 @@ namespace MediaBrowser.Model.Dlna { playMethod = PlayMethod.DirectPlay; } - else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) + else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream) { playMethod = PlayMethod.DirectStream; } @@ -1218,17 +1289,20 @@ namespace MediaBrowser.Model.Dlna .ThenByDescending(analysis => analysis.Rank) .ThenBy(analysis => analysis.Order) .ToArray() - .ToLookup(analysis => analysis.Result.PlayMethod != null); + .ToLookup(analysis => analysis.Result.PlayMethod is not null); var profileMatch = analyzedProfiles[true] .Select(analysis => analysis.Result) .FirstOrDefault(); - if (profileMatch.Profile != null) + if (profileMatch.Profile is not null) { return profileMatch; } - var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason; + var failureReasons = analyzedProfiles[false] + .Select(analysis => analysis.Result) + .Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0) + .FirstOrDefault().TranscodeReason; if (failureReasons == 0) { failureReasons = TranscodeReason.DirectPlayError; @@ -1237,7 +1311,7 @@ namespace MediaBrowser.Model.Dlna return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); } - private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream) + private TranscodeReason CheckVideoAudioStreamDirectPlay(MediaOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream) { var profile = options.Profile; var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream)); @@ -1274,31 +1348,25 @@ namespace MediaBrowser.Model.Dlna mediaSource.Path ?? "Unknown path"); } - private TranscodeReason IsBitrateEligibleForDirectPlayback( - MediaSourceInfo item, - long maxBitrate, - VideoOptions options, - PlayMethod playMethod) - { - bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod); - if (!result) - { - return TranscodeReason.ContainerBitrateExceedsLimit; - } - else - { - return 0; - } - } - + /// <summary> + /// Normalizes input container. + /// </summary> + /// <param name="mediaSource">The <see cref="MediaSourceInfo"/>.</param> + /// <param name="subtitleStream">The <see cref="MediaStream"/> of the subtitle stream.</param> + /// <param name="subtitleProfiles">The list of supported <see cref="SubtitleProfile"/>s.</param> + /// <param name="playMethod">The <see cref="PlayMethod"/>.</param> + /// <param name="transcoderSupport">The <see cref="ITranscoderSupport"/>.</param> + /// <param name="outputContainer">The output container.</param> + /// <param name="transcodingSubProtocol">The subtitle transoding protocol.</param> + /// <returns>The the normalized input container.</returns> public static SubtitleProfile GetSubtitleProfile( MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, - string outputContainer, - string transcodingSubProtocol) + string? outputContainer, + string? transcodingSubProtocol) { if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))) { @@ -1371,7 +1439,7 @@ namespace MediaBrowser.Model.Dlna }; } - private static bool IsSubtitleEmbedSupported(string transcodingContainer) + private static bool IsSubtitleEmbedSupported(string? transcodingContainer) { if (!string.IsNullOrEmpty(transcodingContainer)) { @@ -1383,7 +1451,8 @@ namespace MediaBrowser.Model.Dlna { return false; } - else if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") + + if (ContainerProfile.ContainsContainer(normalizedContainers, "mkv") || ContainerProfile.ContainsContainer(normalizedContainers, "matroska")) { return true; @@ -1393,7 +1462,7 @@ namespace MediaBrowser.Model.Dlna return false; } - private static SubtitleProfile GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion) + private static SubtitleProfile? GetExternalSubtitleProfile(MediaSourceInfo mediaSource, MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, ITranscoderSupport transcoderSupport, bool allowConversion) { foreach (var profile in subtitleProfiles) { @@ -1448,67 +1517,60 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) + private bool IsBitrateLimitExceeded(MediaSourceInfo item, long maxBitrate) { - // Don't restrict by bitrate if coming from an external domain + // Don't restrict bitrate if item is remote. if (item.IsRemote) { - return true; + return false; } - long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000; + // If no maximum bitrate is set, default to no maximum bitrate. + long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : int.MaxValue; - // If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps + // If we don't know the item bitrate, then force a transcode if requested max bitrate is under 40 mbps int itemBitrate = item.Bitrate ?? 40000000; if (itemBitrate > requestedMaxBitrate) { _logger.LogDebug( - "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", - playMethod, + "Bitrate exceeds limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", itemBitrate, requestedMaxBitrate); - return false; - } - - return true; - } - - private static void ValidateInput(VideoOptions options) - { - ValidateAudioInput(options); - - if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested"); + return true; } - if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested"); - } + return false; } - private static void ValidateAudioInput(AudioOptions options) + private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource) { if (options.ItemId.Equals(default)) { - throw new ArgumentException("ItemId is required"); + ArgumentException.ThrowIfNullOrEmpty(options.DeviceId); } - if (string.IsNullOrEmpty(options.DeviceId)) + if (options.Profile is null) { - throw new ArgumentException("DeviceId is required"); + throw new ArgumentException("Profile is required"); } - if (options.Profile == null) + if (options.MediaSources is null) { - throw new ArgumentException("Profile is required"); + throw new ArgumentException("MediaSources is required"); } - if (options.MediaSources == null) + if (isMediaSource) { - throw new ArgumentException("MediaSources is required"); + if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested"); + } + + if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested"); + } } } @@ -1524,7 +1586,8 @@ namespace MediaBrowser.Model.Dlna bool? isSecondaryAudio) { return codecProfiles - .Where(profile => profile.Type == CodecType.VideoAudio && profile.ContainsAnyCodec(codec, container) && + .Where(profile => profile.Type == CodecType.VideoAudio && + profile.ContainsAnyCodec(codec, container) && profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))) .SelectMany(profile => profile.Conditions) .Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)); @@ -1533,7 +1596,7 @@ namespace MediaBrowser.Model.Dlna private static IEnumerable<ProfileCondition> GetProfileConditionsForAudio( IEnumerable<CodecProfile> codecProfiles, string container, - string codec, + string? codec, int? audioChannels, int? audioBitrate, int? audioSampleRate, @@ -1541,7 +1604,8 @@ namespace MediaBrowser.Model.Dlna bool checkConditions) { var conditions = codecProfiles - .Where(profile => profile.Type == CodecType.Audio && profile.ContainsAnyCodec(codec, container) && + .Where(profile => profile.Type == CodecType.Audio && + profile.ContainsAnyCodec(codec, container) && profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth))) .SelectMany(profile => profile.Conditions); @@ -1553,7 +1617,7 @@ namespace MediaBrowser.Model.Dlna return conditions.Where(condition => !ConditionProcessor.IsAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth)); } - private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions) + private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions, string? qualifier, bool enableQualifiedConditions, bool enableNonQualifiedConditions) { foreach (ProfileCondition condition in conditions) { @@ -1579,7 +1643,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1605,7 +1669,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1641,7 +1705,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1765,7 +1829,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1801,7 +1865,7 @@ namespace MediaBrowser.Model.Dlna } } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1827,8 +1891,8 @@ namespace MediaBrowser.Model.Dlna continue; } - // change from split by | to comma - // strip spaces to avoid having to encode + // Change from split by | to comma + // Strip spaces to avoid having to encode var values = value .Split('|', StringSplitOptions.RemoveEmptyEntries); @@ -1891,7 +1955,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1917,7 +1981,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1943,7 +2007,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (float.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1969,7 +2033,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -1995,7 +2059,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var num)) + if (int.TryParse(value, CultureInfo.InvariantCulture, out var num)) { if (condition.Condition == ProfileConditionType.Equals) { @@ -2029,7 +2093,7 @@ namespace MediaBrowser.Model.Dlna } // Check audio codec - string audioCodec = audioStream?.Codec; + string? audioCodec = audioStream?.Codec; if (!profile.SupportsAudioCodec(audioCodec)) { return false; |
