Merge pull request #6013 from Bond-009/minor13
This commit is contained in:
commit
a937a854f2
|
@ -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)
|
private static double[] GetSegmentLengths(StreamState state)
|
||||||
|
=> GetSegmentLengthsInternal(state.RunTimeTicks ?? 0, state.SegmentLength);
|
||||||
|
|
||||||
|
internal static double[] GetSegmentLengthsInternal(long runtimeTicks, int segmentlength)
|
||||||
{
|
{
|
||||||
var result = new List<double>();
|
var segmentLengthTicks = TimeSpan.FromSeconds(segmentlength).Ticks;
|
||||||
|
var wholeSegments = runtimeTicks / segmentLengthTicks;
|
||||||
|
var remainingTicks = runtimeTicks % segmentLengthTicks;
|
||||||
|
|
||||||
var ticks = state.RunTimeTicks ?? 0;
|
var segmentsLen = wholeSegments + (remainingTicks == 0 ? 0 : 1);
|
||||||
|
var segments = new double[segmentsLen];
|
||||||
var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
|
for (int i = 0; i < wholeSegments; i++)
|
||||||
|
|
||||||
while (ticks > 0)
|
|
||||||
{
|
{
|
||||||
var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
|
segments[i] = segmentlength;
|
||||||
|
|
||||||
result.Add(TimeSpan.FromTicks(length).TotalSeconds);
|
|
||||||
|
|
||||||
ticks -= length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToArray();
|
if (remainingTicks != 0)
|
||||||
|
{
|
||||||
|
segments[^1] = TimeSpan.FromTicks(remainingTicks).TotalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 outputFmp4HeaderArg = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) switch
|
||||||
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
if (isWindows)
|
|
||||||
{
|
{
|
||||||
// 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 + "\"";
|
true => " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"",
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Helpers
|
namespace Jellyfin.Api.Helpers
|
||||||
|
|
|
@ -4,59 +4,10 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.MediaEncoding
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
public class EncodingJobOptions : BaseEncodingJobOptions
|
|
||||||
{
|
|
||||||
public string OutputDirectory { get; set; }
|
|
||||||
|
|
||||||
public string ItemId { get; set; }
|
|
||||||
|
|
||||||
public string TempDirectory { get; set; }
|
|
||||||
|
|
||||||
public bool ReadInputAtNativeFramerate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this instance has fixed resolution.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
|
|
||||||
public bool HasFixedResolution => Width.HasValue || Height.HasValue;
|
|
||||||
|
|
||||||
public DeviceProfile DeviceProfile { get; set; }
|
|
||||||
|
|
||||||
public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
|
|
||||||
{
|
|
||||||
Container = info.Container;
|
|
||||||
StartTimeTicks = info.StartPositionTicks;
|
|
||||||
MaxWidth = info.MaxWidth;
|
|
||||||
MaxHeight = info.MaxHeight;
|
|
||||||
MaxFramerate = info.MaxFramerate;
|
|
||||||
Id = info.ItemId;
|
|
||||||
MediaSourceId = info.MediaSourceId;
|
|
||||||
AudioCodec = info.TargetAudioCodec.FirstOrDefault();
|
|
||||||
MaxAudioChannels = info.GlobalMaxAudioChannels;
|
|
||||||
AudioBitRate = info.AudioBitrate;
|
|
||||||
AudioSampleRate = info.TargetAudioSampleRate;
|
|
||||||
DeviceProfile = deviceProfile;
|
|
||||||
VideoCodec = info.TargetVideoCodec.FirstOrDefault();
|
|
||||||
VideoBitRate = info.VideoBitrate;
|
|
||||||
AudioStreamIndex = info.AudioStreamIndex;
|
|
||||||
SubtitleMethod = info.SubtitleDeliveryMethod;
|
|
||||||
Context = info.Context;
|
|
||||||
TranscodingMaxAudioChannels = info.TranscodingMaxAudioChannels;
|
|
||||||
|
|
||||||
if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
|
|
||||||
{
|
|
||||||
SubtitleStreamIndex = info.SubtitleStreamIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamOptions = info.StreamOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For now until api and media encoding layers are unified
|
// For now until api and media encoding layers are unified
|
||||||
public class BaseEncodingJobOptions
|
public class BaseEncodingJobOptions
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AutoFixture;
|
||||||
|
using AutoFixture.AutoMoq;
|
||||||
|
using Jellyfin.Api.Controllers;
|
||||||
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Tests.Controllers
|
||||||
|
{
|
||||||
|
public class DynamicHlsControllerTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(GetSegmentLengths_Success_TestData))]
|
||||||
|
public void GetSegmentLengths_Success(long runtimeTicks, int segmentlength, double[] expected)
|
||||||
|
{
|
||||||
|
var res = DynamicHlsController.GetSegmentLengthsInternal(runtimeTicks, segmentlength);
|
||||||
|
Assert.Equal(expected.Length, res.Length);
|
||||||
|
for (int i = 0; i < expected.Length; i++)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected[i], res[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> GetSegmentLengths_Success_TestData()
|
||||||
|
{
|
||||||
|
yield return new object[] { 0, 6, Array.Empty<double>() };
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
TimeSpan.FromSeconds(3).Ticks,
|
||||||
|
6,
|
||||||
|
new double[] { 3 }
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
TimeSpan.FromSeconds(6).Ticks,
|
||||||
|
6,
|
||||||
|
new double[] { 6 }
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
TimeSpan.FromSeconds(3.3333333).Ticks,
|
||||||
|
6,
|
||||||
|
new double[] { 3.3333333 }
|
||||||
|
};
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
TimeSpan.FromSeconds(9.3333333).Ticks,
|
||||||
|
6,
|
||||||
|
new double[] { 6, 3.3333333 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user