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)); }
/// <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)); }
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); }