/// <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.Ordinal)); 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); }
/// <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; var cancellationTokenSource = new CancellationTokenSource(); using var state = await StreamingHelpers.GetStreamingState( streamingRequest, _httpContextAccessor.HttpContext.Request, _authContext, _mediaSourceManager, _userManager, _libraryManager, _serverConfigurationManager, _mediaEncoder, _fileSystem, _subtitleEncoder, _configuration, _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 encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath); return(await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, _httpContextAccessor.HttpContext, _transcodingJobHelper, ffmpegCommandLineArguments, transcodingJobType, cancellationTokenSource).ConfigureAwait(false)); }