示例#1
0
        public async Task <ActionResult> GetVideoStream(
            [FromRoute] Guid itemId,
            [FromRoute] string?container,
            [FromQuery] bool? @static,
            [FromQuery] string? @params,
            [FromQuery] string?tag,
            [FromQuery] string?deviceProfileId,
            [FromQuery] string?playSessionId,
            [FromQuery] string?segmentContainer,
            [FromQuery] int?segmentLength,
            [FromQuery] int?minSegments,
            [FromQuery] string?mediaSourceId,
            [FromQuery] string?deviceId,
            [FromQuery] string?audioCodec,
            [FromQuery] bool?enableAutoStreamCopy,
            [FromQuery] bool?allowVideoStreamCopy,
            [FromQuery] bool?allowAudioStreamCopy,
            [FromQuery] bool?breakOnNonKeyFrames,
            [FromQuery] int?audioSampleRate,
            [FromQuery] int?maxAudioBitDepth,
            [FromQuery] int?audioBitRate,
            [FromQuery] int?audioChannels,
            [FromQuery] int?maxAudioChannels,
            [FromQuery] string?profile,
            [FromQuery] string?level,
            [FromQuery] float?framerate,
            [FromQuery] float?maxFramerate,
            [FromQuery] bool?copyTimestamps,
            [FromQuery] long?startTimeTicks,
            [FromQuery] int?width,
            [FromQuery] int?height,
            [FromQuery] int?videoBitRate,
            [FromQuery] int?subtitleStreamIndex,
            [FromQuery] SubtitleDeliveryMethod subtitleMethod,
            [FromQuery] int?maxRefFrames,
            [FromQuery] int?maxVideoBitDepth,
            [FromQuery] bool?requireAvc,
            [FromQuery] bool?deInterlace,
            [FromQuery] bool?requireNonAnamorphic,
            [FromQuery] int?transcodingMaxAudioChannels,
            [FromQuery] int?cpuCoreLimit,
            [FromQuery] string?liveStreamId,
            [FromQuery] bool?enableMpegtsM2TsMode,
            [FromQuery] string?videoCodec,
            [FromQuery] string?subtitleCodec,
            [FromQuery] string?transcodingReasons,
            [FromQuery] int?audioStreamIndex,
            [FromQuery] int?videoStreamIndex,
            [FromQuery] EncodingContext context,
            [FromQuery] Dictionary <string, string> streamOptions)
        {
            var isHeadRequest           = Request.Method == System.Net.WebRequestMethods.Http.Head;
            var cancellationTokenSource = new CancellationTokenSource();
            var streamingRequest        = new VideoRequestDto
            {
                Id                          = itemId,
                Container                   = container,
                Static                      = @static ?? true,
                Params                      = @params,
                Tag                         = tag,
                DeviceProfileId             = deviceProfileId,
                PlaySessionId               = playSessionId,
                SegmentContainer            = segmentContainer,
                SegmentLength               = segmentLength,
                MinSegments                 = minSegments,
                MediaSourceId               = mediaSourceId,
                DeviceId                    = deviceId,
                AudioCodec                  = audioCodec,
                EnableAutoStreamCopy        = enableAutoStreamCopy ?? true,
                AllowAudioStreamCopy        = allowAudioStreamCopy ?? true,
                AllowVideoStreamCopy        = allowVideoStreamCopy ?? true,
                BreakOnNonKeyFrames         = breakOnNonKeyFrames ?? false,
                AudioSampleRate             = audioSampleRate,
                MaxAudioChannels            = maxAudioChannels,
                AudioBitRate                = audioBitRate,
                MaxAudioBitDepth            = maxAudioBitDepth,
                AudioChannels               = audioChannels,
                Profile                     = profile,
                Level                       = level,
                Framerate                   = framerate,
                MaxFramerate                = maxFramerate,
                CopyTimestamps              = copyTimestamps ?? true,
                StartTimeTicks              = startTimeTicks,
                Width                       = width,
                Height                      = height,
                VideoBitRate                = videoBitRate,
                SubtitleStreamIndex         = subtitleStreamIndex,
                SubtitleMethod              = subtitleMethod,
                MaxRefFrames                = maxRefFrames,
                MaxVideoBitDepth            = maxVideoBitDepth,
                RequireAvc                  = requireAvc ?? true,
                DeInterlace                 = deInterlace ?? true,
                RequireNonAnamorphic        = requireNonAnamorphic ?? true,
                TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
                CpuCoreLimit                = cpuCoreLimit,
                LiveStreamId                = liveStreamId,
                EnableMpegtsM2TsMode        = enableMpegtsM2TsMode ?? true,
                VideoCodec                  = videoCodec,
                SubtitleCodec               = subtitleCodec,
                TranscodeReasons            = transcodingReasons,
                AudioStreamIndex            = audioStreamIndex,
                VideoStreamIndex            = videoStreamIndex,
                Context                     = context,
                StreamOptions               = streamOptions
            };

            using var state = await StreamingHelpers.GetStreamingState(
                      streamingRequest,
                      Request,
                      _authContext,
                      _mediaSourceManager,
                      _userManager,
                      _libraryManager,
                      _serverConfigurationManager,
                      _mediaEncoder,
                      _fileSystem,
                      _subtitleEncoder,
                      _configuration,
                      _dlnaManager,
                      _deviceManager,
                      _transcodingJobHelper,
                      _transcodingJobType,
                      cancellationTokenSource.Token)
                              .ConfigureAwait(false);

            if (@static.HasValue && @static.Value && state.DirectStreamProvider != null)
            {
                StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);

                await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
                {
                    AllowEndOfFile = false
                }.WriteToAsync(Response.Body, CancellationToken.None)
                .ConfigureAwait(false);

                // TODO (moved from MediaBrowser.Api): Don't hardcode contentType
                return(File(Response.Body, MimeTypes.GetMimeType("file.ts") !));
            }

            // Static remote stream
            if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http)
            {
                StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);

                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
                return(await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false));
            }

            if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
            {
                return(BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically"));
            }

            var outputPath       = state.OutputFilePath;
            var outputPathExists = System.IO.File.Exists(outputPath);

            var transcodingJob    = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
            var isTranscodeCached = outputPathExists && transcodingJob != null;

            StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, startTimeTicks, Request, _dlnaManager);

            // Static stream
            if (@static.HasValue && @static.Value)
            {
                var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);

                if (state.MediaSource.IsInfiniteStream)
                {
                    await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
                    {
                        AllowEndOfFile = false
                    }.WriteToAsync(Response.Body, CancellationToken.None)
                    .ConfigureAwait(false);

                    return(File(Response.Body, contentType));
                }

                return(FileStreamResponseHelpers.GetStaticFileResult(
                           state.MediaPath,
                           contentType,
                           isHeadRequest,
                           HttpContext));
            }

            // Need to start ffmpeg (because media can't be returned directly)
            var encodingOptions            = _serverConfigurationManager.GetEncodingOptions();
            var encodingHelper             = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
            var ffmpegCommandLineArguments = encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, "superfast");

            return(await FileStreamResponseHelpers.GetTranscodedFile(
                       state,
                       isHeadRequest,
                       HttpContext,
                       _transcodingJobHelper,
                       ffmpegCommandLineArguments,
                       _transcodingJobType,
                       cancellationTokenSource).ConfigureAwait(false));
        }
