diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index e9041186f4..bea7a5a0da 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -195,7 +195,7 @@ namespace Emby.Dlna.Didl
{
var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user);
- streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions
+ streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions
{
ItemId = video.Id,
MediaSources = sources.ToArray(),
@@ -537,7 +537,7 @@ namespace Emby.Dlna.Didl
{
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user);
- streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions
+ streamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions
{
ItemId = audio.Id,
MediaSources = sources.ToArray(),
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index 4cda1d8b7a..7b1f942c5a 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -585,7 +585,7 @@ namespace Emby.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(new VideoOptions
+ StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions
{
ItemId = item.Id,
MediaSources = mediaSources,
@@ -605,7 +605,7 @@ namespace Emby.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(new AudioOptions
+ StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions
{
ItemId = item.Id,
MediaSources = mediaSources,
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index e8ce1ca2a2..e0245fe4da 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -181,7 +181,7 @@ namespace Jellyfin.Api.Helpers
{
var streamBuilder = new StreamBuilder(_mediaEncoder, _logger);
- var options = new VideoOptions
+ var options = new MediaOptions
{
MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming,
@@ -244,8 +244,8 @@ namespace Jellyfin.Api.Helpers
// Beginning of Playback Determination
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
- ? streamBuilder.BuildAudioItem(options)
- : streamBuilder.BuildVideoItem(options);
+ ? streamBuilder.GetOptimalAudioStream(options)
+ : streamBuilder.GetOptimalVideoStream(options);
if (streamInfo is not null)
{
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/MediaOptions.cs
similarity index 79%
rename from MediaBrowser.Model/Dlna/AudioOptions.cs
rename to MediaBrowser.Model/Dlna/MediaOptions.cs
index df4018fdd5..939caf813e 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/MediaOptions.cs
@@ -7,11 +7,11 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Dlna
{
///
- /// Class AudioOptions.
+ /// Class MediaOptions.
///
- public class AudioOptions
+ public class MediaOptions
{
- public AudioOptions()
+ public MediaOptions()
{
Context = EncodingContext.Streaming;
@@ -27,8 +27,16 @@ namespace MediaBrowser.Model.Dlna
public bool ForceDirectStream { get; set; }
+ ///
+ /// Gets or sets an override for allowing stream copy.
+ ///
public bool AllowAudioStreamCopy { get; set; }
+ ///
+ /// Gets or sets an override for allowing stream copy.
+ ///
+ public bool AllowVideoStreamCopy { get; set; }
+
public Guid ItemId { get; set; }
public MediaSourceInfo[] MediaSources { get; set; }
@@ -65,6 +73,16 @@ namespace MediaBrowser.Model.Dlna
/// The audio transcoding bitrate.
public int? AudioTranscodingBitrate { get; set; }
+ ///
+ /// Gets or sets an override for the audio stream index.
+ ///
+ public int? AudioStreamIndex { get; set; }
+
+ ///
+ /// Gets or sets an override for the subtitle stream index.
+ ///
+ public int? SubtitleStreamIndex { get; set; }
+
///
/// Gets the maximum bitrate.
///
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index af35e98eef..00c3234dde 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1,5 +1,4 @@
#nullable disable
-#pragma warning disable CS1591
using System;
using System.Collections.Generic;
@@ -13,6 +12,9 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Model.Dlna
{
+ ///
+ /// Class StreamBuilder.
+ ///
public class StreamBuilder
{
// Aliases
@@ -24,35 +26,49 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The object.
+ /// The object.
public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{
_transcoderSupport = transcoderSupport;
_logger = logger;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The object.
public StreamBuilder(ILogger logger)
: this(new FullTranscoderSupport(), logger)
{
}
- public StreamInfo BuildAudioItem(AudioOptions options)
+ ///
+ /// Gets the optimal audio stream.
+ ///
+ /// The object to get the audio stream from.
+ /// The of the optimal audio stream.
+ public StreamInfo GetOptimalAudioStream(MediaOptions options)
{
- ValidateAudioInput(options);
+ ValidateMediaOptions(options, false);
var mediaSources = new List();
- foreach (MediaSourceInfo i in options.MediaSources)
+ foreach (var mediaSource in options.MediaSources)
{
if (string.IsNullOrEmpty(options.MediaSourceId) ||
- string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
+ string.Equals(mediaSource.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
{
- mediaSources.Add(i);
+ mediaSources.Add(mediaSource);
}
}
var streams = new List();
foreach (MediaSourceInfo i in mediaSources)
{
- StreamInfo streamInfo = BuildAudioItem(i, options);
+ StreamInfo streamInfo = GetOptimalAudioStream(i, options);
if (streamInfo is not null)
{
streams.Add(streamInfo);
@@ -68,9 +84,115 @@ namespace MediaBrowser.Model.Dlna
return GetOptimalStream(streams, options.GetMaxBitrate(true) ?? 0);
}
- public StreamInfo BuildVideoItem(VideoOptions options)
+ private StreamInfo GetOptimalAudioStream(MediaSourceInfo item, MediaOptions options)
{
- ValidateInput(options);
+ var 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;
+ }
+
+ 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?.BitDepth;
+ 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)
+ {
+ playlistItem.PlayMethod = directPlayMethod.Value;
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile);
+
+ return playlistItem;
+ }
+ }
+
+ TranscodingProfile transcodingProfile = null;
+ foreach (TranscodingProfile 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;
+ }
+
+ ///
+ /// Gets the optimal video stream.
+ ///
+ /// The object to get the video stream from.
+ /// The of the optimal video stream.
+ public StreamInfo GetOptimalVideoStream(MediaOptions options)
+ {
+ ValidateMediaOptions(options, true);
var mediaSources = new List();
foreach (MediaSourceInfo i in options.MediaSources)
@@ -236,6 +358,14 @@ namespace MediaBrowser.Model.Dlna
}
}
+ ///
+ /// Normalizes input container.
+ ///
+ /// The input container.
+ /// The .
+ /// The .
+ /// The object to get the video stream from.
+ /// The the normalized input container.
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null)
{
if (string.IsNullOrEmpty(inputContainer))
@@ -264,108 +394,7 @@ 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 is not 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));
@@ -388,7 +417,7 @@ namespace MediaBrowser.Model.Dlna
// 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 +433,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)
{
@@ -427,7 +456,6 @@ namespace MediaBrowser.Model.Dlna
var containerSupported = false;
var audioSupported = false;
var videoSupported = false;
- TranscodeReason reasons = 0;
foreach (var profile in directPlayProfiles)
{
@@ -447,6 +475,7 @@ namespace MediaBrowser.Model.Dlna
}
}
+ TranscodeReason reasons = 0;
if (!containerSupported)
{
reasons |= TranscodeReason.ContainerNotSupported;
@@ -547,7 +576,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";
@@ -562,7 +591,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec);
}
- private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
+ private StreamInfo BuildVideoItem(MediaSourceInfo item, MediaOptions options)
{
ArgumentNullException.ThrowIfNull(item);
@@ -601,11 +630,15 @@ 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;
+
+ if (bitrateLimitExceeded)
+ {
+ transcodeReasons = TranscodeReason.ContainerBitrateExceedsLimit;
+ }
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -702,7 +735,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 +749,7 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
- private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem)
+ private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, MediaOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem)
{
if (!(item.SupportsTranscoding || item.SupportsDirectStream))
{
@@ -763,7 +796,7 @@ namespace MediaBrowser.Model.Dlna
return transcodingProfiles.FirstOrDefault();
}
- private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, string container, string videoCodec, string audioCodec)
+ private void BuildStreamVideoItem(StreamInfo playlistItem, MediaOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, string container, string videoCodec, string audioCodec)
{
// Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
@@ -867,7 +900,7 @@ 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 is null ? null : item.IsSecondaryAudio(audioStream);
@@ -882,14 +915,14 @@ namespace MediaBrowser.Model.Dlna
i.ContainsAnyCodec(audioCodec, 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;
}
@@ -1050,7 +1083,7 @@ namespace MediaBrowser.Model.Dlna
}
private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile(
- VideoOptions options,
+ MediaOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
MediaStream audioStream,
@@ -1237,7 +1270,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,23 +1307,17 @@ 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;
- }
- }
-
+ ///
+ /// Normalizes input container.
+ ///
+ /// The .
+ /// The of the subtitle stream.
+ /// The list of supported s.
+ /// The .
+ /// The .
+ /// The output container.
+ /// The subtitle transoding protocol.
+ /// The the normalized input container.
public static SubtitleProfile GetSubtitleProfile(
MediaSourceInfo mediaSource,
MediaStream subtitleStream,
@@ -1448,14 +1475,8 @@ 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
- if (item.IsRemote)
- {
- return true;
- }
-
long requestedMaxBitrate = maxBitrate > 0 ? maxBitrate : 1000000;
// If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
@@ -1464,40 +1485,22 @@ namespace MediaBrowser.Model.Dlna
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;
}
- return true;
+ return false;
}
- 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");
- }
-
- if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
- {
- throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
- }
- }
-
- private static void ValidateAudioInput(AudioOptions options)
+ private static void ValidateMediaOptions(MediaOptions options, Boolean IsMediaSource)
{
if (options.ItemId.Equals(default))
{
- throw new ArgumentException("ItemId is required");
+ ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
}
- ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
-
if (options.Profile is null)
{
throw new ArgumentException("Profile is required");
@@ -1507,6 +1510,19 @@ namespace MediaBrowser.Model.Dlna
{
throw new ArgumentException("MediaSources is required");
}
+
+ if (IsMediaSource)
+ {
+ 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");
+ }
+ }
}
private static IEnumerable GetProfileConditionsForVideoAudio(
@@ -1824,8 +1840,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);
diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs
deleted file mode 100644
index 0cb80af544..0000000000
--- a/MediaBrowser.Model/Dlna/VideoOptions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Dlna
-{
- ///
- /// Class VideoOptions.
- ///
- public class VideoOptions : AudioOptions
- {
- public int? AudioStreamIndex { get; set; }
-
- public int? SubtitleStreamIndex { get; set; }
-
- public bool AllowVideoStreamCopy { get; set; }
- }
-}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 5e11a7232d..60be17a741 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -164,7 +164,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
- var options = await GetVideoOptions(deviceName, mediaSource);
+ var options = await GetMediaOptions(deviceName, mediaSource);
BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
}
@@ -262,7 +262,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
- var options = await GetVideoOptions(deviceName, mediaSource);
+ var options = await GetMediaOptions(deviceName, mediaSource);
options.AudioStreamIndex = 1;
options.SubtitleStreamIndex = options.MediaSources[0].MediaStreams.Count - 1;
@@ -298,7 +298,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{
- var options = await GetVideoOptions(deviceName, mediaSource);
+ var options = await GetMediaOptions(deviceName, mediaSource);
var streamCount = options.MediaSources[0].MediaStreams.Count;
if (streamCount > 0)
{
@@ -311,7 +311,7 @@ namespace Jellyfin.Model.Tests
Assert.Equal(streamInfo?.SubtitleStreamIndex, options.SubtitleStreamIndex);
}
- private StreamInfo? BuildVideoItemSimpleTest(VideoOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
+ private StreamInfo? BuildVideoItemSimpleTest(MediaOptions options, PlayMethod? playMethod, TranscodeReason why, string transcodeMode, string transcodeProtocol)
{
if (string.IsNullOrEmpty(transcodeProtocol))
{
@@ -320,28 +320,28 @@ namespace Jellyfin.Model.Tests
var builder = GetStreamBuilder();
- var val = builder.BuildVideoItem(options);
- Assert.NotNull(val);
+ var streamInfo = builder.GetOptimalVideoStream(options);
+ Assert.NotNull(streamInfo);
if (playMethod is not null)
{
- Assert.Equal(playMethod, val.PlayMethod);
+ Assert.Equal(playMethod, streamInfo.PlayMethod);
}
- Assert.Equal(why, val.TranscodeReasons);
+ Assert.Equal(why, streamInfo.TranscodeReasons);
var audioStreamIndexInput = options.AudioStreamIndex;
- var targetVideoStream = val.TargetVideoStream;
- var targetAudioStream = val.TargetAudioStream;
+ var targetVideoStream = streamInfo.TargetVideoStream;
+ var targetAudioStream = streamInfo.TargetAudioStream;
- var mediaSource = options.MediaSources.First(source => source.Id == val.MediaSourceId);
+ var mediaSource = options.MediaSources.First(source => source.Id == streamInfo.MediaSourceId);
Assert.NotNull(mediaSource);
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
// TODO: Check AudioStreamIndex vs options.AudioStreamIndex
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
- var uri = ParseUri(val);
+ var uri = ParseUri(streamInfo);
if (playMethod == PlayMethod.DirectPlay)
{
@@ -351,98 +351,99 @@ namespace Jellyfin.Model.Tests
// Assert.Contains(uri.Extension, containers);
// Check expected video codec (1)
- Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
- Assert.Single(val.TargetVideoCodec);
+ Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
+ Assert.Single(streamInfo.TargetVideoCodec);
// Check expected audio codecs (1)
- Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
- Assert.Single(val.TargetAudioCodec);
+ Assert.Contains(targetAudioStream.Codec, streamInfo.TargetAudioCodec);
+ Assert.Single(streamInfo.TargetAudioCodec);
// Assert.Single(val.AudioCodecs);
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{
- Assert.Equal(val.Container, uri.Extension);
+ Assert.Equal(streamInfo.Container, uri.Extension);
}
}
else if (playMethod == PlayMethod.DirectStream || playMethod == PlayMethod.Transcode)
{
- Assert.NotNull(val.Container);
- Assert.NotEmpty(val.VideoCodecs);
- Assert.NotEmpty(val.AudioCodecs);
+ Assert.NotNull(streamInfo.Container);
+ Assert.NotEmpty(streamInfo.VideoCodecs);
+ Assert.NotEmpty(streamInfo.AudioCodecs);
// Check expected container (todo: this could be a test param)
if (transcodeProtocol.Equals("http", StringComparison.Ordinal))
{
// Assert.Equal("webm", val.Container);
- Assert.Equal(val.Container, uri.Extension);
+ Assert.Equal(streamInfo.Container, uri.Extension);
Assert.Equal("stream", uri.Filename);
- Assert.Equal("http", val.SubProtocol);
+ Assert.Equal("http", streamInfo.SubProtocol);
}
else if (transcodeProtocol.Equals("HLS.mp4", StringComparison.Ordinal))
{
- Assert.Equal("mp4", val.Container);
+ Assert.Equal("mp4", streamInfo.Container);
Assert.Equal("m3u8", uri.Extension);
Assert.Equal("master", uri.Filename);
- Assert.Equal("hls", val.SubProtocol);
+ Assert.Equal("hls", streamInfo.SubProtocol);
}
else
{
- Assert.Equal("ts", val.Container);
+ Assert.Equal("ts", streamInfo.Container);
Assert.Equal("m3u8", uri.Extension);
Assert.Equal("master", uri.Filename);
- Assert.Equal("hls", val.SubProtocol);
+ Assert.Equal("hls", streamInfo.SubProtocol);
}
// Full transcode
if (transcodeMode.Equals("Transcode", StringComparison.Ordinal))
{
- if ((val.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0)
+ if ((streamInfo.TranscodeReasons & (StreamBuilder.ContainerReasons | TranscodeReason.DirectPlayError)) == 0)
{
Assert.All(
videoStreams,
- stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
+ stream => Assert.DoesNotContain(stream.Codec, streamInfo.VideoCodecs));
}
- // TODO: Fill out tests here
+ // TODO: fill out tests here
}
// DirectStream and Remux
else
{
// Check expected video codec (1)
- Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
- Assert.Single(val.TargetVideoCodec);
+ Assert.Contains(targetVideoStream.Codec, streamInfo.TargetVideoCodec);
+ Assert.Single(streamInfo.TargetVideoCodec);
if (transcodeMode.Equals("DirectStream", StringComparison.Ordinal))
{
// Check expected audio codecs (1)
if (!targetAudioStream.IsExternal)
{
- if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
+ // Check expected audio codecs (1)
+ if (streamInfo.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
{
- Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
+ Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs);
}
else
{
- Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs);
}
}
}
else if (transcodeMode.Equals("Remux", StringComparison.Ordinal))
{
// Check expected audio codecs (1)
- Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
- Assert.Single(val.AudioCodecs);
+ Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs);
+ Assert.Single(streamInfo.AudioCodecs);
}
// Video details
var videoStream = targetVideoStream;
- Assert.False(val.EstimateContentLength);
- Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
- Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, val.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty());
- Assert.Equal(videoStream.Level, val.TargetVideoLevel);
- Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
- Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
+ Assert.False(streamInfo.EstimateContentLength);
+ Assert.Equal(TranscodeSeekInfo.Auto, streamInfo.TranscodeSeekInfo);
+ Assert.Contains(videoStream.Profile?.ToLowerInvariant() ?? string.Empty, streamInfo.TargetVideoProfile?.Split(",").Select(s => s.ToLowerInvariant()) ?? Array.Empty());
+ Assert.Equal(videoStream.Level, streamInfo.TargetVideoLevel);
+ Assert.Equal(videoStream.BitDepth, streamInfo.TargetVideoBitDepth);
+ Assert.InRange(streamInfo.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
// Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
@@ -453,7 +454,7 @@ namespace Jellyfin.Model.Tests
// TODO:fixme
if (!targetAudioStream.IsExternal)
{
- Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
+ Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs);
}
}
@@ -465,7 +466,7 @@ namespace Jellyfin.Model.Tests
{
if (!stream.IsExternal)
{
- Assert.DoesNotContain(stream.Codec, val.AudioCodecs);
+ Assert.DoesNotContain(stream.Codec, streamInfo.AudioCodecs);
}
});
}
@@ -474,14 +475,14 @@ namespace Jellyfin.Model.Tests
}
else if (playMethod is null)
{
- Assert.Null(val.SubProtocol);
+ Assert.Null(streamInfo.SubProtocol);
Assert.Equal("stream", uri.Filename);
- Assert.False(val.EstimateContentLength);
- Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
+ Assert.False(streamInfo.EstimateContentLength);
+ Assert.Equal(TranscodeSeekInfo.Auto, streamInfo.TranscodeSeekInfo);
}
- return val;
+ return streamInfo;
}
private static async ValueTask TestData(string name)
@@ -507,7 +508,7 @@ namespace Jellyfin.Model.Tests
return new StreamBuilder(transcodeSupport.Object, logger);
}
- private static async ValueTask GetVideoOptions(string deviceProfile, params string[] sources)
+ private static async ValueTask GetMediaOptions(string deviceProfile, params string[] sources)
{
var mediaSources = sources.Select(src => TestData(src))
.Select(val => val.Result)
@@ -516,7 +517,7 @@ namespace Jellyfin.Model.Tests
var dp = await TestData(deviceProfile);
- return new VideoOptions()
+ return new MediaOptions()
{
ItemId = new Guid("11D229B7-2D48-4B95-9F9B-49F6AB75E613"),
MediaSourceId = mediaSourceId,