|
@@ -28,7 +28,6 @@ using MediaBrowser.Model.Net;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
-using Microsoft.Extensions.Configuration;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Microsoft.Net.Http.Headers;
|
|
|
|
|
|
@@ -545,7 +544,7 @@ namespace Jellyfin.Api.Controllers
|
|
[FromQuery] EncodingContext? context,
|
|
[FromQuery] EncodingContext? context,
|
|
[FromQuery] Dictionary<string, string> streamOptions)
|
|
[FromQuery] Dictionary<string, string> streamOptions)
|
|
{
|
|
{
|
|
- var cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
|
+ using var cancellationTokenSource = new CancellationTokenSource();
|
|
var streamingRequest = new VideoRequestDto
|
|
var streamingRequest = new VideoRequestDto
|
|
{
|
|
{
|
|
Id = itemId,
|
|
Id = itemId,
|
|
@@ -710,7 +709,7 @@ namespace Jellyfin.Api.Controllers
|
|
[FromQuery] EncodingContext? context,
|
|
[FromQuery] EncodingContext? context,
|
|
[FromQuery] Dictionary<string, string> streamOptions)
|
|
[FromQuery] Dictionary<string, string> streamOptions)
|
|
{
|
|
{
|
|
- var cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
|
+ using var cancellationTokenSource = new CancellationTokenSource();
|
|
var streamingRequest = new StreamingRequestDto
|
|
var streamingRequest = new StreamingRequestDto
|
|
{
|
|
{
|
|
Id = itemId,
|
|
Id = itemId,
|
|
@@ -1138,7 +1137,7 @@ namespace Jellyfin.Api.Controllers
|
|
var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
|
|
var isHlsInFmp4 = string.Equals(segmentContainer, "mp4", StringComparison.OrdinalIgnoreCase);
|
|
var hlsVersion = isHlsInFmp4 ? "7" : "3";
|
|
var hlsVersion = isHlsInFmp4 ? "7" : "3";
|
|
|
|
|
|
- var builder = new StringBuilder();
|
|
|
|
|
|
+ var builder = new StringBuilder(128);
|
|
|
|
|
|
builder.AppendLine("#EXTM3U")
|
|
builder.AppendLine("#EXTM3U")
|
|
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
|
|
.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD")
|
|
@@ -1191,7 +1190,7 @@ namespace Jellyfin.Api.Controllers
|
|
throw new ArgumentException("StartTimeTicks is not allowed.");
|
|
throw new ArgumentException("StartTimeTicks is not allowed.");
|
|
}
|
|
}
|
|
|
|
|
|
- var cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
|
|
+ using var cancellationTokenSource = new CancellationTokenSource();
|
|
var cancellationToken = cancellationTokenSource.Token;
|
|
var cancellationToken = cancellationTokenSource.Token;
|
|
|
|
|
|
using var state = await StreamingHelpers.GetStreamingState(
|
|
using var state = await StreamingHelpers.GetStreamingState(
|
|
@@ -1208,7 +1207,7 @@ namespace Jellyfin.Api.Controllers
|
|
_deviceManager,
|
|
_deviceManager,
|
|
_transcodingJobHelper,
|
|
_transcodingJobHelper,
|
|
TranscodingJobType,
|
|
TranscodingJobType,
|
|
- cancellationTokenSource.Token)
|
|
|
|
|
|
+ cancellationToken)
|
|
.ConfigureAwait(false);
|
|
.ConfigureAwait(false);
|
|
|
|
|
|
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
|
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
|
@@ -1227,7 +1226,7 @@ namespace Jellyfin.Api.Controllers
|
|
}
|
|
}
|
|
|
|
|
|
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
|
|
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
|
|
- await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
|
|
|
|
|
+ await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
var released = false;
|
|
var released = false;
|
|
var startTranscoding = false;
|
|
var startTranscoding = false;
|
|
|
|
|
|
@@ -1323,24 +1322,28 @@ namespace Jellyfin.Api.Controllers
|
|
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
|
|
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
|
|
- private double[] GetSegmentLengths(StreamState state)
|
|
|
|
- {
|
|
|
|
- var result = new List<double>();
|
|
|
|
-
|
|
|
|
- var ticks = state.RunTimeTicks ?? 0;
|
|
|
|
|
|
+ private static double[] GetSegmentLengths(StreamState state)
|
|
|
|
+ => GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
|
|
|
|
|
|
- var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
|
|
|
|
|
|
+ internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
|
|
|
|
+ {
|
|
|
|
+ var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
|
|
|
|
+ var wholeSegments = runtimeTicks / segmentLengthTicks;
|
|
|
|
+ var remainingTicks = runtimeTicks % segmentLengthTicks;
|
|
|
|
|
|
- while (ticks > 0)
|
|
|
|
|
|
+ var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
|
|
|
|
+ var segments = new double[segmentsLen];
|
|
|
|
+ for (int i = 0; i < wholeSegments; i++)
|
|
{
|
|
{
|
|
- var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
|
|
|
|
-
|
|
|
|
- result.Add(TimeSpan.FromTicks(length).TotalSeconds);
|
|
|
|
|
|
+ segments[i] = segmentlength;
|
|
|
|
+ }
|
|
|
|
|
|
- ticks -= length;
|
|
|
|
|
|
+ if (remainingTicks != 0)
|
|
|
|
+ {
|
|
|
|
+ segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
|
|
}
|
|
}
|
|
|
|
|
|
- return result.ToArray();
|
|
|
|
|
|
+ return segments;
|
|
}
|
|
}
|
|
|
|
|
|
private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
|
|
private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
|
|
@@ -1376,18 +1379,13 @@ namespace Jellyfin.Api.Controllers
|
|
}
|
|
}
|
|
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
|
|
else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- var outputFmp4HeaderArg = string.Empty;
|
|
|
|
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
|
|
- if (isWindows)
|
|
|
|
|
|
+ var outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch
|
|
{
|
|
{
|
|
// on Windows, the path of fmp4 header file needs to be configured
|
|
// on Windows, the path of fmp4 header file needs to be configured
|
|
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
|
|
+ true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
|
|
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
|
|
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
|
|
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"";
|
|
|
|
- }
|
|
|
|
|
|
+ false => " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
|
|
|
|
+ };
|
|
|
|
|
|
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
|
segmentFormat = "fmp4" + outputFmp4HeaderArg;
|
|
}
|
|
}
|