From 2d8f7b46f11ee458fe0b31d8e1473aaaea9cf296 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 15 May 2022 20:22:13 -0400 Subject: [PATCH] Merge pull request #7699 from Shadowghost/streambuilder-fix (cherry picked from commit b46d61dfdf5e068a9feb26d8b41377a9fdb2cba6) Signed-off-by: crobibero --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 50 +++++++++---------- .../Dlna/StreamBuilderTests.cs | 49 ++++++++++-------- .../DeviceProfile-Tizen3-stereo.json | 8 +-- .../DeviceProfile-Tizen4-4K-5.1.json | 8 +-- 4 files changed, 60 insertions(+), 55 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index ee9cfeeca8..ce75e678ea 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -385,7 +385,7 @@ namespace MediaBrowser.Model.Dlna // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay) { - if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) + if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) { if (options.EnableDirectPlay) { @@ -401,7 +401,7 @@ namespace MediaBrowser.Model.Dlna // While options takes the network and other factors into account. Only applies to direct stream if (item.SupportsDirectStream) { - if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) + if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) { if (options.EnableDirectStream) { @@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; - 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; + var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); + var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); + bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0); + bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0); + var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility; _logger.LogDebug( "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", @@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna var directPlay = directPlayInfo.PlayMethod; transcodeReasons |= directPlayInfo.TranscodeReasons; - if (directPlay != null) + if (directPlay.HasValue) { directPlayProfile = directPlayInfo.Profile; playlistItem.PlayMethod = directPlay.Value; @@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.TranscodeReasons = transcodeReasons; - if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream) + if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay) { // Can't direct play, find the transcoding profile // If we do this for direct-stream we will overwrite the info @@ -687,6 +687,8 @@ namespace MediaBrowser.Model.Dlna BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec); + playlistItem.PlayMethod = PlayMethod.Transcode; + if (subtitleStream != null) { var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); @@ -696,14 +698,9 @@ namespace MediaBrowser.Model.Dlna playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; } - if (playlistItem.PlayMethod != PlayMethod.DirectPlay) + if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) { - playlistItem.PlayMethod = PlayMethod.Transcode; - - if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0) - { - ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true); - } + ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true); } } } @@ -771,12 +768,12 @@ namespace MediaBrowser.Model.Dlna private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable candidateAudioStreams, string container, string videoCodec, string audioCodec) { - // prefer matching video codecs + // 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 + // Copy video codec options as a starting point, this applies to transcode and direct-stream playlistItem.MaxFramerate = videoStream?.AverageFrameRate; var qualifier = videoStream?.Codec; if (videoStream?.Level != null) @@ -799,7 +796,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString()); } - // prefer matching audio codecs, could do better here + // 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; @@ -809,7 +806,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.AudioStreamIndex = audioStream.Index; playlistItem.AudioCodecs = new[] { audioStream.Codec }; - // copy matching audio codec options + // Copy matching audio codec options playlistItem.AudioSampleRate = audioStream.SampleRate; playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString()); @@ -1070,7 +1067,7 @@ namespace MediaBrowser.Model.Dlna DeviceProfile profile = options.Profile; string container = mediaSource.Container; - // video + // Video int? width = videoStream?.Width; int? height = videoStream?.Height; int? bitDepth = videoStream?.BitDepth; @@ -1082,7 +1079,7 @@ namespace MediaBrowser.Model.Dlna bool? isInterlaced = videoStream?.IsInterlaced; string videoCodecTag = videoStream?.CodecTag; bool? isAvc = videoStream?.IsAVC; - // audio + // Audio var defaultLanguage = audioStream?.Language ?? string.Empty; var defaultMarked = audioStream?.IsDefault ?? false; @@ -1211,6 +1208,7 @@ namespace MediaBrowser.Model.Dlna return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked); }) .OrderByDescending(analysis => analysis.Result.PlayMethod) + .ThenByDescending(analysis => analysis.Rank) .ThenBy(analysis => analysis.Order) .ToArray() .ToLookup(analysis => analysis.Result.PlayMethod != null); @@ -1223,7 +1221,7 @@ namespace MediaBrowser.Model.Dlna return profileMatch; } - var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason; + var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason; if (failureReasons == 0) { failureReasons = TranscodeReason.DirectPlayError; @@ -1269,13 +1267,13 @@ namespace MediaBrowser.Model.Dlna mediaSource.Path ?? "Unknown path"); } - private TranscodeReason IsEligibleForDirectPlay( + private TranscodeReason IsBitrateEligibleForDirectPlayback( MediaSourceInfo item, long maxBitrate, VideoOptions options, PlayMethod playMethod) { - bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod); + bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod); if (!result) { return TranscodeReason.ContainerBitrateExceedsLimit; @@ -1443,7 +1441,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) + private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) { // Don't restrict by bitrate if coming from an external domain if (item.IsRemote) diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs index 0035dc6653..9baf6877d9 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Firefox @@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Safari @@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] - [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // TranscodeMedia @@ -177,7 +177,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Firefox @@ -187,7 +187,7 @@ namespace Jellyfin.Model.Tests [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Safari @@ -338,23 +338,23 @@ namespace Jellyfin.Model.Tests Assert.NotNull(mediaSource); var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video); var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio); - // TODO: check AudioStreamIndex vs options.AudioStreamIndex + // TODO: Check AudioStreamIndex vs options.AudioStreamIndex var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex); var uri = ParseUri(val); if (playMethod == PlayMethod.DirectPlay) { - // check expected container + // Check expected container var containers = ContainerProfile.SplitValue(mediaSource.Container); - // TODO: test transcode too + // TODO: Test transcode too // Assert.Contains(uri.Extension, containers); - // check expected video codec (1) + // Check expected video codec (1) Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Single(val.TargetVideoCodec); - // check expected audio codecs (1) + // Check expected audio codecs (1) Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec); Assert.Single(val.TargetAudioCodec); // Assert.Single(val.AudioCodecs); @@ -370,7 +370,7 @@ namespace Jellyfin.Model.Tests Assert.NotEmpty(val.VideoCodecs); Assert.NotEmpty(val.AudioCodecs); - // check expected container (todo: this could be a test param) + // Check expected container (todo: this could be a test param) if (transcodeProtocol == "http") { // Assert.Equal("webm", val.Container); @@ -403,32 +403,39 @@ namespace Jellyfin.Model.Tests stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); } - // todo: fill out tests here + // TODO: Fill out tests here } // DirectStream and Remux else { - // check expected video codec (1) + // Check expected video codec (1) Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Single(val.TargetVideoCodec); if (transcodeMode == "DirectStream") { + // Check expected audio codecs (1) if (!targetAudioStream.IsExternal) { - // check expected audio codecs (1) - Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); + if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported)) + { + Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); + } + else + { + Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); + } } } else if (transcodeMode == "Remux") { - // check expected audio codecs (1) + // Check expected audio codecs (1) Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); Assert.Single(val.AudioCodecs); } - // video details + // Video details var videoStream = targetVideoStream; Assert.False(val.EstimateContentLength); Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); @@ -437,10 +444,10 @@ namespace Jellyfin.Model.Tests Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); - // audio codec not supported + // Audio codec not supported if ((why & TranscodeReason.AudioCodecNotSupported) != 0) { - // audio stream specified + // Audio stream specified if (options.AudioStreamIndex >= 0) { // TODO:fixme @@ -450,10 +457,10 @@ namespace Jellyfin.Model.Tests } } - // audio stream not specified + // Audio stream not specified else { - // TODO:fixme + // TODO: Fixme Assert.All(audioStreams, stream => { if (!stream.IsExternal) diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 719f553ce8..53637b7931 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -45,8 +45,8 @@ }, { "Container": "wmv", - "AudioCodec": "", - "VideoCodec": "", + "AudioCodec": "wma", + "VideoCodec": "wmv,vc1", "Type": "Video", "$type": "DirectPlayProfile" }, @@ -59,8 +59,8 @@ }, { "Container": "asf", - "AudioCodec": "", - "VideoCodec": "", + "AudioCodec": "wma", + "VideoCodec": "wmv,vc1", "Type": "Video", "$type": "DirectPlayProfile" }, diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index 79b1f4fdb1..d3ef22c256 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -45,8 +45,8 @@ }, { "Container": "wmv", - "AudioCodec": "", - "VideoCodec": "", + "AudioCodec": "wma", + "VideoCodec": "wmv,vc1", "Type": "Video", "$type": "DirectPlayProfile" }, @@ -59,8 +59,8 @@ }, { "Container": "asf", - "AudioCodec": "", - "VideoCodec": "", + "AudioCodec": "wma", + "VideoCodec": "wmv,vc1", "Type": "Video", "$type": "DirectPlayProfile" },