示例#2
0
        /// <summary>
        /// Get audio stream.
        /// </summary>
        /// <param name="transcodingJobType">Transcoding job type.</param>
        /// <param name="streamingRequest">Streaming controller.Request dto.</param>
        /// <returns>A <see cref="Task"/> containing the resulting <see cref="ActionResult"/>.</returns>
        public async Task <ActionResult> GetAudioStream(
            TranscodingJobType transcodingJobType,
            StreamingRequestDto streamingRequest)
        {
            if (_httpContextAccessor.HttpContext == null)
            {
                throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
            }

            bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;

            // CTS lifecycle is managed internally.
            var cancellationTokenSource = new CancellationTokenSource();

            using var state = await StreamingHelpers.GetStreamingState(
                      streamingRequest,
                      _httpContextAccessor.HttpContext.Request,
                      _authContext,
                      _mediaSourceManager,
                      _userManager,
                      _libraryManager,
                      _serverConfigurationManager,
                      _mediaEncoder,
                      _encodingHelper,
                      _dlnaManager,
                      _deviceManager,
                      _transcodingJobHelper,
                      transcodingJobType,
                      cancellationTokenSource.Token)
                              .ConfigureAwait(false);

            if (streamingRequest.Static && state.DirectStreamProvider != null)
            {
                StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

                await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
                {
                    AllowEndOfFile = false
                }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
                .ConfigureAwait(false);

                // TODO (moved from MediaBrowser.Api): Don't hardcode contentType
                return(new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, MimeTypes.GetMimeType("file.ts") !));
            }

            // Static remote stream
            if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http)
            {
                StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
                return(await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false));
            }

            if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File)
            {
                return(new BadRequestObjectResult($"Input protocol {state.InputProtocol} cannot be streamed statically"));
            }

            var outputPath       = state.OutputFilePath;
            var outputPathExists = System.IO.File.Exists(outputPath);

            var transcodingJob    = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
            var isTranscodeCached = outputPathExists && transcodingJob != null;

            StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

            // Static stream
            if (streamingRequest.Static)
            {
                var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);

                if (state.MediaSource.IsInfiniteStream)
                {
                    await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
                    {
                        AllowEndOfFile = false
                    }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
                    .ConfigureAwait(false);

                    return(new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, contentType));
                }

                return(FileStreamResponseHelpers.GetStaticFileResult(
                           state.MediaPath,
                           contentType,
                           isHeadRequest,
                           _httpContextAccessor.HttpContext));
            }

            // Need to start ffmpeg (because media can't be returned directly)
            var encodingOptions            = _serverConfigurationManager.GetEncodingOptions();
            var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);

            return(await FileStreamResponseHelpers.GetTranscodedFile(
                       state,
                       isHeadRequest,
                       _httpContextAccessor.HttpContext,
                       _transcodingJobHelper,
                       ffmpegCommandLineArguments,
                       transcodingJobType,
                       cancellationTokenSource).ConfigureAwait(false));
        }
