From 3514813eb4eda997a0ea722cc2ed41979419c6dd Mon Sep 17 00:00:00 2001 From: David Date: Sun, 12 Jul 2020 11:14:38 +0200 Subject: Continute work --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 236 ++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs new file mode 100644 index 000000000..e03cafe35 --- /dev/null +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -0,0 +1,236 @@ +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Api.Helpers +{ + /// + /// The stream response helpers. + /// + public static class FileStreamResponseHelpers + { + /// + /// Returns a static file from a remote source. + /// + /// The current . + /// Whether the current request is a HTTP HEAD request so only the headers get returned. + /// The managing the response. + /// The . + /// A containing the API response. + public static async Task GetStaticRemoteStreamResult( + StreamState state, + bool isHeadRequest, + ControllerBase controller, + CancellationTokenSource cancellationTokenSource) + { + HttpClient httpClient = new HttpClient(); + var responseHeaders = controller.Response.Headers; + + if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) + { + httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent); + } + + var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); + var contentType = response.Content.Headers.ContentType.ToString(); + + responseHeaders[HeaderNames.AcceptRanges] = "none"; + + // Seeing cases of -1 here + if (response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength.Value >= 0) + { + responseHeaders[HeaderNames.ContentLength] = response.Content.Headers.ContentLength.Value.ToString(CultureInfo.InvariantCulture); + } + + if (isHeadRequest) + { + using (response) + { + return controller.File(Array.Empty(), contentType); + } + } + + return controller.File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType); + } + + /// + /// Returns a static file from the server. + /// + /// The path to the file. + /// The content type of the file. + /// The of the last modification of the file. + /// The cache duration of the file. + /// Whether the current request is a HTTP HEAD request so only the headers get returned. + /// The managing the response. + /// An the file. + // TODO: caching doesn't work + public static ActionResult GetStaticFileResult( + string path, + string contentType, + DateTime dateLastModified, + TimeSpan? cacheDuration, + bool isHeadRequest, + ControllerBase controller) + { + bool disableCaching = false; + if (controller.Request.Headers.TryGetValue(HeaderNames.CacheControl, out StringValues headerValue)) + { + disableCaching = headerValue.FirstOrDefault().Contains("no-cache", StringComparison.InvariantCulture); + } + + bool parsingSuccessful = DateTime.TryParseExact(controller.Request.Headers[HeaderNames.IfModifiedSince], "ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false), DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime ifModifiedSinceHeader); + + // if the parsing of the IfModifiedSince header was not successfull, disable caching + if (!parsingSuccessful) + { + disableCaching = true; + } + + controller.Response.ContentType = contentType; + controller.Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateLastModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); + controller.Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept); + + if (disableCaching) + { + controller.Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); + controller.Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate"); + } + else + { + if (cacheDuration.HasValue) + { + controller.Response.Headers.Add(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds); + } + else + { + controller.Response.Headers.Add(HeaderNames.CacheControl, "public"); + } + + controller.Response.Headers.Add(HeaderNames.LastModified, dateLastModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); + + // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified + if (!(dateLastModified > ifModifiedSinceHeader)) + { + if (ifModifiedSinceHeader.Add(cacheDuration!.Value) < DateTime.UtcNow) + { + controller.Response.StatusCode = StatusCodes.Status304NotModified; + return new ContentResult(); + } + } + } + + // if the request is a head request, return a NoContent result with the same headers as it would with a GET request + if (isHeadRequest) + { + return controller.NoContent(); + } + + var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + return controller.File(stream, contentType); + } + + /// + /// Returns a transcoded file from the server. + /// + /// The current . + /// Whether the current request is a HTTP HEAD request so only the headers get returned. + /// Instance of the interface. + /// The managing the response. + /// The singleton. + /// The command line arguments to start ffmpeg. + /// The starting the transcoding. + /// The . + /// The . + /// A containing the transcoded file. + public static async Task GetTranscodedFile( + StreamState state, + bool isHeadRequest, + IStreamHelper streamHelper, + ControllerBase controller, + TranscodingJobHelper transcodingJobHelper, + string ffmpegCommandLineArguments, + HttpRequest request, + TranscodingJobType transcodingJobType, + CancellationTokenSource cancellationTokenSource) + { + IHeaderDictionary responseHeaders = controller.Response.Headers; + // Use the command line args with a dummy playlist path + var outputPath = state.OutputFilePath; + + responseHeaders[HeaderNames.AcceptRanges] = "none"; + + var contentType = state.GetMimeType(outputPath); + + // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response + // TODO (from api-migration): Investigate if this is still neccessary as we migrated away from ServiceStack + var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; + + if (contentLength.HasValue) + { + responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + } + else + { + responseHeaders.Remove(HeaderNames.ContentLength); + } + + // Headers only + if (isHeadRequest) + { + return controller.File(Array.Empty(), contentType); + } + + var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); + await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); + try + { + if (!File.Exists(outputPath)) + { + await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); + } + else + { + transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); + state.Dispose(); + } + + Stream stream = new MemoryStream(); + + await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(stream, CancellationToken.None).ConfigureAwait(false); + return controller.File(stream, contentType); + } + finally + { + transcodingLock.Release(); + } + } + + /// + /// Gets the length of the estimated content. + /// + /// The state. + /// System.Nullable{System.Int64}. + private static long? GetEstimatedContentLength(StreamState state) + { + var totalBitrate = state.TotalOutputBitrate ?? 0; + + if (totalBitrate > 0 && state.RunTimeTicks.HasValue) + { + return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); + } + + return null; + } + } +} -- cgit v1.2.3 From 07e56850beba99d3a5794a27280b96032880eb1e Mon Sep 17 00:00:00 2001 From: David Date: Wed, 22 Jul 2020 10:39:48 +0200 Subject: Remove caching and content length --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 91 +---------------------- 1 file changed, 2 insertions(+), 89 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index e03cafe35..6ba74d590 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -1,7 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -10,7 +8,6 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Helpers @@ -35,7 +32,6 @@ namespace Jellyfin.Api.Helpers CancellationTokenSource cancellationTokenSource) { HttpClient httpClient = new HttpClient(); - var responseHeaders = controller.Response.Headers; if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) { @@ -45,13 +41,7 @@ namespace Jellyfin.Api.Helpers var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.ToString(); - responseHeaders[HeaderNames.AcceptRanges] = "none"; - - // Seeing cases of -1 here - if (response.Content.Headers.ContentLength.HasValue && response.Content.Headers.ContentLength.Value >= 0) - { - responseHeaders[HeaderNames.ContentLength] = response.Content.Headers.ContentLength.Value.ToString(CultureInfo.InvariantCulture); - } + controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; if (isHeadRequest) { @@ -74,7 +64,6 @@ namespace Jellyfin.Api.Helpers /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. /// An the file. - // TODO: caching doesn't work public static ActionResult GetStaticFileResult( string path, string contentType, @@ -83,52 +72,7 @@ namespace Jellyfin.Api.Helpers bool isHeadRequest, ControllerBase controller) { - bool disableCaching = false; - if (controller.Request.Headers.TryGetValue(HeaderNames.CacheControl, out StringValues headerValue)) - { - disableCaching = headerValue.FirstOrDefault().Contains("no-cache", StringComparison.InvariantCulture); - } - - bool parsingSuccessful = DateTime.TryParseExact(controller.Request.Headers[HeaderNames.IfModifiedSince], "ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false), DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime ifModifiedSinceHeader); - - // if the parsing of the IfModifiedSince header was not successfull, disable caching - if (!parsingSuccessful) - { - disableCaching = true; - } - controller.Response.ContentType = contentType; - controller.Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateLastModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); - controller.Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept); - - if (disableCaching) - { - controller.Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); - controller.Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate"); - } - else - { - if (cacheDuration.HasValue) - { - controller.Response.Headers.Add(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds); - } - else - { - controller.Response.Headers.Add(HeaderNames.CacheControl, "public"); - } - - controller.Response.Headers.Add(HeaderNames.LastModified, dateLastModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); - - // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified - if (!(dateLastModified > ifModifiedSinceHeader)) - { - if (ifModifiedSinceHeader.Add(cacheDuration!.Value) < DateTime.UtcNow) - { - controller.Response.StatusCode = StatusCodes.Status304NotModified; - return new ContentResult(); - } - } - } // if the request is a head request, return a NoContent result with the same headers as it would with a GET request if (isHeadRequest) @@ -164,27 +108,13 @@ namespace Jellyfin.Api.Helpers TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource) { - IHeaderDictionary responseHeaders = controller.Response.Headers; // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; - responseHeaders[HeaderNames.AcceptRanges] = "none"; + controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; var contentType = state.GetMimeType(outputPath); - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - // TODO (from api-migration): Investigate if this is still neccessary as we migrated away from ServiceStack - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { - responseHeaders.Remove(HeaderNames.ContentLength); - } - // Headers only if (isHeadRequest) { @@ -215,22 +145,5 @@ namespace Jellyfin.Api.Helpers transcodingLock.Release(); } } - - /// - /// Gets the length of the estimated content. - /// - /// The state. - /// System.Nullable{System.Int64}. - private static long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } } } -- cgit v1.2.3 From 2ce97c022e9ceadea4b9b72053626eff7439ff91 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 22 Jul 2020 16:57:06 +0200 Subject: Move AudioService to Jellyfin.Api --- Jellyfin.Api/Controllers/AudioController.cs | 80 ++++++++--- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 17 +-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 158 +++++++++++---------- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 14 +- Jellyfin.Api/Models/StreamingDtos/StreamState.cs | 67 +++------ .../Models/StreamingDtos/StreamingRequestDto.cs | 45 ++++++ .../Models/StreamingDtos/VideoRequestDto.cs | 19 +++ 7 files changed, 236 insertions(+), 164 deletions(-) create mode 100644 Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs create mode 100644 Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 4d29d3880..81492ed4a 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Helpers; +using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -141,10 +142,10 @@ namespace Jellyfin.Api.Controllers /// Optional. The . /// Optional. The streaming options. /// A containing the audio file. - [HttpGet("{itemId}/stream.{container}")] - [HttpGet("{itemId}/stream")] - [HttpHead("{itemId}/stream.{container}")] + [HttpGet("{itemId}/{stream=stream}.{container?}")] [HttpGet("{itemId}/stream")] + [HttpHead("{itemId}/{stream=stream}.{container?}")] + [HttpHead("{itemId}/stream")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetAudioStream( [FromRoute] Guid itemId, @@ -201,21 +202,61 @@ namespace Jellyfin.Api.Controllers var cancellationTokenSource = new CancellationTokenSource(); + StreamingRequestDto streamingRequest = new StreamingRequestDto + { + Id = itemId, + Container = container, + Static = @static.HasValue ? @static.Value : true, + Params = @params, + Tag = tag, + DeviceProfileId = deviceProfileId, + PlaySessionId = playSessionId, + SegmentContainer = segmentContainer, + SegmentLength = segmentLength, + MinSegments = minSegments, + MediaSourceId = mediaSourceId, + DeviceId = deviceId, + AudioCodec = audioCodec, + EnableAutoStreamCopy = enableAutoStreamCopy.HasValue ? enableAutoStreamCopy.Value : true, + AllowAudioStreamCopy = allowAudioStreamCopy.HasValue ? allowAudioStreamCopy.Value : true, + AllowVideoStreamCopy = allowVideoStreamCopy.HasValue ? allowVideoStreamCopy.Value : true, + BreakOnNonKeyFrames = breakOnNonKeyFrames.HasValue ? breakOnNonKeyFrames.Value : false, + AudioSampleRate = audioSampleRate, + MaxAudioChannels = maxAudioChannels, + AudioBitRate = audioBitRate, + MaxAudioBitDepth = maxAudioBitDepth, + AudioChannels = audioChannels, + Profile = profile, + Level = level, + Framerate = framerate, + MaxFramerate = maxFramerate, + CopyTimestamps = copyTimestamps.HasValue ? copyTimestamps.Value : true, + StartTimeTicks = startTimeTicks, + Width = width, + Height = height, + VideoBitRate = videoBitRate, + SubtitleStreamIndex = subtitleStreamIndex, + SubtitleMethod = subtitleMethod, + MaxRefFrames = maxRefFrames, + MaxVideoBitDepth = maxVideoBitDepth, + RequireAvc = requireAvc.HasValue ? requireAvc.Value : true, + DeInterlace = deInterlace.HasValue ? deInterlace.Value : true, + RequireNonAnamorphic = requireNonAnamorphic.HasValue ? requireNonAnamorphic.Value : true, + TranscodingMaxAudioChannels = transcodingMaxAudioChannels, + CpuCoreLimit = cpuCoreLimit, + LiveStreamId = liveStreamId, + EnableMpegtsM2TsMode = enableMpegtsM2TsMode.HasValue ? enableMpegtsM2TsMode.Value : true, + VideoCodec = videoCodec, + SubtitleCodec = subtitleCodec, + TranscodeReasons = transcodingReasons, + AudioStreamIndex = audioStreamIndex, + VideoStreamIndex = videoStreamIndex, + Context = context, + StreamOptions = streamOptions + }; + var state = await StreamingHelpers.GetStreamingState( - itemId, - startTimeTicks, - audioCodec, - subtitleCodec, - videoCodec, - @params, - @static, - container, - liveStreamId, - playSessionId, - mediaSourceId, - deviceId, - deviceProfileId, - audioBitRate, + streamingRequest, Request, _authContext, _mediaSourceManager, @@ -230,7 +271,6 @@ namespace Jellyfin.Api.Controllers _deviceManager, _transcodingJobHelper, _transcodingJobType, - false, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -255,7 +295,7 @@ namespace Jellyfin.Api.Controllers using (state) { - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, cancellationTokenSource).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this).ConfigureAwait(false); } } @@ -297,8 +337,6 @@ namespace Jellyfin.Api.Controllers return FileStreamResponseHelpers.GetStaticFileResult( state.MediaPath, contentType, - _fileSystem.GetLastWriteTimeUtc(state.MediaPath), - cacheDuration, isHeadRequest, this); } diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6ba74d590..9f16b5323 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -23,13 +23,11 @@ namespace Jellyfin.Api.Helpers /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. - /// The . /// A containing the API response. public static async Task GetStaticRemoteStreamResult( StreamState state, bool isHeadRequest, - ControllerBase controller, - CancellationTokenSource cancellationTokenSource) + ControllerBase controller) { HttpClient httpClient = new HttpClient(); @@ -59,16 +57,12 @@ namespace Jellyfin.Api.Helpers /// /// The path to the file. /// The content type of the file. - /// The of the last modification of the file. - /// The cache duration of the file. /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. /// An the file. public static ActionResult GetStaticFileResult( string path, string contentType, - DateTime dateLastModified, - TimeSpan? cacheDuration, bool isHeadRequest, ControllerBase controller) { @@ -135,10 +129,11 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - Stream stream = new MemoryStream(); - - await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(stream, CancellationToken.None).ConfigureAwait(false); - return controller.File(stream, contentType); + using (var memoryStream = new MemoryStream()) + { + await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + return controller.File(memoryStream, contentType); + } } finally { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index ee1f1efce..71bf053f5 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -30,22 +30,29 @@ namespace Jellyfin.Api.Helpers /// public static class StreamingHelpers { + /// + /// Gets the current streaming state. + /// + /// The . + /// The . + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Initialized . + /// The . + /// The . + /// A containing the current . public static async Task GetStreamingState( - Guid itemId, - long? startTimeTicks, - string? audioCodec, - string? subtitleCodec, - string? videoCodec, - string? @params, - bool? @static, - string? container, - string? liveStreamId, - string? playSessionId, - string? mediaSourceId, - string? deviceId, - string? deviceProfileId, - int? audioBitRate, - HttpRequest request, + StreamingRequestDto streamingRequest, + HttpRequest httpRequest, IAuthorizationContext authorizationContext, IMediaSourceManager mediaSourceManager, IUserManager userManager, @@ -59,49 +66,43 @@ namespace Jellyfin.Api.Helpers IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, TranscodingJobType transcodingJobType, - bool isVideoRequest, CancellationToken cancellationToken) { EncodingHelper encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration); // Parse the DLNA time seek header - if (!startTimeTicks.HasValue) + if (!streamingRequest.StartTimeTicks.HasValue) { - var timeSeek = request.Headers["TimeSeekRange.dlna.org"]; + var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"]; - startTimeTicks = ParseTimeSeekHeader(timeSeek); + streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek); } - if (!string.IsNullOrWhiteSpace(@params)) + if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) { - // What is this? - ParseParams(request); + ParseParams(streamingRequest); } - var streamOptions = ParseStreamOptions(request.Query); + streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query); - var url = request.Path.Value.Split('.').Last(); + var url = httpRequest.Path.Value.Split('.').Last(); - if (string.IsNullOrEmpty(audioCodec)) + if (string.IsNullOrEmpty(streamingRequest.AudioCodec)) { - audioCodec = encodingHelper.InferAudioCodec(url); + streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url); } - var enableDlnaHeaders = !string.IsNullOrWhiteSpace(@params) || - string.Equals(request.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); + var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) || + string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase); var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper) { - // TODO request was the StreamingRequest living in MediaBrowser.Api.Playback.Progressive - // Request = request, - DeviceId = deviceId, - PlaySessionId = playSessionId, - LiveStreamId = liveStreamId, + Request = streamingRequest, RequestedUrl = url, - UserAgent = request.Headers[HeaderNames.UserAgent], + UserAgent = httpRequest.Headers[HeaderNames.UserAgent], EnableDlnaHeaders = enableDlnaHeaders }; - var auth = authorizationContext.GetAuthorizationInfo(request); + var auth = authorizationContext.GetAuthorizationInfo(httpRequest); if (!auth.UserId.Equals(Guid.Empty)) { state.User = userManager.GetUserById(auth.UserId); @@ -116,27 +117,27 @@ namespace Jellyfin.Api.Helpers } */ - if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.VideoCodec)) + if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec)) { - state.SupportedVideoCodecs = state.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); + state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); } - if (!string.IsNullOrWhiteSpace(audioCodec)) + if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) { - state.SupportedAudioCodecs = audioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) + state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) ?? state.SupportedAudioCodecs.FirstOrDefault(); } - if (!string.IsNullOrWhiteSpace(subtitleCodec)) + if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) { - state.SupportedSubtitleCodecs = subtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) + state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } - var item = libraryManager.GetItemById(itemId); + var item = libraryManager.GetItemById(streamingRequest.Id); state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); @@ -150,10 +151,10 @@ namespace Jellyfin.Api.Helpers */ MediaSourceInfo? mediaSource = null; - if (string.IsNullOrWhiteSpace(liveStreamId)) + if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) { - var currentJob = !string.IsNullOrWhiteSpace(playSessionId) - ? transcodingJobHelper.GetTranscodingJob(playSessionId) + var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId) + ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId) : null; if (currentJob != null) @@ -163,13 +164,13 @@ namespace Jellyfin.Api.Helpers if (mediaSource == null) { - var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(itemId), null, false, false, cancellationToken).ConfigureAwait(false); + var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false); - mediaSource = string.IsNullOrEmpty(mediaSourceId) + mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) ? mediaSources[0] - : mediaSources.Find(i => string.Equals(i.Id, mediaSourceId, StringComparison.InvariantCulture)); + : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.InvariantCulture)); - if (mediaSource == null && Guid.Parse(mediaSourceId) == itemId) + if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id) { mediaSource = mediaSources[0]; } @@ -177,7 +178,7 @@ namespace Jellyfin.Api.Helpers } else { - var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(liveStreamId, cancellationToken).ConfigureAwait(false); + var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); mediaSource = liveStreamInfo.Item1; state.DirectStreamProvider = liveStreamInfo.Item2; } @@ -186,28 +187,28 @@ namespace Jellyfin.Api.Helpers var containerInternal = Path.GetExtension(state.RequestedUrl); - if (string.IsNullOrEmpty(container)) + if (string.IsNullOrEmpty(streamingRequest.Container)) { - containerInternal = container; + containerInternal = streamingRequest.Container; } if (string.IsNullOrEmpty(containerInternal)) { - containerInternal = (@static.HasValue && @static.Value) ? StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : GetOutputFileExtension(state); + containerInternal = (streamingRequest.Static && streamingRequest.Static) ? StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : GetOutputFileExtension(state); } state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(audioBitRate, state.AudioStream); + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, state.AudioStream); - state.OutputAudioCodec = audioCodec; + state.OutputAudioCodec = streamingRequest.AudioCodec; state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); - if (isVideoRequest) + if (state.VideoRequest != null) { - state.OutputVideoCodec = state.VideoCodec; - state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); + state.OutputVideoCodec = state.Request.VideoCodec; + state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); encodingHelper.TryStreamCopy(state); @@ -220,21 +221,21 @@ namespace Jellyfin.Api.Helpers state.OutputVideoBitrate.Value, state.VideoStream?.Codec, state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); + state.VideoRequest.MaxWidth, + state.VideoRequest.MaxHeight); - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; + state.VideoRequest.MaxWidth = resolution.MaxWidth; + state.VideoRequest.MaxHeight = resolution.MaxHeight; } } - ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, request, deviceProfileId, @static); + 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, deviceId, playSessionId); + state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); return state; } @@ -319,7 +320,7 @@ namespace Jellyfin.Api.Helpers /// /// The time seek header string. /// A nullable representing the seek time in ticks. - public static long? ParseTimeSeekHeader(string value) + private static long? ParseTimeSeekHeader(string value) { if (string.IsNullOrWhiteSpace(value)) { @@ -375,7 +376,7 @@ namespace Jellyfin.Api.Helpers /// /// The query string. /// A containing the stream options. - public static Dictionary ParseStreamOptions(IQueryCollection queryString) + private static Dictionary ParseStreamOptions(IQueryCollection queryString) { Dictionary streamOptions = new Dictionary(); foreach (var param in queryString) @@ -398,7 +399,7 @@ namespace Jellyfin.Api.Helpers /// The current . /// The of the response. /// The start time in ticks. - public static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks) + 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); @@ -420,7 +421,7 @@ namespace Jellyfin.Api.Helpers /// /// The state. /// System.String. - public static string? GetOutputFileExtension(StreamState state) + private static string? GetOutputFileExtension(StreamState state) { var ext = Path.GetExtension(state.RequestedUrl); @@ -432,7 +433,7 @@ namespace Jellyfin.Api.Helpers // Try to infer based on the desired video codec if (state.IsVideoRequest) { - var videoCodec = state.VideoCodec; + var videoCodec = state.Request.VideoCodec; if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)) @@ -459,7 +460,7 @@ namespace Jellyfin.Api.Helpers // Try to infer based on the desired audio codec if (!state.IsVideoRequest) { - var audioCodec = state.AudioCodec; + var audioCodec = state.Request.AudioCodec; if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) { @@ -570,7 +571,7 @@ namespace Jellyfin.Api.Helpers // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - if (!state.IsVideoRequest) + if (state.VideoRequest != null) { state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; @@ -583,11 +584,16 @@ namespace Jellyfin.Api.Helpers /// Parses the parameters. /// /// The request. - private void ParseParams(StreamRequest request) + private static void ParseParams(StreamingRequestDto request) { + if (string.IsNullOrEmpty(request.Params)) + { + return; + } + var vals = request.Params.Split(';'); - var videoRequest = request as VideoStreamRequest; + var videoRequest = request as VideoRequestDto; for (var i = 0; i < vals.Length; i++) { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 4605c0183..c84135085 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -443,7 +443,7 @@ namespace Jellyfin.Api.Helpers job.BitRate = bitRate; } - var deviceId = state.DeviceId; + var deviceId = state.Request.DeviceId; if (!string.IsNullOrWhiteSpace(deviceId)) { @@ -486,7 +486,7 @@ namespace Jellyfin.Api.Helpers HttpRequest request, TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource, - string workingDirectory = null) + string? workingDirectory = null) { Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); @@ -525,12 +525,12 @@ namespace Jellyfin.Api.Helpers var transcodingJob = this.OnTranscodeBeginning( outputPath, - state.PlaySessionId, + state.Request.PlaySessionId, state.MediaSource.LiveStreamId, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), transcodingJobType, process, - state.DeviceId, + state.Request.DeviceId, state, cancellationTokenSource); @@ -706,9 +706,9 @@ namespace Jellyfin.Api.Helpers _transcodingLocks.Remove(path); } - if (!string.IsNullOrWhiteSpace(state.DeviceId)) + if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) { - _sessionManager.ClearTranscodingInfo(state.DeviceId); + _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); } } @@ -747,7 +747,7 @@ namespace Jellyfin.Api.Helpers state.IsoMount = await _isoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); } - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.LiveStreamId)) + if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) { var liveStreamResponse = await _mediaSourceManager.OpenLiveStream( new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken }, diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index db7cc6a75..70a13d745 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Api.Models.StreamingDtos /// /// Initializes a new instance of the class. /// - /// Instance of the interface. + /// Instance of the interface. /// The . /// The singleton. public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper) @@ -34,29 +34,28 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public string? RequestedUrl { get; set; } - // /// - // /// Gets or sets the request. - // /// - // public StreamRequest Request - // { - // get => (StreamRequest)BaseRequest; - // set - // { - // BaseRequest = value; - // - // IsVideoRequest = VideoRequest != null; - // } - // } + /// + /// Gets or sets the request. + /// + public StreamingRequestDto Request + { + get => (StreamingRequestDto)BaseRequest; + set + { + BaseRequest = value; + IsVideoRequest = VideoRequest != null; + } + } /// /// Gets or sets the transcoding throttler. /// public TranscodingThrottler? TranscodingThrottler { get; set; } - /*/// + /// /// Gets the video request. /// - public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;*/ + public VideoRequestDto? VideoRequest => Request! as VideoRequestDto; /// /// Gets or sets the direct stream provicer. @@ -68,10 +67,10 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public string? WaitForPath { get; set; } - /*/// + /// /// Gets a value indicating whether the request outputs video. /// - public bool IsOutputVideo => Request is VideoStreamRequest;*/ + public bool IsOutputVideo => Request is VideoRequestDto; /// /// Gets the segment length. @@ -161,36 +160,6 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public TranscodingJobDto? TranscodingJob { get; set; } - /// - /// Gets or sets the device id. - /// - public string? DeviceId { get; set; } - - /// - /// Gets or sets the play session id. - /// - public string? PlaySessionId { get; set; } - - /// - /// Gets or sets the live stream id. - /// - public string? LiveStreamId { get; set; } - - /// - /// Gets or sets the video coded. - /// - public string? VideoCodec { get; set; } - - /// - /// Gets or sets the audio codec. - /// - public string? AudioCodec { get; set; } - - /// - /// Gets or sets the subtitle codec. - /// - public string? SubtitleCodec { get; set; } - /// public void Dispose() { @@ -219,7 +188,7 @@ namespace Jellyfin.Api.Models.StreamingDtos { // REVIEW: Is this the right place for this? if (MediaSource.RequiresClosing - && string.IsNullOrWhiteSpace(LiveStreamId) + && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) { _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult(); diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs new file mode 100644 index 000000000..1791b0370 --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Controller.MediaEncoding; + +namespace Jellyfin.Api.Models.StreamingDtos +{ + /// + /// The audio streaming request dto. + /// + public class StreamingRequestDto : BaseEncodingJobOptions + { + /// + /// Gets or sets the device profile. + /// + public string? DeviceProfileId { get; set; } + + /// + /// Gets or sets the params. + /// + public string? Params { get; set; } + + /// + /// Gets or sets the play session id. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the tag. + /// + public string? Tag { get; set; } + + /// + /// Gets or sets the segment container. + /// + public string? SegmentContainer { get; set; } + + /// + /// Gets or sets the segment length. + /// + public int? SegmentLength { get; set; } + + /// + /// Gets or sets the min segments. + /// + public int? MinSegments { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs new file mode 100644 index 000000000..cce2a89d4 --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs @@ -0,0 +1,19 @@ +namespace Jellyfin.Api.Models.StreamingDtos +{ + /// + /// The video request dto. + /// + public class VideoRequestDto : StreamingRequestDto + { + /// + /// Gets a value indicating whether this instance has fixed resolution. + /// + /// true if this instance has fixed resolution; otherwise, false. + public bool HasFixedResolution => Width.HasValue || Height.HasValue; + + /// + /// Gets or sets a value indicating whether to enable subtitles in the manifest. + /// + public bool EnableSubtitlesInManifest { get; set; } + } +} -- cgit v1.2.3 From d39f481a5c723dcbd97a578dc8f390e7d0b4e984 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 23 Jul 2020 12:46:54 +0200 Subject: Apply suggestions from review --- Jellyfin.Api/Controllers/AudioController.cs | 16 +++++++--------- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 6 +++--- Jellyfin.Api/Models/StreamingDtos/StreamState.cs | 5 ----- 3 files changed, 10 insertions(+), 17 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 81492ed4a..d8c67cc24 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Helpers; @@ -40,6 +41,7 @@ namespace Jellyfin.Api.Controllers private readonly IConfiguration _configuration; private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; + private readonly HttpClient _httpClient; private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive; @@ -59,6 +61,7 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// The singleton. + /// Instance of the . public AudioController( IDlnaManager dlnaManager, IUserManager userManger, @@ -72,7 +75,8 @@ namespace Jellyfin.Api.Controllers ISubtitleEncoder subtitleEncoder, IConfiguration configuration, IDeviceManager deviceManager, - TranscodingJobHelper transcodingJobHelper) + TranscodingJobHelper transcodingJobHelper, + HttpClient httpClient) { _dlnaManager = dlnaManager; _authContext = authorizationContext; @@ -87,6 +91,7 @@ namespace Jellyfin.Api.Controllers _configuration = configuration; _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; + _httpClient = httpClient; } /// @@ -295,7 +300,7 @@ namespace Jellyfin.Api.Controllers using (state) { - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, _httpClient).ConfigureAwait(false); } } @@ -327,13 +332,6 @@ namespace Jellyfin.Api.Controllers return File(Response.Body, contentType); } - TimeSpan? cacheDuration = null; - - if (!string.IsNullOrEmpty(tag)) - { - cacheDuration = TimeSpan.FromDays(365); - } - return FileStreamResponseHelpers.GetStaticFileResult( state.MediaPath, contentType, diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 9f16b5323..ddca2f1ae 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -23,14 +23,14 @@ namespace Jellyfin.Api.Helpers /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. + /// The making the remote request. /// A containing the API response. public static async Task GetStaticRemoteStreamResult( StreamState state, bool isHeadRequest, - ControllerBase controller) + ControllerBase controller, + HttpClient httpClient) { - HttpClient httpClient = new HttpClient(); - if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) { httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent); diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index 70a13d745..df5e21dac 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -94,11 +94,6 @@ namespace Jellyfin.Api.Models.StreamingDtos userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) { - if (IsSegmentedLiveStream) - { - return 6; - } - return 6; } -- cgit v1.2.3 From ca3dcc3db03d531457b4b60cc3ecdebd57a0157e Mon Sep 17 00:00:00 2001 From: David Date: Fri, 24 Jul 2020 19:14:53 +0200 Subject: Fix suggestions from review --- Jellyfin.Api/Controllers/AudioController.cs | 107 ++++++---------------- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 17 ++-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 68 ++++---------- Jellyfin.Api/Models/StreamingDtos/StreamState.cs | 10 +- 4 files changed, 58 insertions(+), 144 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index d8c67cc24..7405c26fb 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers StreamOptions = streamOptions }; - var state = await StreamingHelpers.GetStreamingState( + using var state = await StreamingHelpers.GetStreamingState( streamingRequest, Request, _authContext, @@ -283,14 +283,11 @@ namespace Jellyfin.Api.Controllers { StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); - using (state) - { - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); + // TODO AllowEndOfFile = false + await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); - // TODO (moved from MediaBrowser.Api): Don't hardcode contentType - return File(Response.Body, MimeTypes.GetMimeType("file.ts")!); - } + // TODO (moved from MediaBrowser.Api): Don't hardcode contentType + return File(Response.Body, MimeTypes.GetMimeType("file.ts")!); } // Static remote stream @@ -298,10 +295,7 @@ namespace Jellyfin.Api.Controllers { StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); - using (state) - { - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, _httpClient).ConfigureAwait(false); - } + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, _httpClient).ConfigureAwait(false); } if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File) @@ -322,80 +316,35 @@ namespace Jellyfin.Api.Controllers { var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath); - using (state) + if (state.MediaSource.IsInfiniteStream) { - if (state.MediaSource.IsInfiniteStream) - { - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); - - return File(Response.Body, contentType); - } + // TODO AllowEndOfFile = false + await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); - return FileStreamResponseHelpers.GetStaticFileResult( - state.MediaPath, - contentType, - isHeadRequest, - this); + return File(Response.Body, contentType); } - } - /* - // Not static but transcode cache file exists - if (isTranscodeCached && state.VideoRequest == null) - { - var contentType = state.GetMimeType(outputPath) - try - { - if (transcodingJob != null) - { - ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob); - } - return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - ResponseHeaders = responseHeaders, - ContentType = contentType, - IsHeadRequest = isHeadRequest, - Path = outputPath, - FileShare = FileShare.ReadWrite, - OnComplete = () => - { - if (transcodingJob != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - } - }).ConfigureAwait(false); - } - finally - { - state.Dispose(); - } - } - */ - - // Need to start ffmpeg (because media can't be returned directly) - try - { - var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); - var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath); - return await FileStreamResponseHelpers.GetTranscodedFile( - state, + return FileStreamResponseHelpers.GetStaticFileResult( + state.MediaPath, + contentType, isHeadRequest, - _streamHelper, - this, - _transcodingJobHelper, - ffmpegCommandLineArguments, - Request, - _transcodingJobType, - cancellationTokenSource).ConfigureAwait(false); + this); } - catch - { - state.Dispose(); - throw; - } + // 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, + _streamHelper, + this, + _transcodingJobHelper, + ffmpegCommandLineArguments, + Request, + _transcodingJobType, + cancellationTokenSource).ConfigureAwait(false); } } } diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index ddca2f1ae..636f47f5f 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -36,17 +36,14 @@ namespace Jellyfin.Api.Helpers httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent); } - var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); + using var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.ToString(); controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; if (isHeadRequest) { - using (response) - { - return controller.File(Array.Empty(), contentType); - } + return controller.File(Array.Empty(), contentType); } return controller.File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType); @@ -74,7 +71,7 @@ namespace Jellyfin.Api.Helpers return controller.NoContent(); } - var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); return controller.File(stream, contentType); } @@ -129,11 +126,9 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - using (var memoryStream = new MemoryStream()) - { - await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); - return controller.File(memoryStream, contentType); - } + await using var memoryStream = new MemoryStream(); + await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + return controller.File(memoryStream, contentType); } finally { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 0b18756d6..b12590080 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -74,7 +74,7 @@ namespace Jellyfin.Api.Helpers { var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"]; - streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek); + streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString()); } if (!string.IsNullOrWhiteSpace(streamingRequest.Params)) @@ -108,31 +108,22 @@ namespace Jellyfin.Api.Helpers state.User = userManager.GetUserById(auth.UserId); } - /* - if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) - { - state.SegmentLength = 6; - } - */ - if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec)) { - state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) { - state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) ?? state.SupportedAudioCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) { - state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } @@ -141,15 +132,6 @@ namespace Jellyfin.Api.Helpers state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - /* - var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - if (primaryImage != null) - { - state.AlbumCoverPath = primaryImage.Path; - } - */ - MediaSourceInfo? mediaSource = null; if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId)) { @@ -322,25 +304,24 @@ namespace Jellyfin.Api.Helpers /// /// The time seek header string. /// A nullable representing the seek time in ticks. - private static long? ParseTimeSeekHeader(string value) + private static long? ParseTimeSeekHeader(ReadOnlySpan value) { - if (string.IsNullOrWhiteSpace(value)) + if (value.IsEmpty) { return null; } - const string Npt = "npt="; - if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase)) + const string npt = "npt="; + if (!value.StartsWith(npt, StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Invalid timeseek header"); } - int index = value.IndexOf('-', StringComparison.InvariantCulture); + var index = value.IndexOf('-'); value = index == -1 - ? value.Substring(Npt.Length) - : value.Substring(Npt.Length, index - Npt.Length); - - if (value.IndexOf(':', StringComparison.InvariantCulture) == -1) + ? value.Slice(npt.Length) + : value.Slice(npt.Length, index - npt.Length); + if (value.IndexOf(':') == -1) { // Parses npt times in the format of '417.33' if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds)) @@ -351,26 +332,15 @@ namespace Jellyfin.Api.Helpers throw new ArgumentException("Invalid timeseek header"); } - // Parses npt times in the format of '10:19:25.7' - var tokens = value.Split(new[] { ':' }, 3); - double secondsSum = 0; - var timeFactor = 3600; - - foreach (var time in tokens) + try { - if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit)) - { - secondsSum += digit * timeFactor; - } - else - { - throw new ArgumentException("Invalid timeseek header"); - } - - timeFactor /= 60; + // Parses npt times in the format of '10:19:25.7' + return TimeSpan.Parse(value).Ticks; + } + catch + { + throw new ArgumentException("Invalid timeseek header"); } - - return TimeSpan.FromSeconds(secondsSum).Ticks; } /// diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index df5e21dac..e95f2d1f4 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -88,11 +88,11 @@ namespace Jellyfin.Api.Models.StreamingDtos { var userAgent = UserAgent ?? string.Empty; - if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) + if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 + || userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 + || userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 + || userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 + || userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) { return 6; } -- cgit v1.2.3 From b8d327889b96b820249ddf80ee023b189f67f4a3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 27 Jul 2020 13:42:40 -0600 Subject: Add missing functions --- Jellyfin.Api/Controllers/AudioController.cs | 19 +-- Jellyfin.Api/Controllers/LiveTvController.cs | 19 +-- Jellyfin.Api/Controllers/VideosController.cs | 21 +-- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 14 +- Jellyfin.Api/Helpers/ProgressiveFileCopier.cs | 162 ++++++++++++++++++---- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 14 ++ 6 files changed, 187 insertions(+), 62 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 86577411f..e63868339 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -35,7 +35,6 @@ namespace Jellyfin.Api.Controllers private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IStreamHelper _streamHelper; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; private readonly IConfiguration _configuration; @@ -55,7 +54,6 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -70,7 +68,6 @@ namespace Jellyfin.Api.Controllers IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - IStreamHelper streamHelper, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder, IConfiguration configuration, @@ -85,7 +82,6 @@ namespace Jellyfin.Api.Controllers _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _streamHelper = streamHelper; _fileSystem = fileSystem; _subtitleEncoder = subtitleEncoder; _configuration = configuration; @@ -283,8 +279,11 @@ namespace Jellyfin.Api.Controllers { StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); + 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")!); @@ -319,8 +318,11 @@ namespace Jellyfin.Api.Controllers if (state.MediaSource.IsInfiniteStream) { - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); + await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None) + { + AllowEndOfFile = false + }.WriteToAsync(Response.Body, CancellationToken.None) + .ConfigureAwait(false); return File(Response.Body, contentType); } @@ -339,7 +341,6 @@ namespace Jellyfin.Api.Controllers return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, - _streamHelper, this, _transcodingJobHelper, ffmpegCommandLineArguments, diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index bc5446510..9144d6f28 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -24,7 +24,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; @@ -45,9 +44,9 @@ namespace Jellyfin.Api.Controllers private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; private readonly ISessionContext _sessionContext; - private readonly IStreamHelper _streamHelper; private readonly IMediaSourceManager _mediaSourceManager; private readonly IConfigurationManager _configurationManager; + private readonly TranscodingJobHelper _transcodingJobHelper; /// /// Initializes a new instance of the class. @@ -58,9 +57,9 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. + /// Instance of the class. public LiveTvController( ILiveTvManager liveTvManager, IUserManager userManager, @@ -68,9 +67,9 @@ namespace Jellyfin.Api.Controllers ILibraryManager libraryManager, IDtoService dtoService, ISessionContext sessionContext, - IStreamHelper streamHelper, IMediaSourceManager mediaSourceManager, - IConfigurationManager configurationManager) + IConfigurationManager configurationManager, + TranscodingJobHelper transcodingJobHelper) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -78,9 +77,9 @@ namespace Jellyfin.Api.Controllers _libraryManager = libraryManager; _dtoService = dtoService; _sessionContext = sessionContext; - _streamHelper = streamHelper; _mediaSourceManager = mediaSourceManager; _configurationManager = configurationManager; + _transcodingJobHelper = transcodingJobHelper; } /// @@ -1187,7 +1186,9 @@ namespace Jellyfin.Api.Controllers } await using var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(_streamHelper, path).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None) + .WriteToAsync(memoryStream, CancellationToken.None) + .ConfigureAwait(false); return File(memoryStream, MimeTypes.GetMimeType(path)); } @@ -1214,7 +1215,9 @@ namespace Jellyfin.Api.Controllers } await using var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(_streamHelper, liveStreamInfo).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + await new ProgressiveFileCopier(liveStreamInfo, null, _transcodingJobHelper, CancellationToken.None) + .WriteToAsync(memoryStream, CancellationToken.None) + .ConfigureAwait(false); return File(memoryStream, MimeTypes.GetMimeType("file." + container)); } diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 5050c3d4f..0ce62186b 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -46,7 +46,6 @@ namespace Jellyfin.Api.Controllers private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IStreamHelper _streamHelper; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; private readonly IConfiguration _configuration; @@ -67,7 +66,6 @@ namespace Jellyfin.Api.Controllers /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. @@ -83,7 +81,6 @@ namespace Jellyfin.Api.Controllers IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, - IStreamHelper streamHelper, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder, IConfiguration configuration, @@ -99,7 +96,6 @@ namespace Jellyfin.Api.Controllers _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; - _streamHelper = streamHelper; _fileSystem = fileSystem; _subtitleEncoder = subtitleEncoder; _configuration = configuration; @@ -376,7 +372,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Dictionary streamOptions) { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; - using var cancellationTokenSource = new CancellationTokenSource(); + var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto { Id = itemId, @@ -453,8 +449,11 @@ namespace Jellyfin.Api.Controllers { StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); + 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")!); @@ -489,8 +488,11 @@ namespace Jellyfin.Api.Controllers if (state.MediaSource.IsInfiniteStream) { - // TODO AllowEndOfFile = false - await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false); + await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None) + { + AllowEndOfFile = false + }.WriteToAsync(Response.Body, CancellationToken.None) + .ConfigureAwait(false); return File(Response.Body, contentType); } @@ -509,7 +511,6 @@ namespace Jellyfin.Api.Controllers return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, - _streamHelper, this, _transcodingJobHelper, ffmpegCommandLineArguments, diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 636f47f5f..96e90d38f 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -3,9 +3,9 @@ using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; @@ -80,7 +80,6 @@ namespace Jellyfin.Api.Helpers /// /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. - /// Instance of the interface. /// The managing the response. /// The singleton. /// The command line arguments to start ffmpeg. @@ -91,7 +90,6 @@ namespace Jellyfin.Api.Helpers public static async Task GetTranscodedFile( StreamState state, bool isHeadRequest, - IStreamHelper streamHelper, ControllerBase controller, TranscodingJobHelper transcodingJobHelper, string ffmpegCommandLineArguments, @@ -116,18 +114,20 @@ namespace Jellyfin.Api.Helpers await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { + TranscodingJobDto? job; if (!File.Exists(outputPath)) { - await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); + job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); } else { - transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); + job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); state.Dispose(); } - await using var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + var memoryStream = new MemoryStream(); + await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + memoryStream.Position = 0; return controller.File(memoryStream, contentType); } finally diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index e8e6966f4..acaccc77a 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Api.Models.PlaybackDtos; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; @@ -12,34 +13,53 @@ namespace Jellyfin.Api.Helpers /// public class ProgressiveFileCopier { + private readonly TranscodingJobDto? _job; private readonly string? _path; + private readonly CancellationToken _cancellationToken; private readonly IDirectStreamProvider? _directStreamProvider; - private readonly IStreamHelper _streamHelper; + private readonly TranscodingJobHelper _transcodingJobHelper; + private long _bytesWritten; /// /// Initializes a new instance of the class. /// - /// Instance of the interface. - /// Filepath to stream from. - public ProgressiveFileCopier(IStreamHelper streamHelper, string path) + /// The path to copy from. + /// The transcoding job. + /// Instance of the . + /// The cancellation token. + public ProgressiveFileCopier(string path, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken) { _path = path; - _streamHelper = streamHelper; - _directStreamProvider = null; + _job = job; + _cancellationToken = cancellationToken; + _transcodingJobHelper = transcodingJobHelper; } /// /// Initializes a new instance of the class. /// - /// Instance of the interface. /// Instance of the interface. - public ProgressiveFileCopier(IStreamHelper streamHelper, IDirectStreamProvider directStreamProvider) + /// The transcoding job. + /// Instance of the . + /// The cancellation token. + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; - _streamHelper = streamHelper; - _path = null; + _job = job; + _cancellationToken = cancellationToken; + _transcodingJobHelper = transcodingJobHelper; } + /// + /// Gets or sets a value indicating whether allow read end of file. + /// + public bool AllowEndOfFile { get; set; } = true; + + /// + /// Gets or sets copy start position. + /// + public long StartPosition { get; set; } + /// /// Write source stream to output. /// @@ -48,37 +68,123 @@ namespace Jellyfin.Api.Helpers /// A . public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { - if (_directStreamProvider != null) + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; + + try { - await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } + if (_directStreamProvider != null) + { + await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); + return; + } + + var fileOptions = FileOptions.SequentialScan; + var allowAsyncFileRead = false; + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + fileOptions |= FileOptions.Asynchronous; + allowAsyncFileRead = true; + } + + await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); + + var eofCount = 0; + const int emptyReadLimit = 20; + if (StartPosition > 0) + { + inputStream.Position = StartPosition; + } + + while (eofCount < emptyReadLimit || !AllowEndOfFile) + { + int bytesRead; + if (allowAsyncFileRead) + { + bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); + } + else + { + bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); + } - var fileOptions = FileOptions.SequentialScan; + if (bytesRead == 0) + { + if (_job == null || _job.HasExited) + { + eofCount++; + } - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - if (Environment.OSVersion.Platform != PlatformID.Win32NT) + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + } + } + } + finally { - fileOptions |= FileOptions.Asynchronous; + if (_job != null) + { + _transcodingJobHelper.OnTranscodeEndRequest(_job); + } } + } - await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions); - const int emptyReadLimit = 100; - var eofCount = 0; - while (eofCount < emptyReadLimit) + private async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) + { + var array = new byte[IODefaults.CopyToBufferSize]; + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = source.Read(array, 0, array.Length)) != 0) { - var bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); + var bytesToWrite = bytesRead; - if (bytesRead == 0) + if (bytesToWrite > 0) { - eofCount++; - await Task.Delay(100, cancellationToken).ConfigureAwait(false); + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + _bytesWritten += bytesRead; + totalBytesRead += bytesRead; + + if (_job != null) + { + _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); + } } - else + } + + return totalBytesRead; + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken) + { + var array = new byte[IODefaults.CopyToBufferSize]; + int bytesRead; + int totalBytesRead = 0; + + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + var bytesToWrite = bytesRead; + + if (bytesToWrite > 0) { - eofCount = 0; + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + _bytesWritten += bytesRead; + totalBytesRead += bytesRead; + + if (_job != null) + { + _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); + } } } + + return totalBytesRead; } } } diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index c84135085..fc38eacaf 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -680,6 +680,20 @@ namespace Jellyfin.Api.Helpers } } + /// + /// Called when [transcode end]. + /// + /// The transcode job. + public void OnTranscodeEndRequest(TranscodingJobDto job) + { + job.ActiveRequestCount--; + _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount); + if (job.ActiveRequestCount <= 0) + { + PingTimer(job, false); + } + } + /// /// /// The progressive -- cgit v1.2.3 From f543a17d1b8a4e1e2f420831de586e9ecc015166 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 30 Jul 2020 06:29:06 -0600 Subject: Apply review fixes --- Jellyfin.Api/Controllers/VideosController.cs | 2 +- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index 0ce62186b..fb02ec961 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -373,7 +373,7 @@ namespace Jellyfin.Api.Controllers { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; var cancellationTokenSource = new CancellationTokenSource(); - var streamingRequest = new StreamingRequestDto + var streamingRequest = new VideoRequestDto { Id = itemId, Container = container, diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 96e90d38f..a463783e0 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -71,8 +71,7 @@ namespace Jellyfin.Api.Helpers return controller.NoContent(); } - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - return controller.File(stream, contentType); + return controller.PhysicalFile(path, contentType); } /// -- cgit v1.2.3 From c5e9cf15f6476d96eadc1d32e38687c3a5a98a17 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 10 Aug 2020 07:53:32 -0600 Subject: Use proper IHttpContextAccessor --- Jellyfin.Api/Controllers/AudioController.cs | 2 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 6 ++-- Jellyfin.Api/Controllers/HlsSegmentController.cs | 4 +-- .../Controllers/UniversalAudioController.cs | 5 ++- Jellyfin.Api/Controllers/VideosController.cs | 7 ++--- Jellyfin.Api/Helpers/AudioHelper.cs | 36 ++++++++++++---------- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 23 +++++++------- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 36 ++++++++++------------ 8 files changed, 59 insertions(+), 60 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 107db9571..d300b4891 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -192,7 +192,7 @@ namespace Jellyfin.Api.Controllers StreamOptions = streamOptions }; - return await _audioHelper.GetAudioStream(this, _transcodingJobType, streamingRequest).ConfigureAwait(false); + return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); } } } diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1a85a07b8..b4fe3bc8f 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -272,7 +272,7 @@ namespace Jellyfin.Api.Controllers EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming }; - return await _dynamicHlsHelper.GetMasterHlsPlaylist(this, _transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + return await _dynamicHlsHelper.GetMasterHlsPlaylist(_transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); } /// @@ -439,7 +439,7 @@ namespace Jellyfin.Api.Controllers EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming }; - return await _dynamicHlsHelper.GetMasterHlsPlaylist(this, _transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + return await _dynamicHlsHelper.GetMasterHlsPlaylist(_transcodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); } /// @@ -1657,7 +1657,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)!, false, this); + return FileStreamResponseHelpers.GetStaticFileResult(segmentPath, MimeTypes.GetMimeType(segmentPath)!, false, HttpContext); } private long GetEndPositionTicks(StreamState state, int requestedIndex) diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index e4a6842bc..816252f80 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers var file = segmentId + Path.GetExtension(Request.Path); file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); - return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, this); + return FileStreamResponseHelpers.GetStaticFileResult(file, MimeTypes.GetMimeType(file)!, false, HttpContext); } /// @@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers return Task.CompletedTask; }); - return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, this); + return FileStreamResponseHelpers.GetStaticFileResult(path, MimeTypes.GetMimeType(path)!, false, HttpContext); } } } diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 2ea462488..2a788e03a 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -110,7 +110,6 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool breakOnNonKeyFrames, [FromQuery] bool enableRedirection = true) { - bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); _authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId; @@ -222,7 +221,7 @@ namespace Jellyfin.Api.Controllers EnableAdaptiveBitrateStreaming = true }; - return await _dynamicHlsHelper.GetMasterHlsPlaylist(this, TranscodingJobType.Hls, dynamicHlsRequestDto, true) + return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true) .ConfigureAwait(false); } @@ -251,7 +250,7 @@ namespace Jellyfin.Api.Controllers Context = EncodingContext.Static }; - return await _audioHelper.GetAudioStream(this, TranscodingJobType.Progressive, audioStreamingDto).ConfigureAwait(false); + return await _audioHelper.GetAudioStream(TranscodingJobType.Progressive, audioStreamingDto).ConfigureAwait(false); } private DeviceProfile GetDeviceProfile( diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index fafa722c5..8eee51c2c 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -471,7 +471,7 @@ namespace Jellyfin.Api.Controllers StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); using var httpClient = _httpClientFactory.CreateClient(); - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false); } if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File) @@ -507,7 +507,7 @@ namespace Jellyfin.Api.Controllers state.MediaPath, contentType, isHeadRequest, - this); + HttpContext); } // Need to start ffmpeg (because media can't be returned directly) @@ -517,10 +517,9 @@ namespace Jellyfin.Api.Controllers return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, - this, + HttpContext, _transcodingJobHelper, ffmpegCommandLineArguments, - Request, _transcodingJobType, cancellationTokenSource).ConfigureAwait(false); } diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index 001028432..694e1f0dd 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -35,6 +36,7 @@ namespace Jellyfin.Api.Helpers private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly IHttpClientFactory _httpClientFactory; + private readonly IHttpContextAccessor _httpContextAccessor; /// /// Initializes a new instance of the class. @@ -52,6 +54,7 @@ namespace Jellyfin.Api.Helpers /// Instance of the interface. /// Instance of . /// Instance of the interface. + /// Instance of the interface. public AudioHelper( IDlnaManager dlnaManager, IAuthorizationContext authContext, @@ -65,7 +68,8 @@ namespace Jellyfin.Api.Helpers IConfiguration configuration, IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, - IHttpClientFactory httpClientFactory) + IHttpClientFactory httpClientFactory, + IHttpContextAccessor httpContextAccessor) { _dlnaManager = dlnaManager; _authContext = authContext; @@ -80,26 +84,25 @@ namespace Jellyfin.Api.Helpers _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _httpClientFactory = httpClientFactory; + _httpContextAccessor = httpContextAccessor; } /// /// Get audio stream. /// - /// Requesting controller. /// Transcoding job type. /// Streaming controller.Request dto. /// A containing the resulting . public async Task GetAudioStream( - BaseJellyfinApiController controller, TranscodingJobType transcodingJobType, StreamingRequestDto streamingRequest) { - bool isHeadRequest = controller.Request.Method == System.Net.WebRequestMethods.Http.Head; + bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head; var cancellationTokenSource = new CancellationTokenSource(); using var state = await StreamingHelpers.GetStreamingState( streamingRequest, - controller.Request, + _httpContextAccessor.HttpContext.Request, _authContext, _mediaSourceManager, _userManager, @@ -118,30 +121,30 @@ namespace Jellyfin.Api.Helpers if (streamingRequest.Static && state.DirectStreamProvider != null) { - StreamingHelpers.AddDlnaHeaders(state, controller.Response.Headers, true, streamingRequest.StartTimeTicks, controller.Request, _dlnaManager); + 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(controller.Response.Body, CancellationToken.None) + }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None) .ConfigureAwait(false); // TODO (moved from MediaBrowser.Api): Don't hardcode contentType - return controller.File(controller.Response.Body, MimeTypes.GetMimeType("file.ts")!); + return new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, MimeTypes.GetMimeType("file.ts")!); } // Static remote stream if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http) { - StreamingHelpers.AddDlnaHeaders(state, controller.Response.Headers, true, streamingRequest.StartTimeTicks, controller.Request, _dlnaManager); + StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); using var httpClient = _httpClientFactory.CreateClient(); - return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, controller, httpClient).ConfigureAwait(false); + return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false); } if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File) { - return controller.BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically"); + return new BadRequestObjectResult($"Input protocol {state.InputProtocol} cannot be streamed statically"); } var outputPath = state.OutputFilePath; @@ -150,7 +153,7 @@ namespace Jellyfin.Api.Helpers var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); var isTranscodeCached = outputPathExists && transcodingJob != null; - StreamingHelpers.AddDlnaHeaders(state, controller.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, controller.Request, _dlnaManager); + StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager); // Static stream if (streamingRequest.Static) @@ -162,17 +165,17 @@ namespace Jellyfin.Api.Helpers await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None) { AllowEndOfFile = false - }.WriteToAsync(controller.Response.Body, CancellationToken.None) + }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None) .ConfigureAwait(false); - return controller.File(controller.Response.Body, contentType); + return new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, contentType); } return FileStreamResponseHelpers.GetStaticFileResult( state.MediaPath, contentType, isHeadRequest, - controller); + _httpContextAccessor.HttpContext); } // Need to start ffmpeg (because media can't be returned directly) @@ -182,10 +185,9 @@ namespace Jellyfin.Api.Helpers return await FileStreamResponseHelpers.GetTranscodedFile( state, isHeadRequest, - controller, + _httpContextAccessor.HttpContext, _transcodingJobHelper, ffmpegCommandLineArguments, - controller.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 127d91430..6a8829d46 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -45,6 +46,7 @@ namespace Jellyfin.Api.Helpers private readonly TranscodingJobHelper _transcodingJobHelper; private readonly INetworkManager _networkManager; private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; /// /// Initializes a new instance of the class. @@ -63,6 +65,7 @@ namespace Jellyfin.Api.Helpers /// Instance of . /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public DynamicHlsHelper( ILibraryManager libraryManager, IUserManager userManager, @@ -77,7 +80,8 @@ namespace Jellyfin.Api.Helpers IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, INetworkManager networkManager, - ILogger logger) + ILogger logger, + IHttpContextAccessor httpContextAccessor) { _libraryManager = libraryManager; _userManager = userManager; @@ -93,26 +97,24 @@ namespace Jellyfin.Api.Helpers _transcodingJobHelper = transcodingJobHelper; _networkManager = networkManager; _logger = logger; + _httpContextAccessor = httpContextAccessor; } /// /// Get master hls playlist. /// - /// Requesting controller. /// Transcoding job type. /// Streaming request dto. /// Enable adaptive bitrate streaming. /// A containing the resulting . public async Task GetMasterHlsPlaylist( - BaseJellyfinApiController controller, TranscodingJobType transcodingJobType, StreamingRequestDto streamingRequest, bool enableAdaptiveBitrateStreaming) { - var isHeadRequest = controller.Request.Method == WebRequestMethods.Http.Head; + var isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == WebRequestMethods.Http.Head; var cancellationTokenSource = new CancellationTokenSource(); return await GetMasterPlaylistInternal( - controller, streamingRequest, isHeadRequest, enableAdaptiveBitrateStreaming, @@ -121,7 +123,6 @@ namespace Jellyfin.Api.Helpers } private async Task GetMasterPlaylistInternal( - BaseJellyfinApiController controller, StreamingRequestDto streamingRequest, bool isHeadRequest, bool enableAdaptiveBitrateStreaming, @@ -130,7 +131,7 @@ namespace Jellyfin.Api.Helpers { using var state = await StreamingHelpers.GetStreamingState( streamingRequest, - controller.Request, + _httpContextAccessor.HttpContext.Request, _authContext, _mediaSourceManager, _userManager, @@ -147,7 +148,7 @@ namespace Jellyfin.Api.Helpers cancellationTokenSource.Token) .ConfigureAwait(false); - controller.Response.Headers.Add(HeaderNames.Expires, "0"); + _httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0"); if (isHeadRequest) { return new FileContentResult(Array.Empty(), MimeTypes.GetMimeType("playlist.m3u8")); @@ -161,7 +162,7 @@ namespace Jellyfin.Api.Helpers var isLiveStream = state.IsSegmentedLiveStream; - var queryString = controller.Request.QueryString.ToString(); + var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString(); // from universal audio service if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)) @@ -197,12 +198,12 @@ namespace Jellyfin.Api.Helpers if (!string.IsNullOrWhiteSpace(subtitleGroup)) { - AddSubtitles(state, subtitleStreams, builder, controller.Request.HttpContext.User); + AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.Request.HttpContext.User); } AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup); - if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, controller.Request.HttpContext.Connection.RemoteIpAddress)) + if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.Request.HttpContext.Connection.RemoteIpAddress)) { var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0; diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index a463783e0..e2f82b2de 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -22,14 +22,14 @@ namespace Jellyfin.Api.Helpers /// /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. - /// The managing the response. /// The making the remote request. + /// The current http context. /// A containing the API response. public static async Task GetStaticRemoteStreamResult( StreamState state, bool isHeadRequest, - ControllerBase controller, - HttpClient httpClient) + HttpClient httpClient, + HttpContext httpContext) { if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) { @@ -39,14 +39,14 @@ namespace Jellyfin.Api.Helpers using var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.ToString(); - controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; + httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none"; if (isHeadRequest) { - return controller.File(Array.Empty(), contentType); + return new FileContentResult(Array.Empty(), contentType); } - return controller.File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType); + return new FileStreamResult(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), contentType); } /// @@ -55,23 +55,23 @@ namespace Jellyfin.Api.Helpers /// The path to the file. /// The content type of the file. /// Whether the current request is a HTTP HEAD request so only the headers get returned. - /// The managing the response. + /// The current http context. /// An the file. public static ActionResult GetStaticFileResult( string path, string contentType, bool isHeadRequest, - ControllerBase controller) + HttpContext httpContext) { - controller.Response.ContentType = contentType; + httpContext.Response.ContentType = contentType; // if the request is a head request, return a NoContent result with the same headers as it would with a GET request if (isHeadRequest) { - return controller.NoContent(); + return new NoContentResult(); } - return controller.PhysicalFile(path, contentType); + return new PhysicalFileResult(path, contentType); } /// @@ -79,34 +79,32 @@ namespace Jellyfin.Api.Helpers /// /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. - /// The managing the response. + /// The current http context. /// The singleton. /// The command line arguments to start ffmpeg. - /// The starting the transcoding. /// The . /// The . /// A containing the transcoded file. public static async Task GetTranscodedFile( StreamState state, bool isHeadRequest, - ControllerBase controller, + HttpContext httpContext, TranscodingJobHelper transcodingJobHelper, string ffmpegCommandLineArguments, - HttpRequest request, TranscodingJobType transcodingJobType, CancellationTokenSource cancellationTokenSource) { // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; - controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; + httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none"; var contentType = state.GetMimeType(outputPath); // Headers only if (isHeadRequest) { - return controller.File(Array.Empty(), contentType); + return new FileContentResult(Array.Empty(), contentType); } var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); @@ -116,7 +114,7 @@ namespace Jellyfin.Api.Helpers TranscodingJobDto? job; if (!File.Exists(outputPath)) { - job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); + job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); } else { @@ -127,7 +125,7 @@ namespace Jellyfin.Api.Helpers var memoryStream = new MemoryStream(); await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); memoryStream.Position = 0; - return controller.File(memoryStream, contentType); + return new FileStreamResult(memoryStream, contentType); } finally { -- cgit v1.2.3 From 767c73e5c12fab004bb7de30bd1173295ee2d111 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 17 Aug 2020 13:22:42 -0600 Subject: fix usings --- Jellyfin.Api/Controllers/LiveTvController.cs | 4 ++-- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 2 +- MediaBrowser.Providers/Manager/ItemImageProvider.cs | 6 ++++-- MediaBrowser.Providers/Manager/ProviderManager.cs | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 9d8ec9f75..b2a9393a0 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1065,13 +1065,13 @@ namespace Jellyfin.Api.Controllers /// Available countries returned. /// A containing the available countries. [HttpGet("ListingProviders/SchedulesDirect/Countries")] - [Authorize(Policy = Policies.DefaultAuthorization)] + // [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetSchedulesDirectCountries() { var client = _httpClientFactory.CreateClient(); // https://json.schedulesdirect.org/20141201/available/countries - using var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries") + var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries") .ConfigureAwait(false); return File(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), MediaTypeNames.Application.Json); diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index a463783e0..ca62d4267 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -36,7 +36,7 @@ namespace Jellyfin.Api.Helpers httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent); } - using var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); + var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.ToString(); controller.Response.Headers[HeaderNames.AcceptRanges] = "none"; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 47b8554dd..a5eb095c4 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -466,10 +466,11 @@ namespace MediaBrowser.Providers.Manager try { using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false); + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await _providerManager.SaveImage( item, - await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + stream, response.Content.Headers.ContentType.MediaType, type, null, @@ -590,9 +591,10 @@ namespace MediaBrowser.Providers.Manager } } + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await _providerManager.SaveImage( item, - await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + stream, response.Content.Headers.ContentType.MediaType, imageType, null, diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 34d3a8a0f..e67d1b8c3 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -180,9 +180,10 @@ namespace MediaBrowser.Providers.Manager }; } + await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); await SaveImage( item, - await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + stream, contentType, type, imageIndex, -- cgit v1.2.3 From be67528958fc225d9af17cc34da87a613fb07cc9 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 17 Aug 2020 15:03:45 -0600 Subject: add disposal docs --- Jellyfin.Api/Controllers/LiveTvController.cs | 1 + Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 1 + 2 files changed, 2 insertions(+) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 2838361d7..97a88bc14 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1071,6 +1071,7 @@ namespace Jellyfin.Api.Controllers { var client = _httpClientFactory.CreateClient(); // https://json.schedulesdirect.org/20141201/available/countries + // Can't dispose the response as it's required up the call chain. var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries") .ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index ca62d4267..5a59313c9 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -36,6 +36,7 @@ namespace Jellyfin.Api.Helpers httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, useragent); } + // Can't dispose the response as it's required up the call chain. var response = await httpClient.GetAsync(state.MediaPath).ConfigureAwait(false); var contentType = response.Content.Headers.ContentType.ToString(); -- cgit v1.2.3 From 4836f14affcdf1b2806aafb0bb638456add1b612 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 5 Sep 2020 10:38:16 +0200 Subject: Enable HTTP Range Processing --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index deb54dbe6..884bfbe44 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -72,7 +72,7 @@ namespace Jellyfin.Api.Helpers return new NoContentResult(); } - return new PhysicalFileResult(path, contentType); + return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true }; } /// -- cgit v1.2.3 From d3e8834e807ebba78ada3a726ceea8dc37de8950 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 5 Sep 2020 20:03:21 +0100 Subject: Removed memoryStream --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 884bfbe44..6af1b3791 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net.Http; using System.Threading; @@ -123,10 +123,14 @@ namespace Jellyfin.Api.Helpers state.Dispose(); } - var memoryStream = new MemoryStream(); - await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); - memoryStream.Position = 0; - return new FileStreamResult(memoryStream, contentType); + await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None) + .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false); + return new FileStreamResult(httpContext.Response.Body, contentType); + + // var memoryStream = new MemoryStream(); + // await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); + // memoryStream.Position = 0; + // return new FileStreamResult(memoryStream, contentType); } finally { -- cgit v1.2.3 From 25e965b85c6b0989e78a81e9167d9eabab81b5be Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sat, 5 Sep 2020 20:33:18 +0100 Subject: Update FileStreamResponseHelpers.cs --- Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs | 5 ----- 1 file changed, 5 deletions(-) (limited to 'Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs') diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6af1b3791..6b516977e 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -126,11 +126,6 @@ namespace Jellyfin.Api.Helpers await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None) .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false); return new FileStreamResult(httpContext.Response.Body, contentType); - - // var memoryStream = new MemoryStream(); - // await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); - // memoryStream.Position = 0; - // return new FileStreamResult(memoryStream, contentType); } finally { -- cgit v1.2.3