|
@@ -160,7 +160,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
|
|
|
|
|
|
var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
|
|
|
- var segmentLength = state.SegmentLength;
|
|
|
|
|
|
var segmentExtension = GetSegmentFileExtension(state);
|
|
|
|
|
@@ -169,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
if (File.Exists(segmentPath))
|
|
|
{
|
|
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
|
|
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
|
|
+ return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
|
await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
|
@@ -178,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
if (File.Exists(segmentPath))
|
|
|
{
|
|
|
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
|
|
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
|
|
+ return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
@@ -214,7 +213,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
DeleteLastFile(playlistPath, segmentExtension, 0);
|
|
|
}
|
|
|
|
|
|
- request.StartTimeTicks = GetSeekPositionTicks(state, playlistPath, requestedIndex);
|
|
|
+ request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
|
|
|
|
|
|
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
|
|
}
|
|
@@ -249,37 +248,76 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
|
|
Logger.Info("returning {0}", segmentPath);
|
|
|
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
|
|
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
|
|
+ return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
|
|
}
|
|
|
|
|
|
// 256k
|
|
|
private const int BufferSize = 262144;
|
|
|
|
|
|
- private long GetSeekPositionTicks(StreamState state, string playlist, int requestedIndex)
|
|
|
+ private long GetStartPositionTicks(StreamState state, int requestedIndex)
|
|
|
{
|
|
|
double startSeconds = 0;
|
|
|
+ var lengths = GetSegmentLengths(state);
|
|
|
|
|
|
for (var i = 0; i < requestedIndex; i++)
|
|
|
{
|
|
|
- var segmentPath = GetSegmentPath(state, playlist, i);
|
|
|
+ startSeconds += lengths[requestedIndex];
|
|
|
+ }
|
|
|
+
|
|
|
+ var position = TimeSpan.FromSeconds(startSeconds).Ticks;
|
|
|
+ return position;
|
|
|
+ }
|
|
|
|
|
|
- //double length;
|
|
|
- //if (SegmentLengths.TryGetValue(Path.GetFileName(segmentPath), out length))
|
|
|
- //{
|
|
|
- // Logger.Debug("Found segment length of {0} for index {1}", length, i);
|
|
|
- // startSeconds += length;
|
|
|
- //}
|
|
|
- //else
|
|
|
- //{
|
|
|
- // startSeconds += state.SegmentLength;
|
|
|
- //}
|
|
|
- startSeconds += state.SegmentLength;
|
|
|
+ private long GetEndPositionTicks(StreamState state, int requestedIndex)
|
|
|
+ {
|
|
|
+ double startSeconds = 0;
|
|
|
+ var lengths = GetSegmentLengths(state);
|
|
|
+
|
|
|
+ for (var i = 0; i <= requestedIndex; i++)
|
|
|
+ {
|
|
|
+ startSeconds += lengths[requestedIndex];
|
|
|
}
|
|
|
|
|
|
var position = TimeSpan.FromSeconds(startSeconds).Ticks;
|
|
|
return position;
|
|
|
}
|
|
|
|
|
|
+ private double[] GetSegmentLengths(StreamState state)
|
|
|
+ {
|
|
|
+ var result = new List<double>();
|
|
|
+ var encoder = GetVideoEncoder(state);
|
|
|
+
|
|
|
+ if (string.Equals(encoder, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ var videoStream = state.VideoStream;
|
|
|
+ if (videoStream.KeyFrames != null && videoStream.KeyFrames.Count > 0)
|
|
|
+ {
|
|
|
+ foreach (var frame in videoStream.KeyFrames)
|
|
|
+ {
|
|
|
+ var seconds = TimeSpan.FromMilliseconds(frame).TotalSeconds;
|
|
|
+ seconds -= result.Sum();
|
|
|
+ result.Add(seconds);
|
|
|
+ }
|
|
|
+ return result.ToArray();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var ticks = state.RunTimeTicks ?? 0;
|
|
|
+
|
|
|
+ var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks;
|
|
|
+
|
|
|
+ while (ticks > 0)
|
|
|
+ {
|
|
|
+ var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks;
|
|
|
+
|
|
|
+ result.Add(TimeSpan.FromTicks(length).TotalSeconds);
|
|
|
+
|
|
|
+ ticks -= length;
|
|
|
+ }
|
|
|
+
|
|
|
+ return result.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
|
|
|
{
|
|
|
var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
|
|
@@ -384,17 +422,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state));
|
|
|
}
|
|
|
|
|
|
- private async Task<object> GetSegmentResult(string playlistPath,
|
|
|
+ private async Task<object> GetSegmentResult(StreamState state, string playlistPath,
|
|
|
string segmentPath,
|
|
|
int segmentIndex,
|
|
|
- int segmentLength,
|
|
|
TranscodingJob transcodingJob,
|
|
|
CancellationToken cancellationToken)
|
|
|
{
|
|
|
// If all transcoding has completed, just return immediately
|
|
|
if (transcodingJob != null && transcodingJob.HasExited && File.Exists(segmentPath))
|
|
|
{
|
|
|
- return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
|
|
+ return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
|
|
|
}
|
|
|
|
|
|
var segmentFilename = Path.GetFileName(segmentPath);
|
|
@@ -414,7 +451,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
{
|
|
|
if (File.Exists(segmentPath))
|
|
|
{
|
|
|
- return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
|
|
+ return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
|
|
|
}
|
|
|
//break;
|
|
|
}
|
|
@@ -465,13 +502,12 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
//}
|
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
- return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
|
|
|
+ return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob);
|
|
|
}
|
|
|
|
|
|
- private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
|
|
|
+ private object GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob)
|
|
|
{
|
|
|
- var segmentEndingSeconds = (1 + index) * segmentLength;
|
|
|
- var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
|
|
|
+ var segmentEndingPositionTicks = GetEndPositionTicks(state, index);
|
|
|
|
|
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
|
|
{
|
|
@@ -698,26 +734,22 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
{
|
|
|
var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
|
|
|
|
|
|
+ var segmentLengths = GetSegmentLengths(state);
|
|
|
+
|
|
|
var builder = new StringBuilder();
|
|
|
|
|
|
builder.AppendLine("#EXTM3U");
|
|
|
builder.AppendLine("#EXT-X-VERSION:3");
|
|
|
- builder.AppendLine("#EXT-X-TARGETDURATION:" + (state.SegmentLength).ToString(UsCulture));
|
|
|
+ builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling((segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength)).ToString(UsCulture));
|
|
|
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
|
|
|
|
|
|
var queryStringIndex = Request.RawUrl.IndexOf('?');
|
|
|
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
|
|
|
|
|
|
- var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
|
|
|
-
|
|
|
var index = 0;
|
|
|
|
|
|
- double segmentLength = state.SegmentLength;
|
|
|
-
|
|
|
- while (seconds > 0)
|
|
|
+ foreach (var length in segmentLengths)
|
|
|
{
|
|
|
- var length = seconds >= state.SegmentLength ? segmentLength : seconds;
|
|
|
-
|
|
|
builder.AppendLine("#EXTINF:" + length.ToString("0.000000", UsCulture) + ",");
|
|
|
|
|
|
builder.AppendLine(string.Format("hlsdynamic/{0}/{1}{2}{3}",
|
|
@@ -727,7 +759,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
GetSegmentFileExtension(isOutputVideo),
|
|
|
queryString));
|
|
|
|
|
|
- seconds -= length;
|
|
|
index++;
|
|
|
}
|
|
|
|
|
@@ -850,11 +881,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
args += " -flags +loop-global_header -sc_threshold 0";
|
|
|
}
|
|
|
|
|
|
- if (!EnableSplitTranscoding(state))
|
|
|
- {
|
|
|
- //args += " -copyts";
|
|
|
- }
|
|
|
-
|
|
|
return args;
|
|
|
}
|
|
|
|
|
@@ -870,21 +896,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
var toTimeParam = string.Empty;
|
|
|
var timestampOffsetParam = string.Empty;
|
|
|
|
|
|
- if (EnableSplitTranscoding(state))
|
|
|
- {
|
|
|
- var startTime = state.Request.StartTimeTicks ?? 0;
|
|
|
- var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds;
|
|
|
-
|
|
|
- var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks;
|
|
|
- endTime = Math.Min(endTime, state.RunTimeTicks.Value);
|
|
|
-
|
|
|
- if (endTime < state.RunTimeTicks.Value)
|
|
|
- {
|
|
|
- //toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime);
|
|
|
- toTimeParam = " -t " + MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(durationSeconds).Ticks);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0)
|
|
|
{
|
|
|
timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture);
|
|
@@ -927,36 +938,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
|
|
protected override bool EnableThrottling(StreamState state)
|
|
|
{
|
|
|
- return !EnableSplitTranscoding(state);
|
|
|
- }
|
|
|
-
|
|
|
- private bool EnableSplitTranscoding(StreamState state)
|
|
|
- {
|
|
|
- return false;
|
|
|
- if (string.Equals(Request.QueryString["EnableSplitTranscoding"], "false", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return state.RunTimeTicks.HasValue && state.IsOutputVideo;
|
|
|
- }
|
|
|
-
|
|
|
- protected override bool EnableStreamCopy
|
|
|
- {
|
|
|
- get
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|