diff options
| author | crobibero <cody@robibe.ro> | 2021-06-20 07:09:24 -0600 |
|---|---|---|
| committer | crobibero <cody@robibe.ro> | 2021-06-20 07:09:24 -0600 |
| commit | 23dd6e2d9fdcb65f3ca13914c83f8ffbbd7e1743 (patch) | |
| tree | 38e905846e02da5e255b1ef23ea27a749b53c453 /MediaBrowser.Model/Dlna | |
| parent | 078b6244ee060b2c5caddc3ba8a60633c4e95054 (diff) | |
| parent | 0c3dcdf77b0d124517bffa608bfddf7d8f7682db (diff) | |
Merge remote-tracking branch 'upstream/master' into baseitemkind-fixes
Diffstat (limited to 'MediaBrowser.Model/Dlna')
18 files changed, 745 insertions, 787 deletions
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index bbb8bf4263..4d4d8d78cb 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -34,20 +34,20 @@ namespace MediaBrowser.Model.Dlna public DeviceProfile Profile { get; set; } /// <summary> - /// Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested. + /// Gets or sets a media source id. Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested. /// </summary> public string MediaSourceId { get; set; } public string DeviceId { get; set; } /// <summary> - /// Allows an override of supported number of audio channels - /// Example: DeviceProfile supports five channel, but user only has stereo speakers + /// Gets or sets an override of supported number of audio channels + /// Example: DeviceProfile supports five channel, but user only has stereo speakers. /// </summary> public int? MaxAudioChannels { get; set; } /// <summary> - /// The application's configured quality setting. + /// Gets or sets the application's configured quality setting. /// </summary> public int? MaxBitrate { get; set; } @@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dlna /// <summary> /// Gets the maximum bitrate. /// </summary> + /// <param name="isAudio">Whether or not this is audio.</param> /// <returns>System.Nullable<System.Int32>.</returns> public int? GetMaxBitrate(bool isAudio) { diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index d4fd3e6730..8343cf028b 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -9,6 +9,12 @@ namespace MediaBrowser.Model.Dlna { public class CodecProfile { + public CodecProfile() + { + Conditions = Array.Empty<ProfileCondition>(); + ApplyConditions = Array.Empty<ProfileCondition>(); + } + [XmlAttribute("type")] public CodecType Type { get; set; } @@ -22,12 +28,6 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("container")] public string Container { get; set; } - public CodecProfile() - { - Conditions = Array.Empty<ProfileCondition>(); - ApplyConditions = Array.Empty<ProfileCondition>(); - } - public string[] GetCodecs() { return ContainerProfile.SplitValue(Codec); diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index faf1ee41be..55c4dd0742 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 using System; -using System.Linq; using System.Globalization; +using System.Linq; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index 56c89d854f..7409660881 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -12,22 +11,12 @@ namespace MediaBrowser.Model.Dlna [XmlAttribute("type")] public DlnaProfileType Type { get; set; } - public ProfileCondition[] Conditions { get; set; } + public ProfileCondition[]? Conditions { get; set; } = Array.Empty<ProfileCondition>(); [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; - public ContainerProfile() - { - Conditions = Array.Empty<ProfileCondition>(); - } - - public string[] GetContainers() - { - return SplitValue(Container); - } - - public static string[] SplitValue(string value) + public static string[] SplitValue(string? value) { if (string.IsNullOrEmpty(value)) { @@ -37,14 +26,14 @@ namespace MediaBrowser.Model.Dlna return value.Split(',', StringSplitOptions.RemoveEmptyEntries); } - public bool ContainsContainer(string container) + public bool ContainsContainer(string? container) { - var containers = GetContainers(); + var containers = SplitValue(Container); return ContainsContainer(containers, container); } - public static bool ContainsContainer(string profileContainers, string inputContainer) + public static bool ContainsContainer(string? profileContainers, string? inputContainer) { var isNegativeList = false; if (profileContainers != null && profileContainers.StartsWith('-')) @@ -56,46 +45,30 @@ namespace MediaBrowser.Model.Dlna return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, string? inputContainer) { return ContainsContainer(profileContainers, false, inputContainer); } - public static bool ContainsContainer(string[] profileContainers, bool isNegativeList, string inputContainer) + public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer) { - if (profileContainers.Length == 0) + if (profileContainers == null || profileContainers.Length == 0) { + // Empty profiles always support all containers/codecs return true; } - if (isNegativeList) - { - var allInputContainers = SplitValue(inputContainer); + var allInputContainers = SplitValue(inputContainer); - foreach (var container in allInputContainers) - { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - return true; - } - else + foreach (var container in allInputContainers) { - var allInputContainers = SplitValue(inputContainer); - - foreach (var container in allInputContainers) + if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) { - if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase)) - { - return true; - } + return !isNegativeList; } - - return false; } + + return isNegativeList; } } } diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 50e3374f77..600a441570 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -10,14 +10,8 @@ namespace MediaBrowser.Model.Dlna { public class ContentFeatureBuilder { - private readonly DeviceProfile _profile; - - public ContentFeatureBuilder(DeviceProfile profile) - { - _profile = profile; - } - - public string BuildImageHeader( + public static string BuildImageHeader( + DeviceProfile profile, string container, int? width, int? height, @@ -38,27 +32,31 @@ namespace MediaBrowser.Model.Dlna ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = _profile.GetImageMediaProfile( - container, - width, - height); - if (string.IsNullOrEmpty(orgPn)) { + ResponseProfile mediaProfile = profile.GetImageMediaProfile( + container, + width, + height); + orgPn = mediaProfile?.OrgPn; + + if (string.IsNullOrEmpty(orgPn)) + { + orgPn = GetImageOrgPnValue(container, width, height); + } } if (string.IsNullOrEmpty(orgPn)) { - orgPn = GetImageOrgPnValue(container, width, height); + return orgOp.TrimStart(';') + orgCi + dlnaflags; } - string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; - - return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + return "DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags; } - public string BuildAudioHeader( + public static string BuildAudioHeader( + DeviceProfile profile, string container, string audioCodec, int? audioBitrate, @@ -81,20 +79,20 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.DlnaV15; // if (isDirectStream) - //{ - // flagValue = flagValue | DlnaFlags.ByteBasedSeek; - //} - // else if (runtimeTicks.HasValue) - //{ - // flagValue = flagValue | DlnaFlags.TimeBasedSeek; - //} + // { + // flagValue = flagValue | DlnaFlags.ByteBasedSeek; + // } + // else if (runtimeTicks.HasValue) + // { + // flagValue = flagValue | DlnaFlags.TimeBasedSeek; + // } string dlnaflags = string.Format( CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); - ResponseProfile mediaProfile = _profile.GetAudioMediaProfile( + ResponseProfile mediaProfile = profile.GetAudioMediaProfile( container, audioCodec, audioChannels, @@ -109,12 +107,16 @@ namespace MediaBrowser.Model.Dlna orgPn = GetAudioOrgPnValue(container, audioBitrate, audioSampleRate, audioChannels); } - string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; + if (string.IsNullOrEmpty(orgPn)) + { + return orgOp.TrimStart(';') + orgCi + dlnaflags; + } - return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + return "DLNA.ORG_PN=" + orgPn + orgOp + orgCi + dlnaflags; } - public List<string> BuildVideoHeader( + public static List<string> BuildVideoHeader( + DeviceProfile profile, string container, string videoCodec, string audioCodec, @@ -150,18 +152,20 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.DlnaV15; // if (isDirectStream) - //{ - // flagValue = flagValue | DlnaFlags.ByteBasedSeek; - //} - // else if (runtimeTicks.HasValue) - //{ - // flagValue = flagValue | DlnaFlags.TimeBasedSeek; - //} - - string dlnaflags = string.Format(CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}", - DlnaMaps.FlagsToString(flagValue)); - - ResponseProfile mediaProfile = _profile.GetVideoMediaProfile( + // { + // flagValue = flagValue | DlnaFlags.ByteBasedSeek; + // } + // else if (runtimeTicks.HasValue) + // { + // flagValue = flagValue | DlnaFlags.TimeBasedSeek; + // } + + string dlnaflags = string.Format( + CultureInfo.InvariantCulture, + ";DLNA.ORG_FLAGS={0}", + DlnaMaps.FlagsToString(flagValue)); + + ResponseProfile mediaProfile = profile.GetVideoMediaProfile( container, audioCodec, videoCodec, @@ -190,9 +194,9 @@ namespace MediaBrowser.Model.Dlna } else { - foreach (string s in GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp)) + foreach (var s in GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp)) { - orgPnValues.Add(s); + orgPnValues.Add(s.ToString()); break; } } @@ -201,20 +205,20 @@ namespace MediaBrowser.Model.Dlna foreach (string orgPn in orgPnValues) { - string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; - - var value = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); - - contentFeatureList.Add(value); + if (string.IsNullOrEmpty(orgPn)) + { + contentFeatureList.Add(orgOp.TrimStart(';') + orgCi + dlnaflags); + continue; + } + else + { + contentFeatureList.Add("DLNA.ORG_PN=" + orgPn + orgCi + dlnaflags); + } } if (orgPnValues.Count == 0) { - string contentFeatures = string.Empty; - - var value = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); - - contentFeatureList.Add(value); + contentFeatureList.Add(orgOp.TrimStart(';') + orgCi + dlnaflags); } return contentFeatureList; @@ -222,19 +226,14 @@ namespace MediaBrowser.Model.Dlna private static string GetImageOrgPnValue(string container, int? width, int? height) { - MediaFormatProfile? format = new MediaFormatProfileResolver() - .ResolveImageFormat( - container, - width, - height); + MediaFormatProfile? format = MediaFormatProfileResolver.ResolveImageFormat(container, width, height); return format.HasValue ? format.Value.ToString() : null; } private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels) { - MediaFormatProfile? format = new MediaFormatProfileResolver() - .ResolveAudioFormat( + MediaFormatProfile? format = MediaFormatProfileResolver.ResolveAudioFormat( container, audioBitrate, audioSampleRate, @@ -243,9 +242,9 @@ namespace MediaBrowser.Model.Dlna return format.HasValue ? format.Value.ToString() : null; } - private static string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) + private static MediaFormatProfile[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp) { - return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp); + return MediaFormatProfileResolver.ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp); } } } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index ff51866587..feb3d880ec 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CA1819 // Properties should not return arrays using System; +using System.ComponentModel; using System.Linq; using System.Xml.Serialization; using MediaBrowser.Model.MediaInfo; @@ -8,226 +8,219 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { /// <summary> - /// Defines the <see cref="DeviceProfile" />. + /// A <see cref="DeviceProfile" /> represents a set of metadata which determines which content a certain device is able to play. + /// <br/> + /// Specifically, it defines the supported <see cref="ContainerProfiles">containers</see> and + /// <see cref="CodecProfiles">codecs</see> (video and/or audio, including codec profiles and levels) + /// the device is able to direct play (without transcoding or remuxing), + /// as well as which <see cref="TranscodingProfiles">containers/codecs to transcode to</see> in case it isn't. /// </summary> [XmlRoot("Profile")] public class DeviceProfile { /// <summary> - /// Initializes a new instance of the <see cref="DeviceProfile"/> class. + /// Gets or sets the name of this device profile. /// </summary> - public DeviceProfile() - { - DirectPlayProfiles = Array.Empty<DirectPlayProfile>(); - TranscodingProfiles = Array.Empty<TranscodingProfile>(); - ResponseProfiles = Array.Empty<ResponseProfile>(); - CodecProfiles = Array.Empty<CodecProfile>(); - ContainerProfiles = Array.Empty<ContainerProfile>(); - SubtitleProfiles = Array.Empty<SubtitleProfile>(); - - XmlRootAttributes = Array.Empty<XmlAttribute>(); - - SupportedMediaTypes = "Audio,Photo,Video"; - MaxStreamingBitrate = 8000000; - MaxStaticBitrate = 8000000; - MusicStreamingTranscodingBitrate = 128000; - } - - /// <summary> - /// Gets or sets the Name. - /// </summary> - public string Name { get; set; } + public string? Name { get; set; } /// <summary> /// Gets or sets the Id. /// </summary> [XmlIgnore] - public string Id { get; set; } + public string? Id { get; set; } /// <summary> /// Gets or sets the Identification. /// </summary> - public DeviceIdentification Identification { get; set; } + public DeviceIdentification? Identification { get; set; } /// <summary> - /// Gets or sets the FriendlyName. + /// Gets or sets the friendly name of the device profile, which can be shown to users. /// </summary> - public string FriendlyName { get; set; } + public string? FriendlyName { get; set; } /// <summary> - /// Gets or sets the Manufacturer. + /// Gets or sets the manufacturer of the device which this profile represents. /// </summary> - public string Manufacturer { get; set; } + public string? Manufacturer { get; set; } /// <summary> - /// Gets or sets the ManufacturerUrl. + /// Gets or sets an url for the manufacturer of the device which this profile represents. /// </summary> - public string ManufacturerUrl { get; set; } + public string? ManufacturerUrl { get; set; } /// <summary> - /// Gets or sets the ModelName. + /// Gets or sets the model name of the device which this profile represents. /// </summary> - public string ModelName { get; set; } + public string? ModelName { get; set; } /// <summary> - /// Gets or sets the ModelDescription. + /// Gets or sets the model description of the device which this profile represents. /// </summary> - public string ModelDescription { get; set; } + public string? ModelDescription { get; set; } /// <summary> - /// Gets or sets the ModelNumber. + /// Gets or sets the model number of the device which this profile represents. /// </summary> - public string ModelNumber { get; set; } + public string? ModelNumber { get; set; } /// <summary> /// Gets or sets the ModelUrl. /// </summary> - public string ModelUrl { get; set; } + public string? ModelUrl { get; set; } /// <summary> - /// Gets or sets the SerialNumber. + /// Gets or sets the serial number of the device which this profile represents. /// </summary> - public string SerialNumber { get; set; } + public string? SerialNumber { get; set; } /// <summary> /// Gets or sets a value indicating whether EnableAlbumArtInDidl. /// </summary> + [DefaultValue(false)] public bool EnableAlbumArtInDidl { get; set; } /// <summary> /// Gets or sets a value indicating whether EnableSingleAlbumArtLimit. /// </summary> + [DefaultValue(false)] public bool EnableSingleAlbumArtLimit { get; set; } /// <summary> /// Gets or sets a value indicating whether EnableSingleSubtitleLimit. /// </summary> + [DefaultValue(false)] public bool EnableSingleSubtitleLimit { get; set; } /// <summary> /// Gets or sets the SupportedMediaTypes. /// </summary> - public string SupportedMediaTypes { get; set; } + public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video"; /// <summary> /// Gets or sets the UserId. /// </summary> - public string UserId { get; set; } + public string? UserId { get; set; } /// <summary> /// Gets or sets the AlbumArtPn. /// </summary> - public string AlbumArtPn { get; set; } + public string? AlbumArtPn { get; set; } /// <summary> /// Gets or sets the MaxAlbumArtWidth. /// </summary> - public int MaxAlbumArtWidth { get; set; } + public int? MaxAlbumArtWidth { get; set; } /// <summary> /// Gets or sets the MaxAlbumArtHeight. /// </summary> - public int MaxAlbumArtHeight { get; set; } + public int? MaxAlbumArtHeight { get; set; } /// <summary> - /// Gets or sets the MaxIconWidth. + /// Gets or sets the maximum allowed width of embedded icons. /// </summary> public int? MaxIconWidth { get; set; } /// <summary> - /// Gets or sets the MaxIconHeight. + /// Gets or sets the maximum allowed height of embedded icons. /// </summary> public int? MaxIconHeight { get; set; } /// <summary> - /// Gets or sets the MaxStreamingBitrate. + /// Gets or sets the maximum allowed bitrate for all streamed content. /// </summary> - public int? MaxStreamingBitrate { get; set; } + public int? MaxStreamingBitrate { get; set; } = 8000000; /// <summary> - /// Gets or sets the MaxStaticBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files). /// </summary> - public int? MaxStaticBitrate { get; set; } + public int? MaxStaticBitrate { get; set; } = 8000000; /// <summary> - /// Gets or sets the MusicStreamingTranscodingBitrate. + /// Gets or sets the maximum allowed bitrate for transcoded music streams. /// </summary> - public int? MusicStreamingTranscodingBitrate { get; set; } + public int? MusicStreamingTranscodingBitrate { get; set; } = 128000; /// <summary> - /// Gets or sets the MaxStaticMusicBitrate. + /// Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files. /// </summary> - public int? MaxStaticMusicBitrate { get; set; } + public int? MaxStaticMusicBitrate { get; set; } = 8000000; /// <summary> /// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace. /// </summary> - public string SonyAggregationFlags { get; set; } + public string? SonyAggregationFlags { get; set; } /// <summary> /// Gets or sets the ProtocolInfo. /// </summary> - public string ProtocolInfo { get; set; } + public string? ProtocolInfo { get; set; } /// <summary> /// Gets or sets the TimelineOffsetSeconds. /// </summary> + [DefaultValue(0)] public int TimelineOffsetSeconds { get; set; } /// <summary> /// Gets or sets a value indicating whether RequiresPlainVideoItems. /// </summary> + [DefaultValue(false)] public bool RequiresPlainVideoItems { get; set; } /// <summary> /// Gets or sets a value indicating whether RequiresPlainFolders. /// </summary> + [DefaultValue(false)] public bool RequiresPlainFolders { get; set; } /// <summary> /// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar. /// </summary> + [DefaultValue(false)] public bool EnableMSMediaReceiverRegistrar { get; set; } /// <summary> /// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests. /// </summary> + [DefaultValue(false)] public bool IgnoreTranscodeByteRangeRequests { get; set; } /// <summary> /// Gets or sets the XmlRootAttributes. /// </summary> - public XmlAttribute[] XmlRootAttributes { get; set; } + public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty<XmlAttribute>(); /// <summary> /// Gets or sets the direct play profiles. /// </summary> - public DirectPlayProfile[] DirectPlayProfiles { get; set; } + public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>(); /// <summary> /// Gets or sets the transcoding profiles. /// </summary> - public TranscodingProfile[] TranscodingProfiles { get; set; } + public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>(); /// <summary> - /// Gets or sets the ContainerProfiles. + /// Gets or sets the container profiles. /// </summary> - public ContainerProfile[] ContainerProfiles { get; set; } + public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>(); /// <summary> - /// Gets or sets the CodecProfiles. + /// Gets or sets the codec profiles. /// </summary> - public CodecProfile[] CodecProfiles { get; set; } + public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>(); /// <summary> /// Gets or sets the ResponseProfiles. /// </summary> - public ResponseProfile[] ResponseProfiles { get; set; } + public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty<ResponseProfile>(); /// <summary> - /// Gets or sets the SubtitleProfiles. + /// Gets or sets the subtitle profiles. /// </summary> - public SubtitleProfile[] SubtitleProfiles { get; set; } + public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>(); /// <summary> /// The GetSupportedMediaTypes. @@ -244,13 +237,13 @@ namespace MediaBrowser.Model.Dlna /// <param name="container">The container.</param> /// <param name="audioCodec">The audio Codec.</param> /// <returns>A <see cref="TranscodingProfile"/>.</returns> - public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec) + public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Audio) + if (i.Type != DlnaProfileType.Audio) { continue; } @@ -278,13 +271,13 @@ namespace MediaBrowser.Model.Dlna /// <param name="audioCodec">The audio Codec.</param> /// <param name="videoCodec">The video Codec.</param> /// <returns>The <see cref="TranscodingProfile"/>.</returns> - public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec) + public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec) { container = (container ?? string.Empty).TrimStart('.'); foreach (var i in TranscodingProfiles) { - if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Video) + if (i.Type != DlnaProfileType.Video) { continue; } @@ -299,7 +292,7 @@ namespace MediaBrowser.Model.Dlna continue; } - if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -320,7 +313,7 @@ namespace MediaBrowser.Model.Dlna /// <param name="audioSampleRate">The audio sample rate.</param> /// <param name="audioBitDepth">The audio bit depth.</param> /// <returns>The <see cref="ResponseProfile"/>.</returns> - public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) + public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) { foreach (var i in ResponseProfiles) { @@ -384,7 +377,7 @@ namespace MediaBrowser.Model.Dlna /// <param name="width">The width.</param> /// <param name="height">The height.</param> /// <returns>The <see cref="ResponseProfile"/>.</returns> - public ResponseProfile GetImageMediaProfile(string container, int? width, int? height) + public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height) { foreach (var i in ResponseProfiles) { @@ -442,10 +435,10 @@ namespace MediaBrowser.Model.Dlna /// <param name="videoCodecTag">The video Codec tag.</param> /// <param name="isAvc">True if Avc.</param> /// <returns>The <see cref="ResponseProfile"/>.</returns> - public ResponseProfile GetVideoMediaProfile( + public ResponseProfile? GetVideoMediaProfile( string container, - string audioCodec, - string videoCodec, + string? audioCodec, + string? videoCodec, int? width, int? height, int? bitDepth, diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 88cb839918..fa3ad098f0 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,6 +1,6 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -8,13 +8,13 @@ namespace MediaBrowser.Model.Dlna public class DirectPlayProfile { [XmlAttribute("container")] - public string Container { get; set; } + public string? Container { get; set; } [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string? AudioCodec { get; set; } [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string? VideoCodec { get; set; } [XmlAttribute("type")] public DlnaProfileType Type { get; set; } diff --git a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs index 05209e53d0..086088deae 100644 --- a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna public interface IDeviceDiscovery { event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered; + event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft; } } diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 3c955989a1..7ce248509c 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -9,16 +9,9 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { - public class MediaFormatProfileResolver + public static class MediaFormatProfileResolver { - public string[] ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) - { - return ResolveVideoFormatInternal(container, videoCodec, audioCodec, width, height, timestampType) - .Select(i => i.ToString()) - .ToArray(); - } - - private MediaFormatProfile[] ResolveVideoFormatInternal(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) + public static MediaFormatProfile[] ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { @@ -57,7 +50,6 @@ namespace MediaBrowser.Model.Dlna string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) { - return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType); } @@ -85,7 +77,7 @@ namespace MediaBrowser.Model.Dlna return Array.Empty<MediaFormatProfile>(); } - private MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) + private static MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { string suffix = string.Empty; @@ -210,12 +202,12 @@ namespace MediaBrowser.Model.Dlna return Array.Empty<MediaFormatProfile>(); } - private MediaFormatProfile ValueOf(string value) + private static MediaFormatProfile ValueOf(string value) { return (MediaFormatProfile)Enum.Parse(typeof(MediaFormatProfile), value, true); } - private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height) + private static MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height) { if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { @@ -288,7 +280,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private MediaFormatProfile? ResolveVideo3GPFormat(string videoCodec, string audioCodec) + private static MediaFormatProfile? ResolveVideo3GPFormat(string videoCodec, string audioCodec) { if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { @@ -318,12 +310,11 @@ namespace MediaBrowser.Model.Dlna return null; } - private MediaFormatProfile? ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height) + private static MediaFormatProfile? ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height) { if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) && (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase))) { - if (width.HasValue && height.HasValue) { if ((width.Value <= 720) && (height.Value <= 576)) @@ -373,7 +364,7 @@ namespace MediaBrowser.Model.Dlna return null; } - public MediaFormatProfile? ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels) + public static MediaFormatProfile? ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels) { if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { @@ -415,7 +406,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private MediaFormatProfile ResolveAudioASFFormat(int? bitrate) + private static MediaFormatProfile ResolveAudioASFFormat(int? bitrate) { if (bitrate.HasValue && bitrate.Value <= 193) { @@ -425,7 +416,7 @@ namespace MediaBrowser.Model.Dlna return MediaFormatProfile.WMA_FULL; } - private MediaFormatProfile? ResolveAudioLPCMFormat(int? frequency, int? channels) + private static MediaFormatProfile? ResolveAudioLPCMFormat(int? frequency, int? channels) { if (frequency.HasValue && channels.HasValue) { @@ -455,7 +446,7 @@ namespace MediaBrowser.Model.Dlna return MediaFormatProfile.LPCM16_48_STEREO; } - private MediaFormatProfile ResolveAudioMP4Format(int? bitrate) + private static MediaFormatProfile ResolveAudioMP4Format(int? bitrate) { if (bitrate.HasValue && bitrate.Value <= 320) { @@ -465,7 +456,7 @@ namespace MediaBrowser.Model.Dlna return MediaFormatProfile.AAC_ISO; } - private MediaFormatProfile ResolveAudioADTSFormat(int? bitrate) + private static MediaFormatProfile ResolveAudioADTSFormat(int? bitrate) { if (bitrate.HasValue && bitrate.Value <= 320) { @@ -475,11 +466,13 @@ namespace MediaBrowser.Model.Dlna return MediaFormatProfile.AAC_ADTS; } - public MediaFormatProfile? ResolveImageFormat(string container, int? width, int? height) + public static MediaFormatProfile? ResolveImageFormat(string container, int? width, int? height) { if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase)) + { return ResolveImageJPGFormat(width, height); + } if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase)) { @@ -499,7 +492,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private MediaFormatProfile ResolveImageJPGFormat(int? width, int? height) + private static MediaFormatProfile ResolveImageJPGFormat(int? width, int? height) { if (width.HasValue && height.HasValue) { @@ -524,7 +517,7 @@ namespace MediaBrowser.Model.Dlna return MediaFormatProfile.JPEG_SM; } - private MediaFormatProfile ResolveImagePNGFormat(int? width, int? height) + private static MediaFormatProfile ResolveImagePNGFormat(int? width, int? height) { if (width.HasValue && height.HasValue) { diff --git a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs index 30c44fbe0e..f8f76c69d8 100644 --- a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs +++ b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs @@ -4,14 +4,14 @@ namespace MediaBrowser.Model.Dlna { public class ResolutionConfiguration { - public int MaxWidth { get; set; } - - public int MaxBitrate { get; set; } - public ResolutionConfiguration(int maxWidth, int maxBitrate) { MaxWidth = maxWidth; MaxBitrate = maxBitrate; } + + public int MaxWidth { get; set; } + + public int MaxBitrate { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs index 48f53f06c2..bf9661f7f3 100644 --- a/MediaBrowser.Model/Dlna/ResponseProfile.cs +++ b/MediaBrowser.Model/Dlna/ResponseProfile.cs @@ -8,6 +8,11 @@ namespace MediaBrowser.Model.Dlna { public class ResponseProfile { + public ResponseProfile() + { + Conditions = Array.Empty<ProfileCondition>(); + } + [XmlAttribute("container")] public string Container { get; set; } @@ -28,11 +33,6 @@ namespace MediaBrowser.Model.Dlna public ProfileCondition[] Conditions { get; set; } - public ResponseProfile() - { - Conditions = Array.Empty<ProfileCondition>(); - } - public string[] GetContainers() { return ContainerProfile.SplitValue(Container); diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs index 94f5bd3dbe..b1fc48c087 100644 --- a/MediaBrowser.Model/Dlna/SearchCriteria.cs +++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs @@ -7,31 +7,6 @@ namespace MediaBrowser.Model.Dlna { public class SearchCriteria { - public SearchType SearchType { get; set; } - - /// <summary> - /// Splits the specified string. - /// </summary> - /// <param name="str">The string.</param> - /// <param name="term">The term.</param> - /// <param name="limit">The limit.</param> - /// <returns>System.String[].</returns> - private static string[] RegexSplit(string str, string term, int limit) - { - return new Regex(term).Split(str, limit); - } - - /// <summary> - /// Splits the specified string. - /// </summary> - /// <param name="str">The string.</param> - /// <param name="term">The term.</param> - /// <returns>System.String[].</returns> - private static string[] RegexSplit(string str, string term) - { - return Regex.Split(str, term, RegexOptions.IgnoreCase); - } - public SearchCriteria(string search) { if (search.Length == 0) @@ -48,8 +23,8 @@ namespace MediaBrowser.Model.Dlna if (subFactors.Length == 3) { - if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) && - (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) + if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) + && (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) { if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase)) { @@ -71,5 +46,30 @@ namespace MediaBrowser.Model.Dlna } } } + + public SearchType SearchType { get; set; } + + /// <summary> + /// Splits the specified string. + /// </summary> + /// <param name="str">The string.</param> + /// <param name="term">The term.</param> + /// <param name="limit">The limit.</param> + /// <returns>System.String[].</returns> + private static string[] RegexSplit(string str, string term, int limit) + { + return new Regex(term).Split(str, limit); + } + + /// <summary> + /// Splits the specified string. + /// </summary> + /// <param name="str">The string.</param> + /// <param name="term">The term.</param> + /// <returns>System.String[].</returns> + private static string[] RegexSplit(string str, string term) + { + return Regex.Split(str, term, RegexOptions.IgnoreCase); + } } } diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs index 53e4540cbb..7769d0bd3e 100644 --- a/MediaBrowser.Model/Dlna/SortCriteria.cs +++ b/MediaBrowser.Model/Dlna/SortCriteria.cs @@ -6,10 +6,10 @@ namespace MediaBrowser.Model.Dlna { public class SortCriteria { - public SortOrder SortOrder => SortOrder.Ascending; - public SortCriteria(string value) { } + + public SortOrder SortOrder => SortOrder.Ascending; } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 431cf0bafa..f4c69fe8f5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -227,7 +227,7 @@ namespace MediaBrowser.Model.Dlna } } - public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string _, DeviceProfile profile, DlnaProfileType type) + public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type) { if (string.IsNullOrEmpty(inputContainer)) { @@ -274,14 +274,14 @@ namespace MediaBrowser.Model.Dlna if (options.ForceDirectPlay) { playlistItem.PlayMethod = PlayMethod.DirectPlay; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); return playlistItem; } if (options.ForceDirectStream) { playlistItem.PlayMethod = PlayMethod.DirectStream; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); return playlistItem; } @@ -297,7 +297,7 @@ namespace MediaBrowser.Model.Dlna int? inputAudioSampleRate = audioStream?.SampleRate; int? inputAudioBitDepth = audioStream?.BitDepth; - if (directPlayMethods.Count() > 0) + if (directPlayMethods.Any()) { string audioCodec = audioStream?.Codec; @@ -349,7 +349,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.PlayMethod = PlayMethod.DirectStream; } - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio); return playlistItem; } @@ -514,6 +514,8 @@ namespace MediaBrowser.Model.Dlna private static List<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; @@ -521,7 +523,7 @@ namespace MediaBrowser.Model.Dlna foreach (var profile in directPlayProfiles) { // Check container type - if (profile.SupportsContainer(item.Container)) + if (profile.Type == mediaType && profile.SupportsContainer(item.Container)) { containerSupported = true; @@ -674,7 +676,7 @@ namespace MediaBrowser.Model.Dlna var videoStream = item.VideoStream; - // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough + // 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, options, PlayMethod.DirectPlay); var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, options, PlayMethod.DirectStream); bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1); @@ -698,7 +700,7 @@ namespace MediaBrowser.Model.Dlna if (directPlay != null) { playlistItem.PlayMethod = directPlay.Value; - playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Video); + playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video); if (subtitleStream != null) { @@ -1017,14 +1019,15 @@ namespace MediaBrowser.Model.Dlna } DeviceProfile profile = options.Profile; + string container = mediaSource.Container; // See if it can be direct played DirectPlayProfile directPlay = null; - foreach (var i in profile.DirectPlayProfiles) + foreach (var p in profile.DirectPlayProfiles) { - if (i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream)) + if (p.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(p, container, videoStream, audioStream)) { - directPlay = i; + directPlay = p; break; } } @@ -1032,23 +1035,23 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { _logger.LogInformation( - "Profile: {0}, No video direct play profiles found for {1} with codec {2}", - profile?.Name ?? "Unknown Profile", - mediaSource?.Path ?? "Unknown path", - videoStream?.Codec ?? "Unknown codec"); + "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)); } - string container = mediaSource.Container; - var conditions = new List<ProfileCondition>(); - foreach (var i in profile.ContainerProfiles) + foreach (var p in profile.ContainerProfiles) { - if (i.Type == DlnaProfileType.Video - && i.ContainsContainer(container)) + if (p.Type == DlnaProfileType.Video + && p.ContainsContainer(container)) { - foreach (var c in i.Conditions) + foreach (var c in p.Conditions) { conditions.Add(c); } @@ -1404,7 +1407,9 @@ namespace MediaBrowser.Model.Dlna { _logger.LogInformation( "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", - playMethod, itemBitrate, requestedMaxBitrate); + playMethod, + itemBitrate, + requestedMaxBitrate); return false; } @@ -1733,7 +1738,7 @@ namespace MediaBrowser.Model.Dlna if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny) { - item.SetOption(qualifier, "profile", string.Join(",", values)); + item.SetOption(qualifier, "profile", string.Join(',', values)); } } @@ -1894,10 +1899,10 @@ namespace MediaBrowser.Model.Dlna return true; } - private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream) + private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, string container, MediaStream videoStream, MediaStream audioStream) { // Check container type - if (!profile.SupportsContainer(item.Container)) + if (!profile.SupportsContainer(container)) { return false; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 55b12ae810..252872847a 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -27,45 +27,6 @@ namespace MediaBrowser.Model.Dlna StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } - public void SetOption(string qualifier, string name, string value) - { - if (string.IsNullOrEmpty(qualifier)) - { - SetOption(name, value); - } - else - { - SetOption(qualifier + "-" + name, value); - } - } - - public void SetOption(string name, string value) - { - StreamOptions[name] = value; - } - - public string GetOption(string qualifier, string name) - { - var value = GetOption(qualifier + "-" + name); - - if (string.IsNullOrEmpty(value)) - { - value = GetOption(name); - } - - return value; - } - - public string GetOption(string name) - { - if (StreamOptions.TryGetValue(name, out var value)) - { - return value; - } - - return null; - } - public Guid ItemId { get; set; } public PlayMethod PlayMethod { get; set; } @@ -152,374 +113,18 @@ namespace MediaBrowser.Model.Dlna PlayMethod == PlayMethod.DirectStream || PlayMethod == PlayMethod.DirectPlay; - public string ToUrl(string baseUrl, string accessToken) - { - if (PlayMethod == PlayMethod.DirectPlay) - { - return MediaSource.Path; - } - - if (string.IsNullOrEmpty(baseUrl)) - { - throw new ArgumentNullException(nameof(baseUrl)); - } - - var list = new List<string>(); - foreach (NameValuePair pair in BuildParams(this, accessToken)) - { - if (string.IsNullOrEmpty(pair.Value)) - { - continue; - } - - // Try to keep the url clean by omitting defaults - if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - // Be careful, IsDirectStream==true by default (Static != false or not in query). - // See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true. - if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var encodedValue = pair.Value.Replace(" ", "%20"); - - list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); - } - - string queryString = string.Join("&", list.ToArray()); - - return GetUrl(baseUrl, queryString); - } - - private string GetUrl(string baseUrl, string queryString) - { - if (string.IsNullOrEmpty(baseUrl)) - { - throw new ArgumentNullException(nameof(baseUrl)); - } - - string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; - - baseUrl = baseUrl.TrimEnd('/'); - - if (MediaType == DlnaProfileType.Audio) - { - if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) - { - return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); - } - - return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); - } - - if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) - { - return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); - } - - return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); - } - - private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken) - { - var list = new List<NameValuePair>(); - - string audioCodecs = item.AudioCodecs.Length == 0 ? - string.Empty : - string.Join(",", item.AudioCodecs); - - string videoCodecs = item.VideoCodecs.Length == 0 ? - string.Empty : - string.Join(",", item.VideoCodecs); - - list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); - list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); - list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); - list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - list.Add(new NameValuePair("VideoCodec", videoCodecs)); - list.Add(new NameValuePair("AudioCodec", audioCodecs)); - list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - - list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - - long startPositionTicks = item.StartPositionTicks; - - var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase); - - if (isHls) - { - list.Add(new NameValuePair("StartTimeTicks", string.Empty)); - } - else - { - list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture))); - } - - list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); - list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); - - string liveStreamId = item.MediaSource?.LiveStreamId; - list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); - - list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); - - if (!item.IsDirectStream) - { - if (item.RequireNonAnamorphic) - { - list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); - - if (item.EnableSubtitlesInManifest) - { - list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - if (item.EnableMpegtsM2TsMode) - { - list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - if (item.EstimateContentLength) - { - list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto) - { - list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant())); - } - - if (item.CopyTimestamps) - { - list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); - } - - list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty)); - - string subtitleCodecs = item.SubtitleCodecs.Length == 0 ? - string.Empty : - string.Join(",", item.SubtitleCodecs); - - list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); - - if (isHls) - { - list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); - - if (item.SegmentLength.HasValue) - { - list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture))); - } - - if (item.MinSegments.HasValue) - { - list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); - } - - list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); - } - - foreach (var pair in item.StreamOptions) - { - if (string.IsNullOrEmpty(pair.Value)) - { - continue; - } - - // strip spaces to avoid having to encode h264 profile names - list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", ""))); - } - - if (!item.IsDirectStream) - { - list.Add(new NameValuePair("TranscodeReasons", string.Join(",", item.TranscodeReasons.Distinct().Select(i => i.ToString())))); - } - - return list; - } - - public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) - { - return GetExternalSubtitles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); - } - - public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) - { - var list = GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); - var newList = new List<SubtitleStreamInfo>(); - - // First add the selected track - foreach (SubtitleStreamInfo stream in list) - { - if (stream.DeliveryMethod == SubtitleDeliveryMethod.External) - { - newList.Add(stream); - } - } - - return newList; - } - - public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) - { - return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); - } - - public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) - { - var list = new List<SubtitleStreamInfo>(); - - // HLS will preserve timestamps so we can just grab the full subtitle stream - long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase) - ? 0 - : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0); - - // First add the selected track - if (SubtitleStreamIndex.HasValue) - { - foreach (var stream in MediaSource.MediaStreams) - { - if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) - { - AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks); - } - } - } - - if (!includeSelectedTrackOnly) - { - foreach (var stream in MediaSource.MediaStreams) - { - if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value)) - { - AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks); - } - } - } - - return list; - } - - private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks) - { - if (enableAllProfiles) - { - foreach (var profile in DeviceProfile.SubtitleProfiles) - { - var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport); - - list.Add(info); - } - } - else - { - var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport); - - list.Add(info); - } - } - - private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport) - { - var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol); - var info = new SubtitleStreamInfo - { - IsForced = stream.IsForced, - Language = stream.Language, - Name = stream.Language ?? "Unknown", - Format = subtitleProfile.Format, - Index = stream.Index, - DeliveryMethod = subtitleProfile.Method, - DisplayTitle = stream.DisplayTitle - }; - - if (info.DeliveryMethod == SubtitleDeliveryMethod.External) - { - if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal) - { - info.Url = string.Format(CultureInfo.InvariantCulture, "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", - baseUrl, - ItemId, - MediaSourceId, - stream.Index.ToString(CultureInfo.InvariantCulture), - startPositionTicks.ToString(CultureInfo.InvariantCulture), - subtitleProfile.Format); - - if (!string.IsNullOrEmpty(accessToken)) - { - info.Url += "?api_key=" + accessToken; - } - - info.IsExternalUrl = false; - } - else - { - info.Url = stream.Path; - info.IsExternalUrl = true; - } - } - - return info; - } - /// <summary> - /// Returns the audio stream that will be used. + /// Gets the audio stream that will be used. /// </summary> - public MediaStream TargetAudioStream - { - get - { - if (MediaSource != null) - { - return MediaSource.GetDefaultAudioStream(AudioStreamIndex); - } - - return null; - } - } + public MediaStream TargetAudioStream => MediaSource?.GetDefaultAudioStream(AudioStreamIndex); /// <summary> - /// Returns the video stream that will be used. + /// Gets the video stream that will be used. /// </summary> - public MediaStream TargetVideoStream - { - get - { - if (MediaSource != null) - { - return MediaSource.VideoStream; - } - - return null; - } - } + public MediaStream TargetVideoStream => MediaSource?.VideoStream; /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public int? TargetAudioSampleRate { @@ -533,7 +138,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public int? TargetAudioBitDepth { @@ -556,7 +161,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public int? TargetVideoBitDepth { @@ -603,7 +208,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public float? TargetFramerate { @@ -617,7 +222,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public double? TargetVideoLevel { @@ -639,72 +244,8 @@ namespace MediaBrowser.Model.Dlna } } - public int? GetTargetVideoBitDepth(string codec) - { - var value = GetOption(codec, "videobitdepth"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return null; - } - - public int? GetTargetAudioBitDepth(string codec) - { - var value = GetOption(codec, "audiobitdepth"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return null; - } - - public double? GetTargetVideoLevel(string codec) - { - var value = GetOption(codec, "level"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return null; - } - - public int? GetTargetRefFrames(string codec) - { - var value = GetOption(codec, "maxrefframes"); - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) - { - return result; - } - - return null; - } - /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public int? TargetPacketLength { @@ -718,7 +259,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio sample rate that will be in the output stream. + /// Gets the audio sample rate that will be in the output stream. /// </summary> public string TargetVideoProfile { @@ -756,7 +297,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio bitrate that will be in the output stream. + /// Gets the audio bitrate that will be in the output stream. /// </summary> public int? TargetAudioBitrate { @@ -770,7 +311,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio channels that will be in the output stream. + /// Gets the audio channels that will be in the output stream. /// </summary> public int? TargetAudioChannels { @@ -792,26 +333,8 @@ namespace MediaBrowser.Model.Dlna } } - public int? GetTargetAudioChannels(string codec) - { - var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels; - - var value = GetOption(codec, "audiochannels"); - if (string.IsNullOrEmpty(value)) - { - return defaultValue; - } - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) - { - return Math.Min(result, defaultValue ?? result); - } - - return defaultValue; - } - /// <summary> - /// Predicts the audio codec that will be in the output stream. + /// Gets the audio codec that will be in the output stream. /// </summary> public string[] TargetAudioCodec { @@ -864,7 +387,7 @@ namespace MediaBrowser.Model.Dlna } /// <summary> - /// Predicts the audio channels that will be in the output stream. + /// Gets the audio channels that will be in the output stream. /// </summary> public long? TargetSize { @@ -1035,6 +558,461 @@ namespace MediaBrowser.Model.Dlna } } + public void SetOption(string qualifier, string name, string value) + { + if (string.IsNullOrEmpty(qualifier)) + { + SetOption(name, value); + } + else + { + SetOption(qualifier + "-" + name, value); + } + } + + public void SetOption(string name, string value) + { + StreamOptions[name] = value; + } + + public string GetOption(string qualifier, string name) + { + var value = GetOption(qualifier + "-" + name); + + if (string.IsNullOrEmpty(value)) + { + value = GetOption(name); + } + + return value; + } + + public string GetOption(string name) + { + if (StreamOptions.TryGetValue(name, out var value)) + { + return value; + } + + return null; + } + + public string ToUrl(string baseUrl, string accessToken) + { + if (PlayMethod == PlayMethod.DirectPlay) + { + return MediaSource.Path; + } + + if (string.IsNullOrEmpty(baseUrl)) + { + throw new ArgumentNullException(nameof(baseUrl)); + } + + var list = new List<string>(); + foreach (NameValuePair pair in BuildParams(this, accessToken)) + { + if (string.IsNullOrEmpty(pair.Value)) + { + continue; + } + + // Try to keep the url clean by omitting defaults + if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) && + string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) && + string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && + string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var encodedValue = pair.Value.Replace(" ", "%20"); + + list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); + } + + string queryString = string.Join("&", list.ToArray()); + + return GetUrl(baseUrl, queryString); + } + + private string GetUrl(string baseUrl, string queryString) + { + if (string.IsNullOrEmpty(baseUrl)) + { + throw new ArgumentNullException(nameof(baseUrl)); + } + + string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; + + baseUrl = baseUrl.TrimEnd('/'); + + if (MediaType == DlnaProfileType.Audio) + { + if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); + } + + return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); + } + + if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString); + } + + return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString); + } + + private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken) + { + var list = new List<NameValuePair>(); + + string audioCodecs = item.AudioCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.AudioCodecs); + + string videoCodecs = item.VideoCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.VideoCodecs); + + list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); + list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); + list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); + list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + list.Add(new NameValuePair("VideoCodec", videoCodecs)); + list.Add(new NameValuePair("AudioCodec", audioCodecs)); + list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("AudioBitrate", item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("AudioSampleRate", item.AudioSampleRate.HasValue ? item.AudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + list.Add(new NameValuePair("MaxFramerate", item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + long startPositionTicks = item.StartPositionTicks; + + var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase); + + if (isHls) + { + list.Add(new NameValuePair("StartTimeTicks", string.Empty)); + } + else + { + list.Add(new NameValuePair("StartTimeTicks", startPositionTicks.ToString(CultureInfo.InvariantCulture))); + } + + list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); + list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); + + string liveStreamId = item.MediaSource?.LiveStreamId; + list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); + + list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); + + if (!item.IsDirectStream) + { + if (item.RequireNonAnamorphic) + { + list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); + + if (item.EnableSubtitlesInManifest) + { + list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.EnableMpegtsM2TsMode) + { + list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.EstimateContentLength) + { + list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto) + { + list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant())); + } + + if (item.CopyTimestamps) + { + list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + } + + list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty)); + + string subtitleCodecs = item.SubtitleCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.SubtitleCodecs); + + list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); + + if (isHls) + { + list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty)); + + if (item.SegmentLength.HasValue) + { + list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture))); + } + + if (item.MinSegments.HasValue) + { + list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); + } + + list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); + } + + foreach (var pair in item.StreamOptions) + { + if (string.IsNullOrEmpty(pair.Value)) + { + continue; + } + + // strip spaces to avoid having to encode h264 profile names + list.Add(new NameValuePair(pair.Key, pair.Value.Replace(" ", string.Empty))); + } + + if (!item.IsDirectStream) + { + list.Add(new NameValuePair("TranscodeReasons", string.Join(',', item.TranscodeReasons.Distinct()))); + } + + return list; + } + + public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetExternalSubtitles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); + } + + public List<SubtitleStreamInfo> GetExternalSubtitles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) + { + var list = GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, enableAllProfiles, baseUrl, accessToken); + var newList = new List<SubtitleStreamInfo>(); + + // First add the selected track + foreach (SubtitleStreamInfo stream in list) + { + if (stream.DeliveryMethod == SubtitleDeliveryMethod.External) + { + newList.Add(stream); + } + } + + return newList; + } + + public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, string baseUrl, string accessToken) + { + return GetSubtitleProfiles(transcoderSupport, includeSelectedTrackOnly, false, baseUrl, accessToken); + } + + public List<SubtitleStreamInfo> GetSubtitleProfiles(ITranscoderSupport transcoderSupport, bool includeSelectedTrackOnly, bool enableAllProfiles, string baseUrl, string accessToken) + { + var list = new List<SubtitleStreamInfo>(); + + // HLS will preserve timestamps so we can just grab the full subtitle stream + long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase) + ? 0 + : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0); + + // First add the selected track + if (SubtitleStreamIndex.HasValue) + { + foreach (var stream in MediaSource.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && stream.Index == SubtitleStreamIndex.Value) + { + AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks); + } + } + } + + if (!includeSelectedTrackOnly) + { + foreach (var stream in MediaSource.MediaStreams) + { + if (stream.Type == MediaStreamType.Subtitle && (!SubtitleStreamIndex.HasValue || stream.Index != SubtitleStreamIndex.Value)) + { + AddSubtitleProfiles(list, stream, transcoderSupport, enableAllProfiles, baseUrl, accessToken, startPositionTicks); + } + } + } + + return list; + } + + private void AddSubtitleProfiles(List<SubtitleStreamInfo> list, MediaStream stream, ITranscoderSupport transcoderSupport, bool enableAllProfiles, string baseUrl, string accessToken, long startPositionTicks) + { + if (enableAllProfiles) + { + foreach (var profile in DeviceProfile.SubtitleProfiles) + { + var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, new[] { profile }, transcoderSupport); + + list.Add(info); + } + } + else + { + var info = GetSubtitleStreamInfo(stream, baseUrl, accessToken, startPositionTicks, DeviceProfile.SubtitleProfiles, transcoderSupport); + + list.Add(info); + } + } + + private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles, ITranscoderSupport transcoderSupport) + { + var subtitleProfile = StreamBuilder.GetSubtitleProfile(MediaSource, stream, subtitleProfiles, PlayMethod, transcoderSupport, Container, SubProtocol); + var info = new SubtitleStreamInfo + { + IsForced = stream.IsForced, + Language = stream.Language, + Name = stream.Language ?? "Unknown", + Format = subtitleProfile.Format, + Index = stream.Index, + DeliveryMethod = subtitleProfile.Method, + DisplayTitle = stream.DisplayTitle + }; + + if (info.DeliveryMethod == SubtitleDeliveryMethod.External) + { + if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal) + { + info.Url = string.Format( + CultureInfo.InvariantCulture, + "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}", + baseUrl, + ItemId, + MediaSourceId, + stream.Index.ToString(CultureInfo.InvariantCulture), + startPositionTicks.ToString(CultureInfo.InvariantCulture), + subtitleProfile.Format); + + if (!string.IsNullOrEmpty(accessToken)) + { + info.Url += "?api_key=" + accessToken; + } + + info.IsExternalUrl = false; + } + else + { + info.Url = stream.Path; + info.IsExternalUrl = true; + } + } + + return info; + } + + public int? GetTargetVideoBitDepth(string codec) + { + var value = GetOption(codec, "videobitdepth"); + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return null; + } + + public int? GetTargetAudioBitDepth(string codec) + { + var value = GetOption(codec, "audiobitdepth"); + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return null; + } + + public double? GetTargetVideoLevel(string codec) + { + var value = GetOption(codec, "level"); + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return null; + } + + public int? GetTargetRefFrames(string codec) + { + var value = GetOption(codec, "maxrefframes"); + if (string.IsNullOrEmpty(value)) + { + return null; + } + + if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + { + return result; + } + + return null; + } + + public int? GetTargetAudioChannels(string codec) + { + var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels; + + var value = GetOption(codec, "audiochannels"); + if (string.IsNullOrEmpty(value)) + { + return defaultValue; + } + + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + return Math.Min(result, defaultValue ?? result); + } + + return defaultValue; + } + private int? GetMediaStreamCount(MediaStreamType type, int limit) { var count = MediaSource.GetStreamCount(type); diff --git a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs index e7fe8d6af2..9b39f9e11a 100644 --- a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs +++ b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs @@ -2,25 +2,28 @@ namespace MediaBrowser.Model.Dlna { + /// <summary> + /// Delivery method to use during playback of a specific subtitle format. + /// </summary> public enum SubtitleDeliveryMethod { /// <summary> - /// The encode. + /// Burn the subtitles in the video track. /// </summary> Encode = 0, /// <summary> - /// The embed. + /// Embed the subtitles in the file or stream. /// </summary> Embed = 1, /// <summary> - /// The external. + /// Serve the subtitles as an external file. /// </summary> External = 2, /// <summary> - /// The HLS. + /// Serve the subtitles as a separate HLS stream. /// </summary> Hls = 3 } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index f05e31047c..214578a85e 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,6 +1,7 @@ -#nullable disable #pragma warning disable CS1591 +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -8,47 +9,56 @@ namespace MediaBrowser.Model.Dlna public class TranscodingProfile { [XmlAttribute("container")] - public string Container { get; set; } + public string Container { get; set; } = string.Empty; [XmlAttribute("type")] public DlnaProfileType Type { get; set; } [XmlAttribute("videoCodec")] - public string VideoCodec { get; set; } + public string VideoCodec { get; set; } = string.Empty; [XmlAttribute("audioCodec")] - public string AudioCodec { get; set; } + public string AudioCodec { get; set; } = string.Empty; [XmlAttribute("protocol")] - public string Protocol { get; set; } + public string Protocol { get; set; } = string.Empty; + [DefaultValue(false)] [XmlAttribute("estimateContentLength")] public bool EstimateContentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("enableMpegtsM2TsMode")] public bool EnableMpegtsM2TsMode { get; set; } + [DefaultValue(TranscodeSeekInfo.Auto)] [XmlAttribute("transcodeSeekInfo")] public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + [DefaultValue(false)] [XmlAttribute("copyTimestamps")] public bool CopyTimestamps { get; set; } + [DefaultValue(EncodingContext.Streaming)] [XmlAttribute("context")] public EncodingContext Context { get; set; } + [DefaultValue(false)] [XmlAttribute("enableSubtitlesInManifest")] public bool EnableSubtitlesInManifest { get; set; } [XmlAttribute("maxAudioChannels")] - public string MaxAudioChannels { get; set; } + public string? MaxAudioChannels { get; set; } + [DefaultValue(0)] [XmlAttribute("minSegments")] public int MinSegments { get; set; } + [DefaultValue(0)] [XmlAttribute("segmentLength")] public int SegmentLength { get; set; } + [DefaultValue(false)] [XmlAttribute("breakOnNonKeyFrames")] public bool BreakOnNonKeyFrames { get; set; } diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index d71013f019..987a3a908f 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -16,5 +16,7 @@ namespace MediaBrowser.Model.Dlna public IPAddress LocalIpAddress { get; set; } public int LocalPort { get; set; } + + public IPAddress RemoteIpAddress { get; set; } } } |
