fix hls seeking

This commit is contained in:
Luke Pulverenti 2015-04-13 15:14:37 -04:00
parent 88897141b0
commit 1fdaee1bb9
4 changed files with 158 additions and 45 deletions

View File

@ -151,7 +151,7 @@ namespace MediaBrowser.Api
{
lock (_activeTranscodingJobs)
{
var job = new TranscodingJob
var job = new TranscodingJob(Logger)
{
Type = type,
Path = path,
@ -286,7 +286,7 @@ namespace MediaBrowser.Api
if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
{
job.DisposeKillTimer();
job.StopKillTimer();
}
}
@ -299,29 +299,22 @@ namespace MediaBrowser.Api
PingTimer(job, false);
}
}
internal void PingTranscodingJob(string deviceId, string playSessionId)
internal void PingTranscodingJob(string playSessionId)
{
if (string.IsNullOrEmpty(deviceId))
if (string.IsNullOrEmpty(playSessionId))
{
throw new ArgumentNullException("deviceId");
throw new ArgumentNullException("playSessionId");
}
Logger.Debug("PingTranscodingJob PlaySessionId={0}", playSessionId);
var jobs = new List<TranscodingJob>();
lock (_activeTranscodingJobs)
{
// This is really only needed for HLS.
// Progressive streams can stop on their own reliably
jobs = jobs.Where(j =>
{
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
{
return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
}
return false;
}).ToList();
jobs = jobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList();
}
foreach (var job in jobs)
@ -332,6 +325,12 @@ namespace MediaBrowser.Api
private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
{
if (job.HasExited)
{
job.StopKillTimer();
return;
}
// TODO: Lower this hls timeout
var timerDuration = job.Type == TranscodingJobType.Progressive ?
1000 :
@ -343,19 +342,14 @@ namespace MediaBrowser.Api
timerDuration = 20000;
}
if (job.KillTimer == null)
{
// Don't start the timer for playback checkins with progressive streaming
if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn)
{
Logger.Debug("Starting kill timer at {0}ms", timerDuration);
job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
}
job.StartKillTimer(timerDuration, OnTranscodeKillTimerStopped);
}
else
{
Logger.Debug("Changing kill timer to {0}ms", timerDuration);
job.KillTimer.Change(timerDuration, Timeout.Infinite);
job.ChangeKillTimerIfStarted(timerDuration);
}
}
@ -367,6 +361,8 @@ namespace MediaBrowser.Api
{
var job = (TranscodingJob)state;
Logger.Debug("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
KillTranscodingJob(job, path => true);
}
@ -379,19 +375,14 @@ namespace MediaBrowser.Api
/// <returns>Task.</returns>
internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
{
if (string.IsNullOrEmpty(deviceId))
{
throw new ArgumentNullException("deviceId");
}
KillTranscodingJobs(j =>
{
if (string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(playSessionId))
{
return string.IsNullOrWhiteSpace(playSessionId) || string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
}
return false;
return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
}, deleteFiles);
}
@ -431,6 +422,10 @@ namespace MediaBrowser.Api
/// <param name="delete">The delete.</param>
private void KillTranscodingJob(TranscodingJob job, Func<string, bool> delete)
{
job.DisposeKillTimer();
Logger.Debug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
lock (_activeTranscodingJobs)
{
_activeTranscodingJobs.Remove(job);
@ -439,8 +434,6 @@ namespace MediaBrowser.Api
{
job.CancellationTokenSource.Cancel();
}
job.DisposeKillTimer();
}
lock (job.ProcessLock)
@ -599,6 +592,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <value>The process.</value>
public Process Process { get; set; }
public ILogger Logger { get; private set; }
/// <summary>
/// Gets or sets the active request count.
/// </summary>
@ -608,7 +602,7 @@ namespace MediaBrowser.Api
/// Gets or sets the kill timer.
/// </summary>
/// <value>The kill timer.</value>
public Timer KillTimer { get; set; }
private Timer KillTimer { get; set; }
public string DeviceId { get; set; }
@ -631,7 +625,27 @@ namespace MediaBrowser.Api
public TranscodingThrottler TranscodingThrottler { get; set; }
private readonly object _timerLock = new object();
public TranscodingJob(ILogger logger)
{
Logger = logger;
}
public void StopKillTimer()
{
lock (_timerLock)
{
if (KillTimer != null)
{
KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}
public void DisposeKillTimer()
{
lock (_timerLock)
{
if (KillTimer != null)
{
@ -641,6 +655,48 @@ namespace MediaBrowser.Api
}
}
public void StartKillTimer(int intervalMs, TimerCallback callback)
{
CheckHasExited();
lock (_timerLock)
{
if (KillTimer == null)
{
Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(callback, this, intervalMs, Timeout.Infinite);
}
else
{
Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
}
public void ChangeKillTimerIfStarted(int intervalMs)
{
CheckHasExited();
lock (_timerLock)
{
if (KillTimer != null)
{
Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
}
private void CheckHasExited()
{
if (HasExited)
{
throw new ObjectDisposedException("Job");
}
}
}
/// <summary>
/// Enum TranscodingJobType
/// </summary>

View File

@ -127,9 +127,27 @@ namespace MediaBrowser.Api.Playback.Hls
}
else
{
var startTranscoding = false;
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || (requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange)
if (currentTranscodingIndex == null)
{
Logger.Debug("Starting transcoding because currentTranscodingIndex=null");
startTranscoding = true;
}
else if (requestedIndex < currentTranscodingIndex.Value)
{
Logger.Debug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex);
startTranscoding = true;
}
else if ((requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange)
{
Logger.Debug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", (requestedIndex - currentTranscodingIndex.Value), segmentGapRequiringTranscodingChange, requestedIndex);
startTranscoding = true;
}
if (startTranscoding)
{
// If the playlist doesn't already exist, startup ffmpeg
try
@ -151,7 +169,7 @@ namespace MediaBrowser.Api.Playback.Hls
throw;
}
await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
//await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
else
{

View File

@ -63,6 +63,13 @@ namespace MediaBrowser.Api.Playback.Progressive
new ProgressiveFileCopier(_fileSystem, _job)
.StreamFile(Path, responseStream);
}
catch (IOException)
{
// These error are always the same so don't dump the whole stack trace
Logger.Error("Error streaming media. The client has most likely disconnected or transcoding has failed.");
throw;
}
catch (Exception ex)
{
Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", ex);

View File

@ -114,6 +114,15 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int? SubtitleStreamIndex { get; set; }
[ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public PlayMethod PlayMethod { get; set; }
[ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string LiveStreamId { get; set; }
[ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string PlaySessionId { get; set; }
}
/// <summary>
@ -160,6 +169,15 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int? VolumeLevel { get; set; }
[ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public PlayMethod PlayMethod { get; set; }
[ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string LiveStreamId { get; set; }
[ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string PlaySessionId { get; set; }
}
/// <summary>
@ -191,6 +209,12 @@ namespace MediaBrowser.Api.UserLibrary
/// <value>The position ticks.</value>
[ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")]
public long? PositionTicks { get; set; }
[ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string LiveStreamId { get; set; }
[ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string PlaySessionId { get; set; }
}
[Authenticated]
@ -260,7 +284,10 @@ namespace MediaBrowser.Api.UserLibrary
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex,
SubtitleStreamIndex = request.SubtitleStreamIndex
SubtitleStreamIndex = request.SubtitleStreamIndex,
PlayMethod = request.PlayMethod,
PlaySessionId = request.PlaySessionId,
LiveStreamId = request.LiveStreamId
});
}
@ -288,7 +315,10 @@ namespace MediaBrowser.Api.UserLibrary
MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex,
SubtitleStreamIndex = request.SubtitleStreamIndex,
VolumeLevel = request.VolumeLevel
VolumeLevel = request.VolumeLevel,
PlayMethod = request.PlayMethod,
PlaySessionId = request.PlaySessionId,
LiveStreamId = request.LiveStreamId
});
}
@ -296,7 +326,7 @@ namespace MediaBrowser.Api.UserLibrary
{
if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
{
ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId);
ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId);
}
request.SessionId = GetSession().Result.Id;
@ -316,7 +346,9 @@ namespace MediaBrowser.Api.UserLibrary
{
ItemId = request.Id,
PositionTicks = request.PositionTicks,
MediaSourceId = request.MediaSourceId
MediaSourceId = request.MediaSourceId,
PlaySessionId = request.PlaySessionId,
LiveStreamId = request.LiveStreamId
});
}