diff options
| author | Dominik <git@secnd.me> | 2023-06-15 19:38:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-15 19:38:42 +0200 |
| commit | 17f1e8d19b1fd693893d66d2275ed8ae2476344e (patch) | |
| tree | 7f48be975faa92042769870957587b3c7864f631 /Jellyfin.Api/Helpers/StreamingHelpers.cs | |
| parent | e8ae7e5c38e28f13fa8de295e26c930cb46d9b79 (diff) | |
| parent | 6771b5cabe96b4b3cbd1cd0c998d564f3dd17ed4 (diff) | |
Merge branch 'master' into segment-deletion
Diffstat (limited to 'Jellyfin.Api/Helpers/StreamingHelpers.cs')
| -rw-r--r-- | Jellyfin.Api/Helpers/StreamingHelpers.cs | 1239 |
1 files changed, 622 insertions, 617 deletions
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 370573773..9c91dcc6f 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -22,761 +22,766 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -namespace Jellyfin.Api.Helpers +namespace Jellyfin.Api.Helpers; + +/// <summary> +/// The streaming helpers. +/// </summary> +public static class StreamingHelpers { /// <summary> - /// The streaming helpers. + /// Gets the current streaming state. /// </summary> - public static class StreamingHelpers + /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param> + /// <param name="httpContext">The <see cref="HttpContext"/>.</param> + /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> + /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> + /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> + /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> + /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> + /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> + /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param> + /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> + /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns> + public static async Task<StreamState> GetStreamingState( + StreamingRequestDto streamingRequest, + HttpContext httpContext, + IMediaSourceManager mediaSourceManager, + IUserManager userManager, + ILibraryManager libraryManager, + IServerConfigurationManager serverConfigurationManager, + IMediaEncoder mediaEncoder, + EncodingHelper encodingHelper, + IDlnaManager dlnaManager, + IDeviceManager deviceManager, + TranscodingJobHelper transcodingJobHelper, + TranscodingJobType transcodingJobType, + CancellationToken cancellationToken) { - /// <summary> - /// Gets the current streaming state. - /// </summary> - /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param> - /// <param name="httpContext">The <see cref="HttpContext"/>.</param> - /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> - /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> - /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> - /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> - /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> - /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> - /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> - /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param> - /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> - /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param> - /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns> - public static async Task<StreamState> GetStreamingState( - StreamingRequestDto streamingRequest, - HttpContext httpContext, - IMediaSourceManager mediaSourceManager, - IUserManager userManager, - ILibraryManager libraryManager, - IServerConfigurationManager serverConfigurationManager, - IMediaEncoder mediaEncoder, - EncodingHelper encodingHelper, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - TranscodingJobHelper transcodingJobHelper, - TranscodingJobType transcodingJobType, - CancellationToken cancellationToken) - { - var httpRequest = httpContext.Request; - // Parse the DLNA time seek header - if (!streamingRequest.StartTimeTicks.HasValue) - { - var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"]; + var httpRequest = httpContext.Request; + // Parse the DLNA time seek header + if (!streamingRequest.StartTimeTicks.HasValue) + { + var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"]; - streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString()); - } + streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString()); + } - if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) - { - ParseParams(streamingRequest); - } + if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) + { + ParseParams(streamingRequest); + } - streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query); - if (httpRequest.Path.Value == null) - { - throw new ResourceNotFoundException(nameof(httpRequest.Path)); - } + streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query); + if (httpRequest.Path.Value is null) + { + throw new ResourceNotFoundException(nameof(httpRequest.Path)); + } - var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString(); + var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString(); - if (string.IsNullOrEmpty(streamingRequest.AudioCodec)) - { - streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); - } + if (string.IsNullOrEmpty(streamingRequest.AudioCodec)) + { + streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); + } - var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || - streamingRequest.StreamOptions.ContainsKey("dlnaheaders") || - string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); + var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || + streamingRequest.StreamOptions.ContainsKey("dlnaheaders") || + string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); - var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) - { - Request = streamingRequest, - RequestedUrl = url, - UserAgent = httpRequest.Headers[HeaderNames.UserAgent], - EnableDlnaHeaders = enableDlnaHeaders - }; - - var userId = httpContext.User.GetUserId(); - if (!userId.Equals(default)) - { - state.User = userManager.GetUserById(userId); - } + var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) + { + Request = streamingRequest, + RequestedUrl = url, + UserAgent = httpRequest.Headers[HeaderNames.UserAgent], + EnableDlnaHeaders = enableDlnaHeaders + }; + + var userId = httpContext.User.GetUserId(); + if (!userId.Equals(default)) + { + state.User = userManager.GetUserById(userId); + } - if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec)) - { - state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } + if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec)) + { + state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); + } - if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) - { - state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec) - ?? state.SupportedAudioCodecs.FirstOrDefault(); - } + if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) + { + state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec) + ?? state.SupportedAudioCodecs.FirstOrDefault(); + } - if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); - state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } + if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) + { + state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec) + ?? state.SupportedSubtitleCodecs.FirstOrDefault(); + } - var item = libraryManager.GetItemById(streamingRequest.Id); + var item = libraryManager.GetItemById(streamingRequest.Id); - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); + state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - MediaSourceInfo? mediaSource = null; - if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) - { - var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) - ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId) - : null; + MediaSourceInfo? mediaSource = null; + if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) + { + var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) + ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId) + : null; - if (currentJob != null) - { - mediaSource = currentJob.MediaSource; - } + if (currentJob is not null) + { + mediaSource = currentJob.MediaSource; + } - if (mediaSource == null) - { - var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false); + if (mediaSource is null) + { + var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false); - mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) - ? mediaSources[0] - : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); + mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) + ? mediaSources[0] + : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal)); - if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id)) - { - mediaSource = mediaSources[0]; - } + if (mediaSource is null && Guid.Parse(streamingRequest.MediaSourceId).Equals(streamingRequest.Id)) + { + mediaSource = mediaSources[0]; } } - else - { - var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); - mediaSource = liveStreamInfo.Item1; - state.DirectStreamProvider = liveStreamInfo.Item2; - } + } + else + { + var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); + mediaSource = liveStreamInfo.Item1; + state.DirectStreamProvider = liveStreamInfo.Item2; + } - var encodingOptions = serverConfigurationManager.GetEncodingOptions(); + var encodingOptions = serverConfigurationManager.GetEncodingOptions(); - encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url); + encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url); - string? containerInternal = Path.GetExtension(state.RequestedUrl); + string? containerInternal = Path.GetExtension(state.RequestedUrl); - if (!string.IsNullOrEmpty(streamingRequest.Container)) - { - containerInternal = streamingRequest.Container; - } + if (!string.IsNullOrEmpty(streamingRequest.Container)) + { + containerInternal = streamingRequest.Container; + } - if (string.IsNullOrEmpty(containerInternal)) - { - containerInternal = streamingRequest.Static ? - StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio) - : GetOutputFileExtension(state, mediaSource); - } + if (string.IsNullOrEmpty(containerInternal)) + { + containerInternal = streamingRequest.Static ? + StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio) + : GetOutputFileExtension(state, mediaSource); + } - state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); + var outputAudioCodec = streamingRequest.AudioCodec; + if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec)) + { + state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0; + } - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream); + state.OutputAudioCodec = outputAudioCodec; + state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); + state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); - state.OutputAudioCodec = streamingRequest.AudioCodec; + if (state.VideoRequest is not null) + { + state.OutputVideoCodec = state.Request.VideoCodec; + state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); + encodingHelper.TryStreamCopy(state); - if (state.VideoRequest != null) + if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue) { - state.OutputVideoCodec = state.Request.VideoCodec; - state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - - encodingHelper.TryStreamCopy(state); + var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue + && !state.VideoRequest.Height.HasValue + && !state.VideoRequest.MaxWidth.HasValue + && !state.VideoRequest.MaxHeight.HasValue; - if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue) + if (isVideoResolutionNotRequested + && state.VideoStream is not null + && state.VideoRequest.VideoBitRate.HasValue + && state.VideoStream.BitRate.HasValue + && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value) { - var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue - && !state.VideoRequest.Height.HasValue - && !state.VideoRequest.MaxWidth.HasValue - && !state.VideoRequest.MaxHeight.HasValue; - - if (isVideoResolutionNotRequested - && state.VideoStream != null - && state.VideoRequest.VideoBitRate.HasValue - && state.VideoStream.BitRate.HasValue - && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value) - { - // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested, - // and the requested video bitrate is higher than source video bitrate. - if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue) - { - state.VideoRequest.MaxWidth = state.VideoStream?.Width; - state.VideoRequest.MaxHeight = state.VideoStream?.Height; - } - } - else + // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested, + // and the requested video bitrate is higher than source video bitrate. + if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue) { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream?.BitRate, - state.OutputVideoBitrate.Value, - state.VideoRequest.MaxWidth, - state.VideoRequest.MaxHeight); - - state.VideoRequest.MaxWidth = resolution.MaxWidth; - state.VideoRequest.MaxHeight = resolution.MaxHeight; + state.VideoRequest.MaxWidth = state.VideoStream?.Width; + state.VideoRequest.MaxHeight = state.VideoStream?.Height; } } + else + { + var resolution = ResolutionNormalizer.Normalize( + state.VideoStream?.BitRate, + state.OutputVideoBitrate.Value, + state.VideoRequest.MaxWidth, + state.VideoRequest.MaxHeight); + + state.VideoRequest.MaxWidth = resolution.MaxWidth; + state.VideoRequest.MaxHeight = resolution.MaxHeight; + } } + } - ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static); + ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static); - var ext = string.IsNullOrWhiteSpace(state.OutputContainer) - ? GetOutputFileExtension(state, mediaSource) - : ("." + state.OutputContainer); + var ext = string.IsNullOrWhiteSpace(state.OutputContainer) + ? GetOutputFileExtension(state, mediaSource) + : ("." + state.OutputContainer); - state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); + state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); - return state; - } + return state; + } - /// <summary> - /// Adds the dlna headers. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param> - /// <param name="startTimeTicks">The start time in ticks.</param> - /// <param name="request">The <see cref="HttpRequest"/>.</param> - /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> - public static void AddDlnaHeaders( - StreamState state, - IHeaderDictionary responseHeaders, - bool isStaticallyStreamed, - long? startTimeTicks, - HttpRequest request, - IDlnaManager dlnaManager) + /// <summary> + /// Adds the dlna headers. + /// </summary> + /// <param name="state">The state.</param> + /// <param name="responseHeaders">The response headers.</param> + /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param> + /// <param name="startTimeTicks">The start time in ticks.</param> + /// <param name="request">The <see cref="HttpRequest"/>.</param> + /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> + public static void AddDlnaHeaders( + StreamState state, + IHeaderDictionary responseHeaders, + bool isStaticallyStreamed, + long? startTimeTicks, + HttpRequest request, + IDlnaManager dlnaManager) + { + if (!state.EnableDlnaHeaders) { - if (!state.EnableDlnaHeaders) - { - return; - } + return; + } - var profile = state.DeviceProfile; + var profile = state.DeviceProfile; - StringValues transferMode = request.Headers["transferMode.dlna.org"]; - responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString()); - responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"); + StringValues transferMode = request.Headers["transferMode.dlna.org"]; + responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString()); + responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"); - if (state.RunTimeTicks.HasValue) + if (state.RunTimeTicks.HasValue) + { + if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase)) - { - var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; - responseHeaders.Add("MediaInfo.sec", string.Format( - CultureInfo.InvariantCulture, - "SEC_Duration={0};", - Convert.ToInt32(ms))); - } + var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; + responseHeaders.Add("MediaInfo.sec", string.Format( + CultureInfo.InvariantCulture, + "SEC_Duration={0};", + Convert.ToInt32(ms))); + } - if (!isStaticallyStreamed && profile != null) - { - AddTimeSeekResponseHeaders(state, responseHeaders, startTimeTicks); - } + if (!isStaticallyStreamed && profile is not null) + { + AddTimeSeekResponseHeaders(state, responseHeaders, startTimeTicks); } + } - profile ??= dlnaManager.GetDefaultProfile(); + profile ??= dlnaManager.GetDefaultProfile(); - var audioCodec = state.ActualOutputAudioCodec; + var audioCodec = state.ActualOutputAudioCodec; - if (!state.IsVideoRequest) - { - responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader( - profile, - state.OutputContainer, - audioCodec, - state.OutputAudioBitrate, - state.OutputAudioSampleRate, - state.OutputAudioChannels, - state.OutputAudioBitDepth, - isStaticallyStreamed, - state.RunTimeTicks, - state.TranscodeSeekInfo)); - } - else - { - var videoCodec = state.ActualOutputVideoCodec; + if (!state.IsVideoRequest) + { + responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader( + profile, + state.OutputContainer, + audioCodec, + state.OutputAudioBitrate, + state.OutputAudioSampleRate, + state.OutputAudioChannels, + state.OutputAudioBitDepth, + isStaticallyStreamed, + state.RunTimeTicks, + state.TranscodeSeekInfo)); + } + else + { + var videoCodec = state.ActualOutputVideoCodec; - responseHeaders.Add( - "contentFeatures.dlna.org", - ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty); - } + responseHeaders.Add( + "contentFeatures.dlna.org", + ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty); } + } - /// <summary> - /// Parses the time seek header. - /// </summary> - /// <param name="value">The time seek header string.</param> - /// <returns>A nullable <see cref="long"/> representing the seek time in ticks.</returns> - private static long? ParseTimeSeekHeader(ReadOnlySpan<char> value) + /// <summary> + /// Parses the time seek header. + /// </summary> + /// <param name="value">The time seek header string.</param> + /// <returns>A nullable <see cref="long"/> representing the seek time in ticks.</returns> + private static long? ParseTimeSeekHeader(ReadOnlySpan<char> value) + { + if (value.IsEmpty) { - if (value.IsEmpty) - { - return null; - } + return null; + } - const string npt = "npt="; - if (!value.StartsWith(npt, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Invalid timeseek header"); - } + const string npt = "npt="; + if (!value.StartsWith(npt, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Invalid timeseek header"); + } - var index = value.IndexOf('-'); - value = index == -1 - ? value.Slice(npt.Length) - : value.Slice(npt.Length, index - npt.Length); - if (value.IndexOf(':') == -1) + var index = value.IndexOf('-'); + value = index == -1 + ? value.Slice(npt.Length) + : value.Slice(npt.Length, index - npt.Length); + if (!value.Contains(':')) + { + // Parses npt times in the format of '417.33' + if (double.TryParse(value, CultureInfo.InvariantCulture, out var seconds)) { - // Parses npt times in the format of '417.33' - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds)) - { - return TimeSpan.FromSeconds(seconds).Ticks; - } - - throw new ArgumentException("Invalid timeseek header"); + return TimeSpan.FromSeconds(seconds).Ticks; } - try - { - // Parses npt times in the format of '10:19:25.7' - return TimeSpan.Parse(value).Ticks; - } - catch - { - throw new ArgumentException("Invalid timeseek header"); - } + throw new ArgumentException("Invalid timeseek header"); } - /// <summary> - /// Parses query parameters as StreamOptions. - /// </summary> - /// <param name="queryString">The query string.</param> - /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns> - private static Dictionary<string, string> ParseStreamOptions(IQueryCollection queryString) + try + { + // Parses npt times in the format of '10:19:25.7' + return TimeSpan.Parse(value, CultureInfo.InvariantCulture).Ticks; + } + catch { - Dictionary<string, string> streamOptions = new Dictionary<string, string>(); - foreach (var param in queryString) + throw new ArgumentException("Invalid timeseek header"); + } + } + + /// <summary> + /// Parses query parameters as StreamOptions. + /// </summary> + /// <param name="queryString">The query string.</param> + /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns> + private static Dictionary<string, string?> ParseStreamOptions(IQueryCollection queryString) + { + Dictionary<string, string?> streamOptions = new Dictionary<string, string?>(); + foreach (var param in queryString) + { + if (char.IsLower(param.Key[0])) { - if (char.IsLower(param.Key[0])) - { - // This was probably not parsed initially and should be a StreamOptions - // or the generated URL should correctly serialize it - // TODO: This should be incorporated either in the lower framework for parsing requests - streamOptions[param.Key] = param.Value; - } + // This was probably not parsed initially and should be a StreamOptions + // or the generated URL should correctly serialize it + // TODO: This should be incorporated either in the lower framework for parsing requests + streamOptions[param.Key] = param.Value; } - - return streamOptions; } - /// <summary> - /// Adds the dlna time seek headers to the response. - /// </summary> - /// <param name="state">The current <see cref="StreamState"/>.</param> - /// <param name="responseHeaders">The <see cref="IHeaderDictionary"/> of the response.</param> - /// <param name="startTimeTicks">The start time in ticks.</param> - private static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks) - { - var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture); - var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture); + return streamOptions; + } - responseHeaders.Add("TimeSeekRange.dlna.org", string.Format( - CultureInfo.InvariantCulture, - "npt={0}-{1}/{1}", - startSeconds, - runtimeSeconds)); - responseHeaders.Add("X-AvailableSeekRange", string.Format( - CultureInfo.InvariantCulture, - "1 npt={0}-{1}", - startSeconds, - runtimeSeconds)); + /// <summary> + /// Adds the dlna time seek headers to the response. + /// </summary> + /// <param name="state">The current <see cref="StreamState"/>.</param> + /// <param name="responseHeaders">The <see cref="IHeaderDictionary"/> of the response.</param> + /// <param name="startTimeTicks">The start time in ticks.</param> + private static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks) + { + var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture); + var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture); + + responseHeaders.Add("TimeSeekRange.dlna.org", string.Format( + CultureInfo.InvariantCulture, + "npt={0}-{1}/{1}", + startSeconds, + runtimeSeconds)); + responseHeaders.Add("X-AvailableSeekRange", string.Format( + CultureInfo.InvariantCulture, + "1 npt={0}-{1}", + startSeconds, + runtimeSeconds)); + } + + /// <summary> + /// Gets the output file extension. + /// </summary> + /// <param name="state">The state.</param> + /// <param name="mediaSource">The mediaSource.</param> + /// <returns>System.String.</returns> + private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource) + { + var ext = Path.GetExtension(state.RequestedUrl); + + if (!string.IsNullOrEmpty(ext)) + { + return ext; } - /// <summary> - /// Gets the output file extension. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="mediaSource">The mediaSource.</param> - /// <returns>System.String.</returns> - private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource) + // Try to infer based on the desired video codec + if (state.IsVideoRequest) { - var ext = Path.GetExtension(state.RequestedUrl); + var videoCodec = state.Request.VideoCodec; - if (!string.IsNullOrEmpty(ext)) + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) { - return ext; + return ".ts"; } - // Try to infer based on the desired video codec - if (state.IsVideoRequest) + if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) { - var videoCodec = state.Request.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - - if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } + return ".ogv"; } - // Try to infer based on the desired audio codec - if (!state.IsVideoRequest) + if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) { - var audioCodec = state.Request.AudioCodec; + return ".webm"; + } - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } + if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) + { + return ".asf"; + } + } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } + // Try to infer based on the desired audio codec + if (!state.IsVideoRequest) + { + var audioCodec = state.Request.AudioCodec; - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } + if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".aac"; + } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } + if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".mp3"; } - // Fallback to the container of mediaSource - if (!string.IsNullOrEmpty(mediaSource?.Container)) + if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) { - var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase); - return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim(); + return ".ogg"; } - return null; + if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) + { + return ".wma"; + } } - /// <summary> - /// Gets the output file path for transcoding. - /// </summary> - /// <param name="state">The current <see cref="StreamState"/>.</param> - /// <param name="outputFileExtension">The file extension of the output file.</param> - /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> - /// <param name="deviceId">The device id.</param> - /// <param name="playSessionId">The play session id.</param> - /// <returns>The complete file path, including the folder, for the transcoding file.</returns> - private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId) + // Fallback to the container of mediaSource + if (!string.IsNullOrEmpty(mediaSource?.Container)) { - var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}"; + var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase); + return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim(); + } - var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); - var ext = outputFileExtension?.ToLowerInvariant(); - var folder = serverConfigurationManager.GetTranscodePath(); + return null; + } - return Path.Combine(folder, filename + ext); - } + /// <summary> + /// Gets the output file path for transcoding. + /// </summary> + /// <param name="state">The current <see cref="StreamState"/>.</param> + /// <param name="outputFileExtension">The file extension of the output file.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + /// <param name="deviceId">The device id.</param> + /// <param name="playSessionId">The play session id.</param> + /// <returns>The complete file path, including the folder, for the transcoding file.</returns> + private static string GetOutputFilePath(StreamState state, string outputFileExtension, IServerConfigurationManager serverConfigurationManager, string? deviceId, string? playSessionId) + { + var data = $"{state.MediaPath}-{state.UserAgent}-{deviceId!}-{playSessionId!}"; - private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) - { - if (!string.IsNullOrWhiteSpace(deviceProfileId)) - { - state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); + var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); + var ext = outputFileExtension?.ToLowerInvariant(); + var folder = serverConfigurationManager.GetTranscodePath(); - if (state.DeviceProfile == null) - { - var caps = deviceManager.GetCapabilities(deviceProfileId); - state.DeviceProfile = caps == null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile; - } - } + return Path.Combine(folder, filename + ext); + } - var profile = state.DeviceProfile; + private static void ApplyDeviceProfileSettings(StreamState state, IDlnaManager dlnaManager, IDeviceManager deviceManager, HttpRequest request, string? deviceProfileId, bool? @static) + { + if (!string.IsNullOrWhiteSpace(deviceProfileId)) + { + state.DeviceProfile = dlnaManager.GetProfile(deviceProfileId); - if (profile == null) + if (state.DeviceProfile is null) { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; + var caps = deviceManager.GetCapabilities(deviceProfileId); + state.DeviceProfile = caps is null ? dlnaManager.GetProfile(request.Headers) : caps.DeviceProfile; } + } - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; + var profile = state.DeviceProfile; - var mediaProfile = !state.IsVideoRequest - ? profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) - : profile.GetVideoMediaProfile( - state.OutputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoRangeType, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } + if (profile is null) + { + // Don't use settings from the default profile. + // Only use a specific profile if it was requested. + return; + } + + var audioCodec = state.ActualOutputAudioCodec; + var videoCodec = state.ActualOutputVideoCodec; + + var mediaProfile = !state.IsVideoRequest + ? profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) + : profile.GetVideoMediaProfile( + state.OutputContainer, + audioCodec, + videoCodec, + state.OutputWidth, + state.OutputHeight, + state.TargetVideoBitDepth, + state.OutputVideoBitrate, + state.TargetVideoProfile, + state.TargetVideoRangeType, + state.TargetVideoLevel, + state.TargetFramerate, + state.TargetPacketLength, + state.TargetTimestamp, + state.IsTargetAnamorphic, + state.IsTargetInterlaced, + state.TargetRefFrames, + state.TargetVideoStreamCount, + state.TargetAudioStreamCount, + state.TargetVideoCodecTag, + state.IsTargetAVC); + + if (mediaProfile is not null) + { + state.MimeType = mediaProfile.MimeType; + } - if (!(@static.HasValue && @static.Value)) + if (!(@static.HasValue && @static.Value)) + { + var transcodingProfile = !state.IsVideoRequest ? profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec); + + if (transcodingProfile is not null) { - var transcodingProfile = !state.IsVideoRequest ? profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec); + state.EstimateContentLength = transcodingProfile.EstimateContentLength; + // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; + state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - if (transcodingProfile != null) + if (state.VideoRequest is not null) { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - if (state.VideoRequest != null) - { - state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; - state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; - } + state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; + state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; } } } + } - /// <summary> - /// Parses the parameters. - /// </summary> - /// <param name="request">The request.</param> - private static void ParseParams(StreamingRequestDto request) + /// <summary> + /// Parses the parameters. + /// </summary> + /// <param name="request">The request.</param> + private static void ParseParams(StreamingRequestDto request) + { + if (string.IsNullOrEmpty(request.Params)) { - if (string.IsNullOrEmpty(request.Params)) - { - return; - } + return; + } - var vals = request.Params.Split(';'); + var vals = request.Params.Split(';'); - var videoRequest = request as VideoRequestDto; + var videoRequest = request as VideoRequestDto; - for (var i = 0; i < vals.Length; i++) - { - var val = vals[i]; + for (var i = 0; i < vals.Length; i++) + { + var val = vals[i]; - if (string.IsNullOrWhiteSpace(val)) - { - continue; - } + if (string.IsNullOrWhiteSpace(val)) + { + continue; + } - switch (i) - { - case 0: - request.DeviceProfileId = val; - break; - case 1: - request.DeviceId = val; - break; - case 2: - request.MediaSourceId = val; - break; - case 3: - request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - break; - case 4: - if (videoRequest != null) - { - videoRequest.VideoCodec = val; - } + switch (i) + { + case 0: + request.DeviceProfileId = val; + break; + case 1: + request.DeviceId = val; + break; + case 2: + request.MediaSourceId = val; + break; + case 3: + request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + break; + case 4: + if (videoRequest is not null) + { + videoRequest.VideoCodec = val; + } - break; - case 5: - request.AudioCodec = val; - break; - case 6: - if (videoRequest != null) - { - videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 5: + request.AudioCodec = val; + break; + case 6: + if (videoRequest is not null) + { + videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 7: - if (videoRequest != null) - { - videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 7: + if (videoRequest is not null) + { + videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 8: - if (videoRequest != null) - { - videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 8: + if (videoRequest is not null) + { + videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 9: - request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 10: - request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 11: - if (videoRequest != null) - { - videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 9: + request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 10: + request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 11: + if (videoRequest is not null) + { + videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 12: - if (videoRequest != null) - { - videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 12: + if (videoRequest is not null) + { + videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 13: - if (videoRequest != null) - { - videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 13: + if (videoRequest is not null) + { + videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 14: - request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture); - break; - case 15: - if (videoRequest != null) - { - videoRequest.Level = val; - } + break; + case 14: + request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture); + break; + case 15: + if (videoRequest is not null) + { + videoRequest.Level = val; + } - break; - case 16: - if (videoRequest != null) - { - videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 16: + if (videoRequest is not null) + { + videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 17: - if (videoRequest != null) - { - videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture); - } + break; + case 17: + if (videoRequest is not null) + { + videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture); + } - break; - case 18: - if (videoRequest != null) - { - videoRequest.Profile = val; - } + break; + case 18: + if (videoRequest is not null) + { + videoRequest.Profile = val; + } - break; - case 19: - // cabac no longer used - break; - case 20: - request.PlaySessionId = val; - break; - case 21: - // api_key - break; - case 22: - request.LiveStreamId = val; - break; - case 23: - // Duplicating ItemId because of MediaMonkey - break; - case 24: - if (videoRequest != null) - { - videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } + break; + case 19: + // cabac no longer used + break; + case 20: + request.PlaySessionId = val; + break; + case 21: + // api_key + break; + case 22: + request.LiveStreamId = val; + break; + case 23: + // Duplicating ItemId because of MediaMonkey + break; + case 24: + if (videoRequest is not null) + { + videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } - break; - case 25: - if (!string.IsNullOrWhiteSpace(val) && videoRequest != null) + break; + case 25: + if (!string.IsNullOrWhiteSpace(val) && videoRequest is not null) + { + if (Enum.TryParse(val, out SubtitleDeliveryMethod method)) { - if (Enum.TryParse(val, out SubtitleDeliveryMethod method)) - { - videoRequest.SubtitleMethod = method; - } + videoRequest.SubtitleMethod = method; } + } - break; - case 26: - request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 27: - if (videoRequest != null) - { - videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } + break; + case 26: + request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); + break; + case 27: + if (videoRequest is not null) + { + videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } - break; - case 28: - request.Tag = val; - break; - case 29: - if (videoRequest != null) - { - videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } + break; + case 28: + request.Tag = val; + break; + case 29: + if (videoRequest is not null) + { + videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } - break; - case 30: - request.SubtitleCodec = val; - break; - case 31: - if (videoRequest != null) - { - videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } + break; + case 30: + request.SubtitleCodec = val; + break; + case 31: + if (videoRequest is not null) + { + videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } - break; - case 32: - if (videoRequest != null) - { - videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } + break; + case 32: + if (videoRequest is not null) + { + videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } - break; - case 33: - request.TranscodeReasons = val; - break; - } + break; + case 33: + request.TranscodeReasons = val; + break; } } } |
