|
|
@ -13,6 +13,7 @@ using MediaBrowser.Model.IO;
|
|
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
using MediaBrowser.Model.Serialization;
|
|
|
|
using ServiceStack;
|
|
|
|
using ServiceStack;
|
|
|
|
using System;
|
|
|
|
using System;
|
|
|
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Globalization;
|
|
|
|
using System.Globalization;
|
|
|
|
using System.IO;
|
|
|
|
using System.IO;
|
|
|
@ -156,12 +157,14 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
{
|
|
|
|
{
|
|
|
|
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
|
|
|
|
ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await ReadSegmentLengths(playlistPath).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentTranscodingIndex.HasValue)
|
|
|
|
if (currentTranscodingIndex.HasValue)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
DeleteLastFile(playlistPath, segmentExtension, 0);
|
|
|
|
DeleteLastFile(playlistPath, segmentExtension, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex);
|
|
|
|
request.StartTimeTicks = GetSeekPositionTicks(state, playlistPath, requestedIndex);
|
|
|
|
|
|
|
|
|
|
|
|
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
|
|
|
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -199,11 +202,73 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
|
|
|
return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private long GetSeekPositionTicks(StreamState state, int requestedIndex)
|
|
|
|
private static readonly ConcurrentDictionary<string, double> SegmentLengths = new ConcurrentDictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
private async Task ReadSegmentLengths(string playlist)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var startSeconds = requestedIndex * state.SegmentLength;
|
|
|
|
try
|
|
|
|
var position = TimeSpan.FromSeconds(startSeconds).Ticks;
|
|
|
|
{
|
|
|
|
|
|
|
|
using (var fileStream = GetPlaylistFileStream(playlist))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
using (var reader = new StreamReader(fileStream))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
double duration = -1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (!reader.EndOfStream)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var text = await reader.ReadLineAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (text.StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var parts = text.Split(new[] { ':' }, 2);
|
|
|
|
|
|
|
|
if (parts.Length == 2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var time = parts[1].Trim(new[] { ',' }).Trim();
|
|
|
|
|
|
|
|
double timeValue;
|
|
|
|
|
|
|
|
if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out timeValue))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
duration = timeValue;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (duration != -1)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
SegmentLengths.AddOrUpdate(text, duration, (k, v) => duration);
|
|
|
|
|
|
|
|
Logger.Debug("Added segment length of {0} for {1}", duration, text);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
duration = -1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
catch (FileNotFoundException)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private long GetSeekPositionTicks(StreamState state, string playlist, int requestedIndex)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
double startSeconds = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < requestedIndex; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var segmentPath = GetSegmentPath(playlist, i);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var position = TimeSpan.FromSeconds(startSeconds).Ticks;
|
|
|
|
return position;
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -693,7 +758,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
|
|
|
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
|
|
|
|
state.SegmentLength.ToString(UsCulture));
|
|
|
|
1.ToString(UsCulture));
|
|
|
|
|
|
|
|
|
|
|
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
|
|
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
|
|
|
|
|
|
|
|
|
|
|
@ -728,6 +793,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|
|
|
{
|
|
|
|
{
|
|
|
|
var startTime = state.Request.StartTimeTicks ?? 0;
|
|
|
|
var startTime = state.Request.StartTimeTicks ?? 0;
|
|
|
|
var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds;
|
|
|
|
var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds;
|
|
|
|
|
|
|
|
|
|
|
|
var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks;
|
|
|
|
var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks;
|
|
|
|
endTime = Math.Min(endTime, state.RunTimeTicks.Value);
|
|
|
|
endTime = Math.Min(endTime, state.RunTimeTicks.Value);
|
|
|
|
|
|
|
|
|
|
|
|