aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Dlna/StreamBuilder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model/Dlna/StreamBuilder.cs')
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs1036
1 files changed, 551 insertions, 485 deletions
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index d2ca21150..8671e5cbb 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -15,6 +15,12 @@ namespace MediaBrowser.Model.Dlna
{
public class StreamBuilder
{
+ // Aliases
+ internal const TranscodeReason ContainerReasons = TranscodeReason.ContainerNotSupported | TranscodeReason.ContainerBitrateExceedsLimit;
+ internal const TranscodeReason AudioReasons = TranscodeReason.AudioCodecNotSupported | TranscodeReason.AudioBitrateNotSupported | TranscodeReason.AudioChannelsNotSupported | TranscodeReason.AudioProfileNotSupported | TranscodeReason.AudioSampleRateNotSupported | TranscodeReason.SecondaryAudioNotSupported | TranscodeReason.AudioBitDepthNotSupported | TranscodeReason.AudioIsExternal;
+ internal const TranscodeReason VideoReasons = TranscodeReason.VideoCodecNotSupported | TranscodeReason.VideoResolutionNotSupported | TranscodeReason.AnamorphicVideoNotSupported | TranscodeReason.InterlacedVideoNotSupported | TranscodeReason.VideoBitDepthNotSupported | TranscodeReason.VideoBitrateNotSupported | TranscodeReason.VideoFramerateNotSupported | TranscodeReason.VideoLevelNotSupported | TranscodeReason.RefFramesNotSupported;
+ internal const TranscodeReason DirectStreamReasons = AudioReasons | TranscodeReason.ContainerNotSupported;
+
private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport;
@@ -143,7 +149,7 @@ namespace MediaBrowser.Model.Dlna
}).ThenBy(streams.IndexOf);
}
- private static TranscodeReason? GetTranscodeReasonForFailedCondition(ProfileCondition condition)
+ private static TranscodeReason GetTranscodeReasonForFailedCondition(ProfileCondition condition)
{
switch (condition.Property)
{
@@ -161,7 +167,7 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.Has64BitOffsets:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.Height:
return TranscodeReason.VideoResolutionNotSupported;
@@ -171,7 +177,7 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.IsAvc:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.IsInterlaced:
return TranscodeReason.InterlacedVideoNotSupported;
@@ -181,15 +187,15 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.NumAudioStreams:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.NumVideoStreams:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.PacketLength:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.RefFrames:
return TranscodeReason.RefFramesNotSupported;
@@ -217,17 +223,17 @@ namespace MediaBrowser.Model.Dlna
case ProfileConditionValue.VideoTimestamp:
// TODO
- return null;
+ return 0;
case ProfileConditionValue.Width:
return TranscodeReason.VideoResolutionNotSupported;
default:
- return null;
+ return 0;
}
}
- public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type)
+ public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type, DirectPlayProfile playProfile = null)
{
if (string.IsNullOrEmpty(inputContainer))
{
@@ -236,16 +242,12 @@ namespace MediaBrowser.Model.Dlna
var formats = ContainerProfile.SplitValue(inputContainer);
- if (formats.Length == 1)
- {
- return formats[0];
- }
-
if (profile != null)
{
+ var playProfiles = playProfile == null ? profile.DirectPlayProfiles : new[] { playProfile };
foreach (var format in formats)
{
- foreach (var directPlayProfile in profile.DirectPlayProfiles)
+ foreach (var directPlayProfile in playProfiles)
{
if (directPlayProfile.Type == type
&& directPlayProfile.SupportsContainer(format))
@@ -287,69 +289,27 @@ namespace MediaBrowser.Model.Dlna
var audioStream = item.GetDefaultAudioStream(null);
- var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
+ var directPlayInfo = GetAudioDirectPlayProfile(item, audioStream, options);
- var directPlayMethods = directPlayInfo.PlayMethods;
- var transcodeReasons = directPlayInfo.TranscodeReasons.ToList();
+ 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 (directPlayMethods.Any())
+ if (directPlayMethod.HasValue)
{
- string audioCodec = audioStream?.Codec;
-
- // Make sure audio codec profiles are satisfied
- var conditions = new List<ProfileCondition>();
- foreach (var i in options.Profile.CodecProfiles)
- {
- if (i.Type == CodecType.Audio && i.ContainsAnyCodec(audioCodec, item.Container))
- {
- bool applyConditions = true;
- foreach (ProfileCondition applyCondition in i.ApplyConditions)
- {
- if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
- {
- LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
- applyConditions = false;
- break;
- }
- }
+ 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 (applyConditions)
- {
- conditions.AddRange(i.Conditions);
- }
- }
- }
-
- bool all = true;
- foreach (ProfileCondition c in conditions)
+ if (audioFailureReasons == 0)
{
- if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
- {
- LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
- var transcodeReason = GetTranscodeReasonForFailedCondition(c);
- if (transcodeReason.HasValue)
- {
- transcodeReasons.Add(transcodeReason.Value);
- }
-
- all = false;
- break;
- }
- }
-
- if (all)
- {
- if (directPlayMethods.Contains(PlayMethod.DirectStream))
- {
- playlistItem.PlayMethod = PlayMethod.DirectStream;
- }
-
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
+ playlistItem.PlayMethod = directPlayMethod.Value;
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio, directPlayInfo.Profile);
return playlistItem;
}
@@ -374,45 +334,9 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
-
- var audioCodecProfiles = new List<CodecProfile>();
- foreach (var i in options.Profile.CodecProfiles)
- {
- if (i.Type == CodecType.Audio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container))
- {
- audioCodecProfiles.Add(i);
- }
-
- if (audioCodecProfiles.Count >= 1)
- {
- break;
- }
- }
-
- var audioTranscodingConditions = new List<ProfileCondition>();
- foreach (var i in audioCodecProfiles)
- {
- bool applyConditions = true;
- foreach (var applyCondition in i.ApplyConditions)
- {
- if (!ConditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
- {
- LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
- applyConditions = false;
- break;
- }
- }
-
- if (applyConditions)
- {
- foreach (ProfileCondition c in i.Conditions)
- {
- audioTranscodingConditions.Add(c);
- }
- }
- }
+ 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
@@ -434,23 +358,13 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
}
- playlistItem.TranscodeReasons = transcodeReasons.ToArray();
+ playlistItem.TranscodeReasons = transcodeReasons;
return playlistItem;
}
- private static long? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
- {
- if (item.Protocol == MediaProtocol.File)
- {
- return options.Profile.MaxStaticBitrate;
- }
-
- return options.GetMaxBitrate(isAudio);
- }
-
- private (IEnumerable<PlayMethod> PlayMethods, IEnumerable<TranscodeReason> TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
+ private (DirectPlayProfile Profile, PlayMethod? PlayMethod, TranscodeReason TranscodeReasons) GetAudioDirectPlayProfile(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
- DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
+ var directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
if (directPlayProfile == null)
@@ -461,64 +375,56 @@ namespace MediaBrowser.Model.Dlna
item.Path ?? "Unknown path",
audioStream.Codec ?? "Unknown codec");
- return (Enumerable.Empty<PlayMethod>(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
+ return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles));
}
var playMethods = new List<PlayMethod>();
- var transcodeReasons = new List<TranscodeReason>();
+ TranscodeReason transcodeReasons = 0;
- // While options takes the network and other factors into account. Only applies to direct stream
- if (item.SupportsDirectStream)
+ // The profile describes what the device supports
+ // If device requirements are satisfied then allow both direct stream and direct play
+ if (item.SupportsDirectPlay)
{
- if (IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
+ if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
{
- if (options.EnableDirectStream)
+ if (options.EnableDirectPlay)
{
- playMethods.Add(PlayMethod.DirectStream);
+ return (directPlayProfile, PlayMethod.DirectPlay, 0);
}
}
else
{
- transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
+ transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
}
}
- // The profile describes what the device supports
- // If device requirements are satisfied then allow both direct stream and direct play
- if (item.SupportsDirectPlay)
+ // While options takes the network and other factors into account. Only applies to direct stream
+ if (item.SupportsDirectStream)
{
- if (IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
+ if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{
- if (options.EnableDirectPlay)
+ if (options.EnableDirectStream)
{
- playMethods.Add(PlayMethod.DirectPlay);
+ return (directPlayProfile, PlayMethod.DirectStream, transcodeReasons);
}
}
else
{
- transcodeReasons.Add(TranscodeReason.ContainerBitrateExceedsLimit);
+ transcodeReasons |= TranscodeReason.ContainerBitrateExceedsLimit;
}
}
- if (playMethods.Count > 0)
- {
- transcodeReasons.Clear();
- }
- else
- {
- transcodeReasons = transcodeReasons.Distinct().ToList();
- }
-
- return (playMethods, transcodeReasons);
+ return (directPlayProfile, null, transcodeReasons);
}
- private static List<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 containerSupported = false;
var audioSupported = false;
var videoSupported = false;
+ TranscodeReason reasons = 0;
foreach (var profile in directPlayProfiles)
{
@@ -541,20 +447,20 @@ namespace MediaBrowser.Model.Dlna
var list = new List<TranscodeReason>();
if (!containerSupported)
{
- list.Add(TranscodeReason.ContainerNotSupported);
+ reasons |= TranscodeReason.ContainerNotSupported;
}
if (videoStream != null && !videoSupported)
{
- list.Add(TranscodeReason.VideoCodecNotSupported);
+ reasons |= TranscodeReason.VideoCodecNotSupported;
}
if (audioStream != null && !audioSupported)
{
- list.Add(TranscodeReason.AudioCodecNotSupported);
+ reasons |= TranscodeReason.AudioCodecNotSupported;
}
- return list;
+ return reasons;
}
private static int? GetDefaultSubtitleStreamIndex(MediaSourceInfo item, SubtitleProfile[] subtitleProfiles)
@@ -599,30 +505,29 @@ namespace MediaBrowser.Model.Dlna
return item.DefaultSubtitleStreamIndex;
}
- private static void SetStreamInfoOptionsFromTranscodingProfile(StreamInfo playlistItem, TranscodingProfile transcodingProfile)
+ private static void SetStreamInfoOptionsFromTranscodingProfile(MediaSourceInfo item, StreamInfo playlistItem, TranscodingProfile transcodingProfile)
{
- if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
- {
- playlistItem.AudioCodecs = Array.Empty<string>();
- }
- else
- {
- playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
- }
+ var container = transcodingProfile.Container;
+ var protocol = transcodingProfile.Protocol;
- playlistItem.Container = transcodingProfile.Container;
- playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
- playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+ item.TranscodingContainer = container;
+ item.TranscodingSubProtocol = protocol;
- if (string.IsNullOrEmpty(transcodingProfile.VideoCodec))
+ if (playlistItem.PlayMethod == PlayMethod.Transcode)
{
- playlistItem.VideoCodecs = Array.Empty<string>();
+ playlistItem.Container = container;
+ playlistItem.SubProtocol = protocol;
}
- else
+
+ playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+ if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)
+ && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels))
{
- playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(',');
+ playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
}
+ playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
+
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
@@ -638,14 +543,21 @@ namespace MediaBrowser.Model.Dlna
{
playlistItem.SegmentLength = transcodingProfile.SegmentLength;
}
+ }
- playlistItem.SubProtocol = transcodingProfile.Protocol;
+ private static void SetStreamInfoOptionsFromDirectPlayProfile(VideoOptions options, MediaSourceInfo item, StreamInfo playlistItem, DirectPlayProfile directPlayProfile)
+ {
+ var container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
+ var protocol = "http";
- if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)
- && int.TryParse(transcodingProfile.MaxAudioChannels, NumberStyles.Any, CultureInfo.InvariantCulture, out int transcodingMaxAudioChannels))
- {
- playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
- }
+ item.TranscodingContainer = container;
+ item.TranscodingSubProtocol = protocol;
+
+ playlistItem.Container = container;
+ playlistItem.SubProtocol = protocol;
+
+ playlistItem.VideoCodecs = new[] { item.VideoStream.Codec };
+ playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec);
}
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
@@ -674,13 +586,29 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream.Index;
}
+ // Collect candidate audio streams
+ IEnumerable<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream };
+ if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
+ {
+ if (audioStream?.IsDefault == true)
+ {
+ candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault);
+ }
+ else
+ {
+ candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language);
+ }
+ }
+
+ candidateAudioStreams = candidateAudioStreams.ToArray();
+
var videoStream = item.VideoStream;
- // TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough
- var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay);
- var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream);
- bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay);
- bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay);
+ var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
+ var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0);
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0);
+ var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -689,189 +617,305 @@ namespace MediaBrowser.Model.Dlna
isEligibleForDirectPlay,
isEligibleForDirectStream);
- var transcodeReasons = new List<TranscodeReason>();
-
+ DirectPlayProfile directPlayProfile = null;
if (isEligibleForDirectPlay || isEligibleForDirectStream)
{
// See if it can be direct played
- var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream);
+ var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, candidateAudioStreams, subtitleStream, isEligibleForDirectPlay, isEligibleForDirectStream);
var directPlay = directPlayInfo.PlayMethod;
+ transcodeReasons |= directPlayInfo.TranscodeReasons;
if (directPlay != null)
{
+ directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value;
- playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video);
+ playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
+ playlistItem.VideoCodecs = new[] { videoStream.Codec };
+
+ if (directPlay == PlayMethod.DirectPlay)
+ {
+ playlistItem.SubProtocol = "http";
+
+ var audioStreamIndex = directPlayInfo.AudioStreamIndex ?? audioStream?.Index;
+ if (audioStreamIndex.HasValue)
+ {
+ playlistItem.AudioStreamIndex = audioStreamIndex;
+ playlistItem.AudioCodecs = new[] { item.GetMediaStream(MediaStreamType.Audio, audioStreamIndex.Value)?.Codec };
+ }
+ }
+ else if (directPlay == PlayMethod.DirectStream)
+ {
+ playlistItem.AudioStreamIndex = audioStream?.Index;
+ if (audioStream != null)
+ {
+ playlistItem.AudioCodecs = ContainerProfile.SplitValue(directPlayProfile.AudioCodec);
+ }
+
+ SetStreamInfoOptionsFromDirectPlayProfile(options, item, playlistItem, directPlayProfile);
+ BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, directPlayProfile.Container, directPlayProfile.VideoCodec, directPlayProfile.AudioCodec);
+ }
if (subtitleStream != null)
{
- var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, item.Container, null);
+ var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, _transcoderSupport, directPlayProfile.Container, null);
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
}
-
- return playlistItem;
}
- transcodeReasons.AddRange(directPlayInfo.TranscodeReasons);
+ _logger.LogDebug(
+ "DirectPlay Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStreamIndex: {4}, Reasons: {5}",
+ options.Profile.Name ?? "Anonymous Profile",
+ item.Path ?? "Unknown path",
+ directPlayInfo.PlayMethod,
+ directPlayInfo.AudioStreamIndex ?? audioStream?.Index,
+ playlistItem.SubtitleStreamIndex,
+ directPlayInfo.TranscodeReasons);
}
- if (directPlayEligibilityResult.Reason.HasValue)
+ playlistItem.TranscodeReasons = transcodeReasons;
+
+ if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream)
{
- transcodeReasons.Add(directPlayEligibilityResult.Reason.Value);
+ // 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)
+ {
+ SetStreamInfoOptionsFromTranscodingProfile(item, playlistItem, transcodingProfile);
+
+ BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
+
+ if (subtitleStream != null)
+ {
+ var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
+
+ playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
+ playlistItem.SubtitleFormat = subtitleProfile.Format;
+ playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
+ }
+
+ if (playlistItem.PlayMethod != PlayMethod.DirectPlay)
+ {
+ playlistItem.PlayMethod = PlayMethod.Transcode;
+
+ if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
+ {
+ ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
+ }
+ }
+ }
}
- if (directStreamEligibilityResult.Reason.HasValue)
+ _logger.LogInformation(
+ "StreamBuilder.BuildVideoItem( Profile={0}, Path={1}, AudioStreamIndex={2}, SubtitleStreamIndex={3} ) => ( PlayMethod={4}, TranscodeReason={5} ) {6}",
+ options.Profile.Name ?? "Anonymous Profile",
+ item.Path ?? "Unknown path",
+ options.AudioStreamIndex,
+ options.SubtitleStreamIndex,
+ playlistItem.PlayMethod,
+ playlistItem.TranscodeReasons,
+ playlistItem.ToUrl("media:", "<token>"));
+
+ item.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video, directPlayProfile);
+ return playlistItem;
+ }
+
+ private TranscodingProfile GetVideoTranscodeProfile(MediaSourceInfo item, VideoOptions options, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, MediaStream subtitleStream, StreamInfo playlistItem)
+ {
+ if (!(item.SupportsTranscoding || item.SupportsDirectStream))
{
- transcodeReasons.Add(directStreamEligibilityResult.Reason.Value);
+ return null;
}
- // Can't direct play, find the transcoding profile
- TranscodingProfile transcodingProfile = null;
- foreach (var i in options.Profile.TranscodingProfiles)
+ var transcodingProfiles = options.Profile.TranscodingProfiles
+ .Where(i => i.Type == playlistItem.MediaType && i.Context == options.Context);
+
+ if (options.AllowVideoStreamCopy)
{
- if (i.Type == playlistItem.MediaType && i.Context == options.Context)
+ // prefer direct copy profile
+ float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
+ TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
+
+ transcodingProfiles = transcodingProfiles.ToLookup(transcodingProfile =>
{
- transcodingProfile = i;
- break;
- }
+ var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
+
+ if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec))
+ {
+ var videoCodec = transcodingProfile.VideoCodec;
+ var container = transcodingProfile.Container;
+ var appliedVideoConditions = options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.Video &&
+ i.ContainsAnyCodec(videoCodec, container))
+ .Select(i =>
+ i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
+ var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied);
+ return conditionsSatisfied ? 1 : 2;
+ }
+
+ return 3;
+ })
+ .OrderBy(lookup => lookup.Key)
+ .SelectMany(lookup => lookup);
}
- if (transcodingProfile != null)
+ 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)
+ {
+ // prefer matching video codecs
+ var videoCodecs = ContainerProfile.SplitValue(videoCodec);
+ var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null;
+ playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs;
+
+ // 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.HasValue)
{
- if (!item.SupportsTranscoding)
+ playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ if (videoStream.BitDepth.HasValue)
+ {
+ playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Profile))
+ {
+ playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
+ }
+
+ if (videoStream.Level != 0)
+ {
+ playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
+ }
+
+ // prefer matching audio codecs, could do better here
+ var audioCodecs = ContainerProfile.SplitValue(audioCodec);
+ var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
+ playlistItem.AudioCodecs = audioCodecs;
+ if (directAudioStream != null)
+ {
+ audioStream = directAudioStream;
+ playlistItem.AudioStreamIndex = audioStream.Index;
+ playlistItem.AudioCodecs = new[] { audioStream.Codec };
+
+ // copy matching audio codec options
+ playlistItem.AudioSampleRate = audioStream.SampleRate;
+ playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
+
+ if (!string.IsNullOrEmpty(audioStream.Profile))
{
- return null;
+ playlistItem.SetOption(audioStream.Codec, "profile", audioStream.Profile.ToLowerInvariant());
}
- if (subtitleStream != null)
+ if (audioStream.Level != 0)
{
- var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
-
- playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
- playlistItem.SubtitleFormat = subtitleProfile.Format;
- playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
+ playlistItem.SetOption(audioStream.Codec, "level", audioStream.Level.ToString());
}
+ }
- playlistItem.PlayMethod = PlayMethod.Transcode;
+ int? width = videoStream?.Width;
+ int? height = videoStream?.Height;
+ int? bitDepth = videoStream?.BitDepth;
+ int? videoBitrate = videoStream?.BitRate;
+ double? videoLevel = videoStream?.Level;
+ string videoProfile = videoStream?.Profile;
+ float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
+ bool? isAnamorphic = videoStream?.IsAnamorphic;
+ bool? isInterlaced = videoStream?.IsInterlaced;
+ string videoCodecTag = videoStream?.CodecTag;
+ bool? isAvc = videoStream?.IsAVC;
- SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
+ TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? packetLength = videoStream?.PacketLength;
+ int? refFrames = videoStream?.RefFrames;
+
+ int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
- var isFirstAppliedCodecProfile = true;
- foreach (var i in options.Profile.CodecProfiles)
+ var appliedVideoConditions = options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.Video &&
+ i.ContainsAnyCodec(videoCodec, container) &&
+ i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
+ var isFirstAppliedCodecProfile = true;
+ foreach (var i in appliedVideoConditions)
+ {
+ var transcodingVideoCodecs = ContainerProfile.SplitValue(videoCodec);
+ foreach (var transcodingVideoCodec in transcodingVideoCodecs)
{
- if (i.Type == CodecType.Video && i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
+ if (i.ContainsAnyCodec(transcodingVideoCodec, container))
{
- bool applyConditions = true;
- foreach (ProfileCondition applyCondition in i.ApplyConditions)
- {
- int? width = videoStream?.Width;
- int? height = videoStream?.Height;
- int? bitDepth = videoStream?.BitDepth;
- int? videoBitrate = videoStream?.BitRate;
- double? videoLevel = videoStream?.Level;
- string videoProfile = videoStream?.Profile;
- float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0;
- bool? isAnamorphic = videoStream?.IsAnamorphic;
- bool? isInterlaced = videoStream?.IsInterlaced;
- string videoCodecTag = videoStream?.CodecTag;
- bool? isAvc = videoStream?.IsAVC;
-
- TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
- int? packetLength = videoStream?.PacketLength;
- int? refFrames = videoStream?.RefFrames;
-
- int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
- int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
-
- if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
- {
- // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
- applyConditions = false;
- break;
- }
- }
-
- if (applyConditions)
- {
- var transcodingVideoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
- foreach (var transcodingVideoCodec in transcodingVideoCodecs)
- {
- if (i.ContainsAnyCodec(transcodingVideoCodec, transcodingProfile.Container))
- {
- ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile);
- isFirstAppliedCodecProfile = false;
- }
- }
- }
+ ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingVideoCodec, true, isFirstAppliedCodecProfile);
+ isFirstAppliedCodecProfile = false;
+ continue;
}
}
+ }
- // Honor requested max channels
- playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
+ // Honor requested max channels
+ playlistItem.GlobalMaxAudioChannels = options.MaxAudioChannels;
- int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem);
- playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
+ int audioBitrate = GetAudioBitrate(options.GetMaxBitrate(false) ?? 0, playlistItem.TargetAudioCodec, audioStream, playlistItem);
+ playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
- isFirstAppliedCodecProfile = true;
- foreach (var i in options.Profile.CodecProfiles)
+ 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;
+
+ var appliedAudioConditions = options.Profile.CodecProfiles
+ .Where(i => i.Type == CodecType.Video &&
+ i.ContainsAnyCodec(audioCodec, container) &&
+ i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
+ isFirstAppliedCodecProfile = true;
+ foreach (var i in appliedAudioConditions)
+ {
+ var transcodingAudioCodecs = ContainerProfile.SplitValue(audioCodec);
+ foreach (var transcodingAudioCodec in transcodingAudioCodecs)
{
- if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(transcodingProfile.AudioCodec, transcodingProfile.Container))
+ if (i.ContainsAnyCodec(transcodingAudioCodec, container))
{
- bool applyConditions = true;
- foreach (ProfileCondition applyCondition in i.ApplyConditions)
- {
- 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;
-
- if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))
- {
- // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item);
- applyConditions = false;
- break;
- }
- }
-
- if (applyConditions)
- {
- var transcodingAudioCodecs = ContainerProfile.SplitValue(transcodingProfile.AudioCodec);
- foreach (var transcodingAudioCodec in transcodingAudioCodecs)
- {
- if (i.ContainsAnyCodec(transcodingAudioCodec, transcodingProfile.Container))
- {
- ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile);
- isFirstAppliedCodecProfile = false;
- }
- }
- }
+ ApplyTranscodingConditions(playlistItem, i.Conditions, transcodingAudioCodec, true, isFirstAppliedCodecProfile);
+ isFirstAppliedCodecProfile = false;
+ break;
}
}
+ }
- var maxBitrateSetting = options.GetMaxBitrate(false);
- // Honor max rate
- if (maxBitrateSetting.HasValue)
- {
- var availableBitrateForVideo = maxBitrateSetting.Value;
-
- if (playlistItem.AudioBitrate.HasValue)
- {
- availableBitrateForVideo -= playlistItem.AudioBitrate.Value;
- }
+ var maxBitrateSetting = options.GetMaxBitrate(false);
+ // Honor max rate
+ if (maxBitrateSetting.HasValue)
+ {
+ var availableBitrateForVideo = maxBitrateSetting.Value;
- // Make sure the video bitrate is lower than bitrate settings but at least 64k
- long currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo;
- var longBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64000);
- playlistItem.VideoBitrate = longBitrate >= int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
+ if (playlistItem.AudioBitrate.HasValue)
+ {
+ availableBitrateForVideo -= playlistItem.AudioBitrate.Value;
}
- }
- playlistItem.TranscodeReasons = transcodeReasons.ToArray();
+ // Make sure the video bitrate is lower than bitrate settings but at least 64k
+ // Don't use Math.Clamp as availableBitrateForVideo can be lower then 64k.
+ var currentValue = playlistItem.VideoBitrate ?? availableBitrateForVideo;
+ playlistItem.VideoBitrate = Math.Max(Math.Min(availableBitrateForVideo, currentValue), 64_000);
+ }
- return playlistItem;
+ _logger.LogDebug(
+ "Transcode Result for Profile: {Profile}, Path: {Path}, PlayMethod: {PlayMethod}, AudioStreamIndex: {AudioStreamIndex}, SubtitleStreamIndex: {SubtitleStreamIndex}, Reasons: {TranscodeReason}",
+ options.Profile?.Name ?? "Anonymous Profile",
+ item.Path ?? "Unknown path",
+ playlistItem?.PlayMethod,
+ audioStream?.Index,
+ playlistItem?.SubtitleStreamIndex,
+ playlistItem?.TranscodeReasons);
}
private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels)
@@ -1000,63 +1044,30 @@ namespace MediaBrowser.Model.Dlna
return 7168000;
}
- private (PlayMethod? PlayMethod, List<TranscodeReason> TranscodeReasons) GetVideoDirectPlayProfile(
+ private (DirectPlayProfile Profile, PlayMethod? PlayMethod, int? AudioStreamIndex, TranscodeReason TranscodeReasons) GetVideoDirectPlayProfile(
VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
MediaStream audioStream,
+ IEnumerable<MediaStream> candidateAudioStreams,
+ MediaStream subtitleStream,
+ bool isEligibleForDirectPlay,
bool isEligibleForDirectStream)
{
if (options.ForceDirectPlay)
{
- return (PlayMethod.DirectPlay, new List<TranscodeReason>());
+ return (null, PlayMethod.DirectPlay, audioStream?.Index, 0);
}
if (options.ForceDirectStream)
{
- return (PlayMethod.DirectStream, new List<TranscodeReason>());
+ return (null, PlayMethod.DirectStream, audioStream?.Index, 0);
}
DeviceProfile profile = options.Profile;
string container = mediaSource.Container;
- // See if it can be direct played
- DirectPlayProfile directPlay = null;
- foreach (var p in profile.DirectPlayProfiles)
- {
- if (p.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(p, container, videoStream, audioStream))
- {
- directPlay = p;
- break;
- }
- }
-
- if (directPlay == null)
- {
- _logger.LogDebug(
- "Container: {Container}, Video: {Video}, Audio: {Audio} cannot be direct played by profile: {Profile} for path: {Path}",
- container,
- videoStream?.Codec ?? "no video",
- audioStream?.Codec ?? "no audio",
- profile.Name ?? "unknown profile",
- mediaSource.Path ?? "unknown path");
-
- return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles));
- }
-
- var conditions = new List<ProfileCondition>();
- foreach (var p in profile.ContainerProfiles)
- {
- if (p.Type == DlnaProfileType.Video
- && p.ContainsContainer(container))
- {
- foreach (var c in p.Conditions)
- {
- conditions.Add(c);
- }
- }
- }
-
+ // video
int? width = videoStream?.Width;
int? height = videoStream?.Height;
int? bitDepth = videoStream?.BitDepth;
@@ -1068,12 +1079,9 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC;
-
- int? audioBitrate = audioStream?.BitRate;
- int? audioChannels = audioStream?.Channels;
- string audioProfile = audioStream?.Profile;
- int? audioSampleRate = audioStream?.SampleRate;
- int? audioBitDepth = audioStream?.BitDepth;
+ // audio
+ var defaultLanguage = audioStream?.Language ?? string.Empty;
+ var defaultMarked = audioStream?.IsDefault ?? false;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream?.PacketLength;
@@ -1082,118 +1090,165 @@ namespace MediaBrowser.Model.Dlna
int? numAudioStreams = mediaSource.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = mediaSource.GetStreamCount(MediaStreamType.Video);
- // Check container conditions
- foreach (ProfileCondition i in conditions)
- {
- if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
- {
- LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
-
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
-
- return (null, transcodeReasons);
- }
- }
-
- string videoCodec = videoStream?.Codec;
+ var checkVideoConditions = (ProfileCondition[] conditions) =>
+ conditions.Where(applyCondition => !ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc));
- conditions = new List<ProfileCondition>();
- foreach (var i in profile.CodecProfiles)
- {
- if (i.Type == CodecType.Video && i.ContainsAnyCodec(videoCodec, container))
- {
- bool applyConditions = true;
- foreach (ProfileCondition applyCondition in i.ApplyConditions)
+ // Check container conditions
+ var containerProfileReasons = AggregateFailureConditions(
+ mediaSource,
+ profile,
+ "VideoCodecProfile",
+ profile.ContainerProfiles
+ .Where(containerProfile => containerProfile.Type == DlnaProfileType.Video && containerProfile.ContainsContainer(container))
+ .SelectMany(containerProfile => checkVideoConditions(containerProfile.Conditions)));
+
+ // Check video conditions
+ var videoCodecProfileReasons = AggregateFailureConditions(
+ mediaSource,
+ profile,
+ "VideoCodecProfile",
+ profile.CodecProfiles
+ .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container))
+ .SelectMany(codecProfile =>
{
- if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
+ var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions);
+ if (!failedApplyConditions.Any())
{
- // LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource);
- applyConditions = false;
- break;
+ return Array.Empty<ProfileCondition>();
}
- }
- if (applyConditions)
- {
- foreach (ProfileCondition c in i.Conditions)
- {
- conditions.Add(c);
- }
- }
- }
- }
+ var failedConditions = checkVideoConditions(codecProfile.Conditions);
+ return failedApplyConditions.Concat(failedConditions);
+ }));
- foreach (ProfileCondition i in conditions)
- {
- if (!ConditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
- {
- LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
+ // Check audiocandidates profile conditions
+ var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
+ TranscodeReason subtitleProfileReasons = 0;
+ if (subtitleStream != null)
+ {
+ var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null);
- return (null, transcodeReasons);
+ if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop
+ && subtitleProfile.Method != SubtitleDeliveryMethod.External
+ && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
+ {
+ _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", PlayMethod.DirectPlay);
+ subtitleProfileReasons |= TranscodeReason.SubtitleCodecNotSupported;
}
}
- if (audioStream != null)
- {
- string audioCodec = audioStream.Codec;
- conditions = new List<ProfileCondition>();
- bool? isSecondaryAudio = mediaSource.IsSecondaryAudio(audioStream);
-
- foreach (var i in profile.CodecProfiles)
+ var rankings = new[] { VideoReasons, AudioReasons, ContainerReasons };
+ var rank = (ref TranscodeReason a) =>
{
- if (i.Type == CodecType.VideoAudio && i.ContainsAnyCodec(audioCodec, container))
+ var index = 1;
+ foreach (var flag in rankings)
{
- bool applyConditions = true;
- foreach (ProfileCondition applyCondition in i.ApplyConditions)
+ var reason = a & flag;
+ if (reason != 0)
{
- if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
- {
- // LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource);
- applyConditions = false;
- break;
- }
+ a = reason;
+ return index;
}
- if (applyConditions)
- {
- foreach (ProfileCondition c in i.Conditions)
- {
- conditions.Add(c);
- }
- }
+ index++;
}
- }
- foreach (ProfileCondition i in conditions)
+ return index;
+ };
+
+ // Check DirectPlay profiles to see if it can be direct played
+ var analyzedProfiles = profile.DirectPlayProfiles
+ .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video)
+ .Select((directPlayProfile, order) =>
{
- if (!ConditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))
+ TranscodeReason directPlayProfileReasons = 0;
+ TranscodeReason audioCodecProfileReasons = 0;
+
+ // Check container type
+ if (!directPlayProfile.SupportsContainer(container))
{
- LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
+ directPlayProfileReasons |= TranscodeReason.ContainerNotSupported;
+ }
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- var transcodeReasons = transcodeReason.HasValue
- ? new List<TranscodeReason> { transcodeReason.Value }
- : new List<TranscodeReason>();
+ // Check video codec
+ string videoCodec = videoStream?.Codec;
+ if (!directPlayProfile.SupportsVideoCodec(videoCodec))
+ {
+ directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported;
+ }
- return (null, transcodeReasons);
+ // Check audio codec
+ var selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec));
+ if (selectedAudioStream == null)
+ {
+ directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported;
}
- }
+ else
+ {
+ audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
+ }
+
+ var failureReasons = directPlayProfileReasons | containerProfileReasons | videoCodecProfileReasons | audioCodecProfileReasons | subtitleProfileReasons;
+ var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
+
+ PlayMethod? playMethod = null;
+ if (failureReasons == 0 && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay)
+ {
+ playMethod = PlayMethod.DirectPlay;
+ }
+ else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null)
+ {
+ playMethod = PlayMethod.DirectStream;
+ }
+
+ var ranked = rank(ref failureReasons);
+ return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
+ })
+ .OrderByDescending(analysis => analysis.Result.PlayMethod)
+ .ThenBy(analysis => analysis.Order)
+ .ToArray()
+ .ToLookup(analysis => analysis.Result.PlayMethod != null);
+
+ var profileMatch = analyzedProfiles[true]
+ .Select(analysis => analysis.Result)
+ .FirstOrDefault();
+ if (profileMatch.Profile != null)
+ {
+ return profileMatch;
+ }
+
+ var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason;
+ if (failureReasons == 0)
+ {
+ failureReasons = TranscodeReason.DirectPlayError;
}
- if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
+ return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
+ }
+
+ private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string language, bool isDefault)
+ {
+ var profile = options.Profile;
+ var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, !audioStream.IsDefault);
+
+ var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
+ if (audioStream?.IsExternal == true)
{
- return (PlayMethod.DirectStream, new List<TranscodeReason>());
+ audioStreamFailureReasons |= TranscodeReason.AudioIsExternal;
}
- return (null, new List<TranscodeReason> { TranscodeReason.ContainerBitrateExceedsLimit });
+ return audioStreamFailureReasons;
+ }
+
+ private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable<ProfileCondition> conditions)
+ {
+ return conditions.Aggregate<ProfileCondition, TranscodeReason>(0, (reasons, i) =>
+ {
+ LogConditionFailure(profile, type, i, mediaSource);
+ var transcodeReasons = GetTranscodeReasonForFailedCondition(i);
+ return reasons | transcodeReasons;
+ });
}
private void LogConditionFailure(DeviceProfile profile, string type, ProfileCondition condition, MediaSourceInfo mediaSource)
@@ -1209,39 +1264,21 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
- private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay(
+ private TranscodeReason IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
- MediaStream subtitleStream,
- MediaStream audioStream,
VideoOptions options,
PlayMethod playMethod)
{
- if (subtitleStream != null)
- {
- var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, playMethod, _transcoderSupport, item.Container, null);
-
- if (subtitleProfile.Method != SubtitleDeliveryMethod.Drop
- && subtitleProfile.Method != SubtitleDeliveryMethod.External
- && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
- {
- _logger.LogDebug("Not eligible for {0} due to unsupported subtitles", playMethod);
- return (false, TranscodeReason.SubtitleCodecNotSupported);
- }
- }
-
- bool result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
+ bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
if (!result)
{
- return (false, TranscodeReason.ContainerBitrateExceedsLimit);
+ return TranscodeReason.ContainerBitrateExceedsLimit;
}
-
- if (audioStream?.IsExternal == true)
+ else
{
- return (false, TranscodeReason.AudioIsExternal);
+ return 0;
}
-
- return (true, null);
}
public static SubtitleProfile GetSubtitleProfile(
@@ -1401,7 +1438,7 @@ namespace MediaBrowser.Model.Dlna
return null;
}
- private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
+ private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
@@ -1444,7 +1481,7 @@ namespace MediaBrowser.Model.Dlna
private static void ValidateAudioInput(AudioOptions options)
{
- if (options.ItemId.Equals(Guid.Empty))
+ if (options.ItemId.Equals(default))
{
throw new ArgumentException("ItemId is required");
}
@@ -1465,6 +1502,47 @@ namespace MediaBrowser.Model.Dlna
}
}
+ private static IEnumerable<ProfileCondition> GetProfileConditionsForVideoAudio(
+ IEnumerable<CodecProfile> codecProfiles,
+ string container,
+ string codec,
+ int? audioChannels,
+ int? audioBitrate,
+ int? audioSampleRate,
+ int? audioBitDepth,
+ string audioProfile,
+ bool? isSecondaryAudio)
+ {
+ return codecProfiles
+ .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));
+ }
+
+ private static IEnumerable<ProfileCondition> GetProfileConditionsForAudio(
+ IEnumerable<CodecProfile> codecProfiles,
+ string container,
+ string codec,
+ int? audioChannels,
+ int? audioBitrate,
+ int? audioSampleRate,
+ int? audioBitDepth,
+ bool checkConditions)
+ {
+ var conditions = codecProfiles
+ .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);
+
+ if (!checkConditions)
+ {
+ return conditions;
+ }
+
+ 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)
{
foreach (ProfileCondition condition in conditions)
@@ -1744,10 +1822,22 @@ namespace MediaBrowser.Model.Dlna
var values = value
.Split('|', StringSplitOptions.RemoveEmptyEntries);
- if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny)
+ if (condition.Condition == ProfileConditionType.Equals)
{
item.SetOption(qualifier, "profile", string.Join(',', values));
}
+ else if (condition.Condition == ProfileConditionType.EqualsAny)
+ {
+ var currentValue = item.GetOption(qualifier, "profile");
+ if (!string.IsNullOrEmpty(currentValue) && values.Any(value => value == currentValue))
+ {
+ item.SetOption(qualifier, "profile", currentValue);
+ }
+ else
+ {
+ item.SetOption(qualifier, "profile", string.Join(',', values));
+ }
+ }
break;
}
@@ -1905,29 +1995,5 @@ namespace MediaBrowser.Model.Dlna
return true;
}
-
- private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, string container, MediaStream videoStream, MediaStream audioStream)
- {
- // Check container type
- if (!profile.SupportsContainer(container))
- {
- return false;
- }
-
- // Check video codec
- string videoCodec = videoStream?.Codec;
- if (!profile.SupportsVideoCodec(videoCodec))
- {
- return false;
- }
-
- // Check audio codec
- if (audioStream != null && !profile.SupportsAudioCodec(audioStream.Codec))
- {
- return false;
- }
-
- return true;
- }
}
}