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.cs946
1 files changed, 516 insertions, 430 deletions
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 7654337e7..0f48e985d 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;
@@ -227,7 +233,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- 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,9 +289,9 @@ 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 directPlayMethod = directPlayInfo.PlayMethod;
var transcodeReasons = directPlayInfo.TranscodeReasons;
int? inputAudioChannels = audioStream?.Channels;
@@ -297,55 +299,17 @@ namespace MediaBrowser.Model.Dlna
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;
- }
- }
-
- if (applyConditions)
- {
- conditions.AddRange(i.Conditions);
- }
- }
- }
+ 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;
- bool all = true;
- foreach (ProfileCondition c in conditions)
+ if (audioFailureReasons == TranscodeReason.None)
{
- if (!ConditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth))
- {
- LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
- transcodeReasons |= GetTranscodeReasonForFailedCondition(c);
-
- 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;
}
@@ -370,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
@@ -444,9 +372,9 @@ namespace MediaBrowser.Model.Dlna
return options.GetMaxBitrate(isAudio);
}
- private (IEnumerable<PlayMethod> PlayMethods, 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)
@@ -457,20 +385,21 @@ 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 = TranscodeReason.None;
- // 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 (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
+ if (IsItemBitrateEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, PlayMethod.DirectPlay))
{
- if (options.EnableDirectStream)
+ if (options.EnableDirectPlay)
{
- playMethods.Add(PlayMethod.DirectStream);
+ return (directPlayProfile, PlayMethod.DirectPlay, TranscodeReason.None);
}
}
else
@@ -479,15 +408,14 @@ namespace MediaBrowser.Model.Dlna
}
}
- // 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 (IsItemBitrateEligibleForDirectPlay(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
@@ -496,12 +424,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- if (playMethods.Count > 0)
- {
- transcodeReasons = TranscodeReason.None;
- }
-
- return (playMethods, transcodeReasons);
+ return (directPlayProfile, null, transcodeReasons);
}
private static TranscodeReason GetTranscodeReasonsFromDirectPlayProfile(MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<DirectPlayProfile> directPlayProfiles)
@@ -592,30 +515,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;
@@ -631,14 +553,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)
@@ -667,11 +596,27 @@ 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);
+ var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, options, PlayMethod.DirectPlay);
+ var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == TranscodeReason.None);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == TranscodeReason.None);
var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
@@ -683,183 +628,292 @@ namespace MediaBrowser.Model.Dlna
isEligibleForDirectPlay,
isEligibleForDirectStream);
+ 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 |= directPlayInfo.TranscodeReasons;
+ _logger.LogInformation(
+ "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 (playlistItem.PlayMethod != PlayMethod.Transcode)
+ playlistItem.TranscodeReasons = transcodeReasons;
+
+ if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream)
{
- playlistItem.TranscodeReasons = transcodeReasons;
- return playlistItem;
+ // 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;
+ }
+ }
}
- // Can't direct play, find the transcoding profile
- TranscodingProfile transcodingProfile = null;
- foreach (var i in options.Profile.TranscodingProfiles)
+ _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))
+ {
+ return null;
+ }
+
+ 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);
+ var match = ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec) &&
+ options.Profile.CodecProfiles
+ .Any(i => i.Type == CodecType.Video &&
+ i.ContainsAnyCodec(transcodingProfile.VideoCodec, transcodingProfile.Container) &&
+ 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)));
+
+ return match ? 1 : 2;
+ })
+ .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 beter 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;
+
+ TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? packetLength = videoStream?.PacketLength;
+ int? refFrames = videoStream?.RefFrames;
- SetStreamInfoOptionsFromTranscodingProfile(playlistItem, transcodingProfile);
+ 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;
+ // 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);
+ }
- return playlistItem;
+ _logger.LogInformation(
+ "Transcode Result for Profile: {0}, Path: {1}, PlayMethod: {2}, AudioStreamIndex: {3}, SubtitleStreamIndex: {4}, Reasons: {5}",
+ 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)
@@ -988,63 +1042,30 @@ namespace MediaBrowser.Model.Dlna
return 7168000;
}
- private (PlayMethod? PlayMethod, 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, TranscodeReason.None);
+ return (null, PlayMethod.DirectPlay, audioStream?.Index, TranscodeReason.None);
}
if (options.ForceDirectStream)
{
- return (PlayMethod.DirectStream, TranscodeReason.None);
+ return (null, PlayMethod.DirectStream, audioStream?.Index, TranscodeReason.None);
}
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;
@@ -1056,12 +1077,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;
@@ -1070,106 +1088,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 transcodeReasons = GetTranscodeReasonForFailedCondition(i);
- 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);
+ }));
+
+ // Check audiocandidates profile conditions
+ var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
- foreach (ProfileCondition i in conditions)
+ TranscodeReason subtitleProfileReasons = TranscodeReason.None;
+ if (subtitleStream != null)
{
- 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);
+ var subtitleProfile = GetSubtitleProfile(mediaSource, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.DirectPlay, _transcoderSupport, container, null);
- var transcodeReason = GetTranscodeReasonForFailedCondition(i);
- return (null, transcodeReason);
+ 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 != TranscodeReason.None)
{
- 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))
+ var directPlayProfileReasons = TranscodeReason.None;
+ var audioCodecProfileReasons = TranscodeReason.None;
+
+ // Check container type
+ if (!directPlayProfile.SupportsContainer(container))
{
- LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
+ directPlayProfileReasons |= TranscodeReason.ContainerNotSupported;
+ }
- var transcodeReasons = GetTranscodeReasonForFailedCondition(i);
- return (null, transcodeReasons);
+ // Check video codec
+ string videoCodec = videoStream?.Codec;
+ if (!directPlayProfile.SupportsVideoCodec(videoCodec))
+ {
+ directPlayProfileReasons |= TranscodeReason.VideoCodecNotSupported;
}
- }
+
+ // 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 == TranscodeReason.None && isEligibleForDirectPlay && mediaSource.SupportsDirectPlay)
+ {
+ playMethod = PlayMethod.DirectPlay;
+ }
+ else if (directStreamFailureReasons == TranscodeReason.None && 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;
}
- if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
+ var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason;
+ if (failureReasons == TranscodeReason.None)
{
- return (PlayMethod.DirectStream, TranscodeReason.None);
+ failureReasons = TranscodeReason.DirectPlayError;
}
- return (null, TranscodeReason.ContainerBitrateExceedsLimit);
+ 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)
+ {
+ audioStreamFailureReasons |= TranscodeReason.AudioIsExternal;
+ }
+
+ return audioStreamFailureReasons;
+ }
+
+ private TranscodeReason AggregateFailureConditions(MediaSourceInfo mediaSource, DeviceProfile profile, string type, IEnumerable<ProfileCondition> conditions)
+ {
+ return conditions.Aggregate(TranscodeReason.None, (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)
@@ -1188,38 +1265,18 @@ namespace MediaBrowser.Model.Dlna
private TranscodeReason IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
- MediaStream subtitleStream,
- MediaStream audioStream,
VideoOptions options,
PlayMethod playMethod)
{
- var reason = TranscodeReason.None;
- 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);
- reason |= TranscodeReason.SubtitleCodecNotSupported;
- }
- }
-
bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
if (!result)
{
- reason |= TranscodeReason.ContainerBitrateExceedsLimit;
+ return TranscodeReason.ContainerBitrateExceedsLimit;
}
-
- // TODO:6450 support external audio in DirectStream?
- if (audioStream?.IsExternal == true)
+ else
{
- reason |= TranscodeReason.AudioIsExternal;
+ return TranscodeReason.None;
}
-
- return reason;
}
public static SubtitleProfile GetSubtitleProfile(
@@ -1443,6 +1500,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)
@@ -1722,10 +1820,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;
}
@@ -1883,29 +1993,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;
- }
}
}