mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-08 23:00:51 +02:00
Add missing functions
This commit is contained in:
parent
7bb34fc9e7
commit
b8d327889b
|
@ -35,7 +35,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IStreamHelper _streamHelper;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
@ -55,7 +54,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||||
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
||||||
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
||||||
|
@ -70,7 +68,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ISubtitleEncoder subtitleEncoder,
|
ISubtitleEncoder subtitleEncoder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
@ -85,7 +82,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_streamHelper = streamHelper;
|
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
@ -283,8 +279,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
||||||
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
||||||
|
@ -319,8 +318,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
|
|
||||||
if (state.MediaSource.IsInfiniteStream)
|
if (state.MediaSource.IsInfiniteStream)
|
||||||
{
|
{
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return File(Response.Body, contentType);
|
return File(Response.Body, contentType);
|
||||||
}
|
}
|
||||||
|
@ -339,7 +341,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
return await FileStreamResponseHelpers.GetTranscodedFile(
|
return await FileStreamResponseHelpers.GetTranscodedFile(
|
||||||
state,
|
state,
|
||||||
isHeadRequest,
|
isHeadRequest,
|
||||||
_streamHelper,
|
|
||||||
this,
|
this,
|
||||||
_transcodingJobHelper,
|
_transcodingJobHelper,
|
||||||
ffmpegCommandLineArguments,
|
ffmpegCommandLineArguments,
|
||||||
|
|
|
@ -24,7 +24,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
@ -45,9 +44,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
private readonly ISessionContext _sessionContext;
|
private readonly ISessionContext _sessionContext;
|
||||||
private readonly IStreamHelper _streamHelper;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IConfigurationManager _configurationManager;
|
private readonly IConfigurationManager _configurationManager;
|
||||||
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
||||||
|
@ -58,9 +57,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||||
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
||||||
/// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
|
/// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||||
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
|
||||||
public LiveTvController(
|
public LiveTvController(
|
||||||
ILiveTvManager liveTvManager,
|
ILiveTvManager liveTvManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
|
@ -68,9 +67,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
ISessionContext sessionContext,
|
ISessionContext sessionContext,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IConfigurationManager configurationManager)
|
IConfigurationManager configurationManager,
|
||||||
|
TranscodingJobHelper transcodingJobHelper)
|
||||||
{
|
{
|
||||||
_liveTvManager = liveTvManager;
|
_liveTvManager = liveTvManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
@ -78,9 +77,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_sessionContext = sessionContext;
|
_sessionContext = sessionContext;
|
||||||
_streamHelper = streamHelper;
|
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1187,7 +1186,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
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));
|
return File(memoryStream, MimeTypes.GetMimeType(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1214,7 +1215,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
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));
|
return File(memoryStream, MimeTypes.GetMimeType("file." + container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IStreamHelper _streamHelper;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
@ -67,7 +66,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||||
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
||||||
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
||||||
|
@ -83,7 +81,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ISubtitleEncoder subtitleEncoder,
|
ISubtitleEncoder subtitleEncoder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
@ -99,7 +96,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_streamHelper = streamHelper;
|
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
|
@ -376,7 +372,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
[FromQuery] Dictionary<string, string> streamOptions)
|
[FromQuery] Dictionary<string, string> streamOptions)
|
||||||
{
|
{
|
||||||
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||||
using var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
var streamingRequest = new StreamingRequestDto
|
var streamingRequest = new StreamingRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
|
@ -453,8 +449,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
||||||
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
||||||
|
@ -489,8 +488,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
|
|
||||||
if (state.MediaSource.IsInfiniteStream)
|
if (state.MediaSource.IsInfiniteStream)
|
||||||
{
|
{
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return File(Response.Body, contentType);
|
return File(Response.Body, contentType);
|
||||||
}
|
}
|
||||||
|
@ -509,7 +511,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
return await FileStreamResponseHelpers.GetTranscodedFile(
|
return await FileStreamResponseHelpers.GetTranscodedFile(
|
||||||
state,
|
state,
|
||||||
isHeadRequest,
|
isHeadRequest,
|
||||||
_streamHelper,
|
|
||||||
this,
|
this,
|
||||||
_transcodingJobHelper,
|
_transcodingJobHelper,
|
||||||
ffmpegCommandLineArguments,
|
ffmpegCommandLineArguments,
|
||||||
|
|
|
@ -3,9 +3,9 @@ using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
using Jellyfin.Api.Models.StreamingDtos;
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
@ -80,7 +80,6 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The current <see cref="StreamState"/>.</param>
|
/// <param name="state">The current <see cref="StreamState"/>.</param>
|
||||||
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
|
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
|
/// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
|
||||||
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
||||||
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
|
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
|
||||||
|
@ -91,7 +90,6 @@ namespace Jellyfin.Api.Helpers
|
||||||
public static async Task<ActionResult> GetTranscodedFile(
|
public static async Task<ActionResult> GetTranscodedFile(
|
||||||
StreamState state,
|
StreamState state,
|
||||||
bool isHeadRequest,
|
bool isHeadRequest,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
ControllerBase controller,
|
ControllerBase controller,
|
||||||
TranscodingJobHelper transcodingJobHelper,
|
TranscodingJobHelper transcodingJobHelper,
|
||||||
string ffmpegCommandLineArguments,
|
string ffmpegCommandLineArguments,
|
||||||
|
@ -116,18 +114,20 @@ namespace Jellyfin.Api.Helpers
|
||||||
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
TranscodingJobDto? job;
|
||||||
if (!File.Exists(outputPath))
|
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
|
else
|
||||||
{
|
{
|
||||||
transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
memoryStream.Position = 0;
|
||||||
return controller.File(memoryStream, contentType);
|
return controller.File(memoryStream, contentType);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
@ -12,34 +13,53 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProgressiveFileCopier
|
public class ProgressiveFileCopier
|
||||||
{
|
{
|
||||||
|
private readonly TranscodingJobDto? _job;
|
||||||
private readonly string? _path;
|
private readonly string? _path;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
private readonly IDirectStreamProvider? _directStreamProvider;
|
private readonly IDirectStreamProvider? _directStreamProvider;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
|
private long _bytesWritten;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
/// <param name="path">The path to copy from.</param>
|
||||||
/// <param name="path">Filepath to stream from.</param>
|
/// <param name="job">The transcoding job.</param>
|
||||||
public ProgressiveFileCopier(IStreamHelper streamHelper, string path)
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
public ProgressiveFileCopier(string path, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_path = path;
|
_path = path;
|
||||||
_streamHelper = streamHelper;
|
_job = job;
|
||||||
_directStreamProvider = null;
|
_cancellationToken = cancellationToken;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
|
/// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
|
||||||
public ProgressiveFileCopier(IStreamHelper streamHelper, IDirectStreamProvider directStreamProvider)
|
/// <param name="job">The transcoding job.</param>
|
||||||
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_directStreamProvider = directStreamProvider;
|
_directStreamProvider = directStreamProvider;
|
||||||
_streamHelper = streamHelper;
|
_job = job;
|
||||||
_path = null;
|
_cancellationToken = cancellationToken;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether allow read end of file.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowEndOfFile { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets copy start position.
|
||||||
|
/// </summary>
|
||||||
|
public long StartPosition { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write source stream to output.
|
/// Write source stream to output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -48,37 +68,123 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// <returns>A <see cref="Task"/>.</returns>
|
/// <returns>A <see cref="Task"/>.</returns>
|
||||||
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
|
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);
|
if (_directStreamProvider != null)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileOptions = FileOptions.SequentialScan;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (bytesRead == 0)
|
|
||||||
{
|
{
|
||||||
eofCount++;
|
await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
eofCount = 0;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
if (_job == null || _job.HasExited)
|
||||||
|
{
|
||||||
|
eofCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eofCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_job != null)
|
||||||
|
{
|
||||||
|
_transcodingJobHelper.OnTranscodeEndRequest(_job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> 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 bytesToWrite = bytesRead;
|
||||||
|
|
||||||
|
if (bytesToWrite > 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> 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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -680,6 +680,20 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [transcode end].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="job">The transcode job.</param>
|
||||||
|
public void OnTranscodeEndRequest(TranscodingJobDto job)
|
||||||
|
{
|
||||||
|
job.ActiveRequestCount--;
|
||||||
|
_logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount);
|
||||||
|
if (job.ActiveRequestCount <= 0)
|
||||||
|
{
|
||||||
|
PingTimer(job, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The progressive
|
/// The progressive
|
||||||
|
|
Loading…
Reference in a new issue