示例#3
0
        private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration)
        {
            string videoArgs;

            if (EncodeVideo(mediaSource))
            {
                const int MaxBitrate = 25000000;
                videoArgs = string.Format(
                    CultureInfo.InvariantCulture,
                    "-codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41",
                    GetOutputSizeParam(),
                    MaxBitrate);
            }
            else
            {
                videoArgs = "-codec:v:0 copy";
            }

            videoArgs += " -fflags +genpts";

            var flags = new List <string>();

            if (mediaSource.IgnoreDts)
            {
                flags.Add("+igndts");
            }

            if (mediaSource.IgnoreIndex)
            {
                flags.Add("+ignidx");
            }

            if (mediaSource.GenPtsInput)
            {
                flags.Add("+genpts");
            }

            var inputModifier = "-async 1 -vsync -1";

            if (flags.Count > 0)
            {
                inputModifier += " -fflags " + string.Join(string.Empty, flags);
            }

            if (mediaSource.ReadAtNativeFramerate)
            {
                inputModifier += " -re";
            }

            if (mediaSource.RequiresLooping)
            {
                inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
            }

            var analyzeDurationSeconds = 5;
            var analyzeDuration        = " -analyzeduration " +
                                         (analyzeDurationSeconds * 1000000).ToString(CultureInfo.InvariantCulture);

            inputModifier += analyzeDuration;

            var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";

            // var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
            //    " -f mp4 -movflags frag_keyframe+empty_moov" :
            //    string.Empty;

            var outputParam = string.Empty;

            var threads         = EncodingHelper.GetNumberOfThreads(null, _serverConfigurationManager.GetEncodingOptions(), null);
            var commandLineArgs = string.Format(
                CultureInfo.InvariantCulture,
                "-i \"{0}\" {2} -map_metadata -1 -threads {6} {3}{4}{5} -y \"{1}\"",
                inputTempFile,
                targetFile.Replace("\"", "\\\"", StringComparison.Ordinal), // Escape quotes in filename
                videoArgs,
                GetAudioArgs(mediaSource),
                subtitleArgs,
                outputParam,
                threads);

            return(inputModifier + " " + commandLineArgs);
        }
        /// <summary>
        /// Gets the current streaming state.
        /// </summary>
        /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
        /// <param name="httpRequest">The <see cref="HttpRequest"/>.</param>
        /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</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,
            HttpRequest httpRequest,
            IAuthorizationContext authorizationContext,
            IMediaSourceManager mediaSourceManager,
            IUserManager userManager,
            ILibraryManager libraryManager,
            IServerConfigurationManager serverConfigurationManager,
            IMediaEncoder mediaEncoder,
            EncodingHelper encodingHelper,
            IDlnaManager dlnaManager,
            IDeviceManager deviceManager,
            TranscodingJobHelper transcodingJobHelper,
            TranscodingJobType transcodingJobType,
            CancellationToken cancellationToken)
        {
            // Parse the DLNA time seek header
            if (!streamingRequest.StartTimeTicks.HasValue)
            {
                var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];

                streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString());
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
            {
                ParseParams(streamingRequest);
            }

            streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
            if (httpRequest.Path.Value == null)
            {
                throw new ResourceNotFoundException(nameof(httpRequest.Path));
            }

            var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();

            if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
            {
                streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
            }

            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
                                    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 auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);

            if (!auth.UserId.Equals(Guid.Empty))
            {
                state.User = userManager.GetUserById(auth.UserId);
            }

            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.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);

            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;

                if (currentJob != null)
                {
                    mediaSource = currentJob.MediaSource;
                }

                if (mediaSource == 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.InvariantCulture));

                    if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
                    {
                        mediaSource = mediaSources[0];
                    }
                }
            }
            else
            {
                var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false);

                mediaSource = liveStreamInfo.Item1;
                state.DirectStreamProvider = liveStreamInfo.Item2;
            }

            var encodingOptions = serverConfigurationManager.GetEncodingOptions();

            encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);

            string?containerInternal = Path.GetExtension(state.RequestedUrl);

            if (!string.IsNullOrEmpty(streamingRequest.Container))
            {
                containerInternal = streamingRequest.Container;
            }

            if (string.IsNullOrEmpty(containerInternal))
            {
                containerInternal = streamingRequest.Static ?
                                    StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
                    : GetOutputFileExtension(state);
            }

            state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');

            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);

            state.OutputAudioCodec = streamingRequest.AudioCodec;

            state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);

            if (state.VideoRequest != null)
            {
                state.OutputVideoCodec   = state.Request.VideoCodec;
                state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);

                encodingHelper.TryStreamCopy(state);

                if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
                {
                    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
                    {
                        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);

            var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
                ? GetOutputFileExtension(state)
                : ("." + state.OutputContainer);

            state.OutputFilePath = GetOutputFilePath(state, ext !, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);

            return(state);
        }