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

View file

@ -127,9 +127,27 @@ namespace MediaBrowser.Api.Playback.Hls
} }
else else
{ {
var startTranscoding = false;
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
var segmentGapRequiringTranscodingChange = 24/state.SegmentLength; 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 // If the playlist doesn't already exist, startup ffmpeg
try try
@ -151,7 +169,7 @@ namespace MediaBrowser.Api.Playback.Hls
throw; throw;
} }
await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
} }
else else
{ {

View file

@ -63,6 +63,13 @@ namespace MediaBrowser.Api.Playback.Progressive
new ProgressiveFileCopier(_fileSystem, _job) new ProgressiveFileCopier(_fileSystem, _job)
.StreamFile(Path, responseStream); .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) catch (Exception ex)
{ {
Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", 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")] [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int? SubtitleStreamIndex { get; set; } 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> /// <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")] [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
public int? VolumeLevel { get; set; } 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> /// <summary>
@ -191,6 +209,12 @@ namespace MediaBrowser.Api.UserLibrary
/// <value>The position ticks.</value> /// <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")] [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; } 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] [Authenticated]
@ -260,7 +284,10 @@ namespace MediaBrowser.Api.UserLibrary
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
MediaSourceId = request.MediaSourceId, MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex, 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, MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex, AudioStreamIndex = request.AudioStreamIndex,
SubtitleStreamIndex = request.SubtitleStreamIndex, 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)) if (!string.IsNullOrWhiteSpace(request.PlaySessionId))
{ {
ApiEntryPoint.Instance.PingTranscodingJob(AuthorizationContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId); ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId);
} }
request.SessionId = GetSession().Result.Id; request.SessionId = GetSession().Result.Id;
@ -316,7 +346,9 @@ namespace MediaBrowser.Api.UserLibrary
{ {
ItemId = request.Id, ItemId = request.Id,
PositionTicks = request.PositionTicks, PositionTicks = request.PositionTicks,
MediaSourceId = request.MediaSourceId MediaSourceId = request.MediaSourceId,
PlaySessionId = request.PlaySessionId,
LiveStreamId = request.LiveStreamId
}); });
} }