From e4101128e011c089bad46a524856438154cfbd3d Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 22 Apr 2024 06:19:17 +0800 Subject: feat: add audio remux to UniversalAudioController Signed-off-by: gnattu --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 63 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Model/Dlna') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 55d1c3d51a..f9fe7d351a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.Model.Dlna var inputAudioSampleRate = audioStream?.SampleRate; var inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethod.HasValue) + if (directPlayMethod is PlayMethod.DirectPlay) { var profile = options.Profile; var audioFailureConditions = GetProfileConditionsForAudio(profile.CodecProfiles, item.Container, audioStream?.Codec, inputAudioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, true); @@ -124,6 +124,41 @@ namespace MediaBrowser.Model.Dlna } } + if (directPlayMethod is PlayMethod.DirectStream) + { + var remuxContainer = item.TranscodingContainer ?? "ts"; + bool codeIsSupported; + if (item.TranscodingSubProtocol == MediaStreamProtocol.hls) + { + // Enforce HLS audio codec restrictions + if (string.Equals(remuxContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + codeIsSupported = _supportedHlsAudioCodecsMp4.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + else + { + codeIsSupported = _supportedHlsAudioCodecsTs.Contains(directPlayInfo.Profile?.AudioCodec ?? directPlayInfo.Profile?.Container); + } + } + else + { + // Let's assume the client has given a correct container for http + codeIsSupported = true; + } + + if (codeIsSupported) + { + playlistItem.PlayMethod = directPlayMethod.Value; + playlistItem.Container = remuxContainer; + playlistItem.TranscodeReasons = transcodeReasons; + playlistItem.SubProtocol = item.TranscodingSubProtocol; + return playlistItem; + } + + transcodeReasons |= TranscodeReason.AudioCodecNotSupported; + playlistItem.TranscodeReasons = transcodeReasons; + } + TranscodingProfile? transcodingProfile = null; foreach (var tcProfile in options.Profile.TranscodingProfiles) { @@ -387,6 +422,14 @@ namespace MediaBrowser.Model.Dlna item.Path ?? "Unknown path", audioStream.Codec ?? "Unknown codec"); + var directStreamProfile = options.Profile.DirectPlayProfiles + .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectStreamSupported(x, item, audioStream)); + + if (directStreamProfile is not null) + { + return (directStreamProfile, PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported); + } + return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } @@ -2129,5 +2172,23 @@ namespace MediaBrowser.Model.Dlna return true; } + + private static bool IsAudioDirectStreamSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + // Check container type, this should NOT be supported + if (!profile.SupportsContainer(item.Container)) + { + // Check audio codec, we cannot use the SupportsAudioCodec here + // Because that one assumes empty container supports all codec, which is just useless + string? audioCodec = audioStream?.Codec; + if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) || + string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } } } -- cgit v1.2.3 From a16d3d488704be5a2e8b528c446f06588831246d Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 22 Apr 2024 22:31:41 +0800 Subject: Allow clients to send audio container override for HLS This will improve flexibility due to overcome the complex compatibility situation of HLS Signed-off-by: gnattu --- Jellyfin.Api/Controllers/UniversalAudioController.cs | 3 ++- MediaBrowser.Model/Dlna/StreamBuilder.cs | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Model/Dlna') diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 3115c4750a..1305d14176 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -187,7 +187,8 @@ public class UniversalAudioController : BaseJellyfinApiController var supportedHlsContainers = new[] { "ts", "mp4" }; // fallback to mpegts if device reports some weird value unsupported by hls - var segmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts"; + var requestedSegmentContainer = Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "ts"; + var segmentContainer = Array.Exists(supportedHlsContainers, element => element == mediaSource.TranscodingContainer) ? mediaSource.TranscodingContainer : requestedSegmentContainer; var dynamicHlsRequestDto = new HlsAudioRequestDto { Id = itemId, diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index f9fe7d351a..20d9cd8d53 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -127,6 +127,10 @@ namespace MediaBrowser.Model.Dlna if (directPlayMethod is PlayMethod.DirectStream) { var remuxContainer = item.TranscodingContainer ?? "ts"; + var supportedHlsContainers = new[] { "ts", "mp4" }; + // If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference + // The client should be responsible to ensure this container is compatible + remuxContainer = Array.Exists(supportedHlsContainers, element => element == directPlayInfo.Profile?.Container) ? directPlayInfo.Profile?.Container : remuxContainer; bool codeIsSupported; if (item.TranscodingSubProtocol == MediaStreamProtocol.hls) { @@ -152,6 +156,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.Container = remuxContainer; playlistItem.TranscodeReasons = transcodeReasons; playlistItem.SubProtocol = item.TranscodingSubProtocol; + item.TranscodingContainer = remuxContainer; return playlistItem; } -- cgit v1.2.3 From ed9d27bb3a5fb2b187c453dcc2c572b7123f4beb Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 13:35:53 +0800 Subject: Correctly set bitrate limit for remuxing Signed-off-by: gnattu --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Model/Dlna') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 20d9cd8d53..c6509035c1 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -419,6 +419,7 @@ namespace MediaBrowser.Model.Dlna var directPlayProfile = options.Profile.DirectPlayProfiles .FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream)); + TranscodeReason transcodeReasons = 0; if (directPlayProfile is null) { _logger.LogDebug( @@ -432,17 +433,20 @@ namespace MediaBrowser.Model.Dlna if (directStreamProfile is not null) { - return (directStreamProfile, PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported); + directPlayProfile = directStreamProfile; + transcodeReasons |= TranscodeReason.ContainerNotSupported; + } + else + { + return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } - - return (null, null, GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } - TranscodeReason transcodeReasons = 0; - // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play - if (item.SupportsDirectPlay) + // Note: As of 10.10 codebase, SupportsDirectPlay is always true because the MediaSourceInfo initializes this key as true + // Need to check additionally for current transcode reasons + if (item.SupportsDirectPlay && transcodeReasons == 0) { if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { -- cgit v1.2.3 From c8f157444c85c9fb79514c2b0a5e35dc2d083921 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 14:08:39 +0800 Subject: Make comment better Signed-off-by: gnattu --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'MediaBrowser.Model/Dlna') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index c6509035c1..410a44776a 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -2185,6 +2185,7 @@ namespace MediaBrowser.Model.Dlna private static bool IsAudioDirectStreamSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) { // Check container type, this should NOT be supported + // If the container is supported, the file should be directly played if (!profile.SupportsContainer(item.Container)) { // Check audio codec, we cannot use the SupportsAudioCodec here -- cgit v1.2.3 From ce7cbc1f64e2b443369ffb8be3f715062c24910f Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 18 Jul 2024 01:49:55 +0800 Subject: Don't check the misleading options.EnableDirectStream for direct stream availability Signed-off-by: gnattu --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Model/Dlna') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 410a44776a..d9520b2e54 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -466,7 +466,10 @@ namespace MediaBrowser.Model.Dlna { if (!IsBitrateLimitExceeded(item, options.GetMaxBitrate(true) ?? 0)) { - if (options.EnableDirectStream) + // Note: as of 10.10 codebase, the options.EnableDirectStream is always false due to + // "direct-stream http streaming is currently broken" + // Don't check that option for audio as we always assume that is supported + if (transcodeReasons == TranscodeReason.ContainerNotSupported) { return (directPlayProfile, PlayMethod.DirectStream, transcodeReasons); } -- cgit v1.2.3 From 855215673a113268565065b100797e2f73636c0f Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 18 Jul 2024 17:50:19 +0800 Subject: Use string.Equals Co-authored-by: Bond-009 --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Model/Dlna') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index d9520b2e54..2868521a79 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -130,7 +130,7 @@ namespace MediaBrowser.Model.Dlna var supportedHlsContainers = new[] { "ts", "mp4" }; // If the container specified for the profile is an HLS supported container, use that container instead, overriding the preference // The client should be responsible to ensure this container is compatible - remuxContainer = Array.Exists(supportedHlsContainers, element => element == directPlayInfo.Profile?.Container) ? directPlayInfo.Profile?.Container : remuxContainer; + remuxContainer = Array.Exists(supportedHlsContainers, element => string.Equals(element, directPlayInfo.Profile?.Container, StringComparison.OrdinalIgnoreCase)) ? directPlayInfo.Profile?.Container : remuxContainer; bool codeIsSupported; if (item.TranscodingSubProtocol == MediaStreamProtocol.hls) { -- cgit v1.2.3