Browse Source

fix hls seeking

Luke Pulverenti 10 years ago
parent
commit
1fdaee1bb9

+ 94 - 38
MediaBrowser.Api/ApiEntryPoint.cs

@@ -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)
             {
-                // 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,12 +625,74 @@ 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()
         {
-            if (KillTimer != null)
+            lock (_timerLock)
+            {
+                if (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)
             {
-                KillTimer.Dispose();
-                KillTimer = null;
+                throw new ObjectDisposedException("Job");
             }
         }
     }

+ 21 - 3
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -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)
+                    var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
+
+                    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
                     {

+ 7 - 0
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -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);

+ 36 - 4
MediaBrowser.Api/UserLibrary/PlaystateService.cs

@@ -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
             });
         }