Browse Source

restore nuget targets for mono build

Luke Pulverenti 10 năm trước cách đây
mục cha
commit
60d1d5cdee
27 tập tin đã thay đổi với 395 bổ sung250 xóa
  1. 58 36
      MediaBrowser.Api/ApiEntryPoint.cs
  2. 1 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  3. 47 16
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  4. 14 14
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  5. 43 20
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  6. 16 22
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  7. 8 14
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  8. 2 10
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  9. 83 12
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  10. 26 3
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  11. 2 9
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  12. 1 0
      MediaBrowser.Api/Playback/StreamRequest.cs
  13. 3 3
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  14. 1 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  15. 3 0
      MediaBrowser.Controller/Net/StaticResultOptions.cs
  16. 1 13
      MediaBrowser.Dlna/Ssdp/Datagram.cs
  17. 3 7
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  18. 1 1
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  19. 1 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  20. 1 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  21. 2 2
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  22. 9 34
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  23. 25 12
      MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
  24. 11 1
      MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
  25. 31 15
      MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs
  26. 1 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  27. 1 1
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 58 - 36
MediaBrowser.Api/ApiEntryPoint.cs

@@ -120,12 +120,15 @@ namespace MediaBrowser.Api
         /// Called when [transcode beginning].
         /// </summary>
         /// <param name="path">The path.</param>
+        /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="type">The type.</param>
         /// <param name="process">The process.</param>
         /// <param name="deviceId">The device id.</param>
         /// <param name="state">The state.</param>
         /// <param name="cancellationTokenSource">The cancellation token source.</param>
-        public void OnTranscodeBeginning(string path,
+        /// <returns>TranscodingJob.</returns>
+        public TranscodingJob OnTranscodeBeginning(string path,
+            string transcodingJobId,
             TranscodingJobType type,
             Process process,
             string deviceId,
@@ -134,22 +137,37 @@ namespace MediaBrowser.Api
         {
             lock (_activeTranscodingJobs)
             {
-                _activeTranscodingJobs.Add(new TranscodingJob
+                var job = new TranscodingJob
                 {
                     Type = type,
                     Path = path,
                     Process = process,
                     ActiveRequestCount = 1,
                     DeviceId = deviceId,
-                    CancellationTokenSource = cancellationTokenSource
-                });
+                    CancellationTokenSource = cancellationTokenSource,
+                    Id = transcodingJobId
+                };
+
+                _activeTranscodingJobs.Add(job);
+
+                ReportTranscodingProgress(job, state, null, null, null, null);
 
-                ReportTranscodingProgress(state, null, null);
+                return job;
             }
         }
 
-        public void ReportTranscodingProgress(StreamState state, float? framerate, double? percentComplete)
+        public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
         {
+            var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
+
+            if (job != null)
+            {
+                job.Framerate = framerate;
+                job.CompletionPercentage = percentComplete;
+                job.TranscodingPositionTicks = ticks;
+                job.BytesTranscoded = bytesTranscoded;
+            }
+
             var deviceId = state.Request.DeviceId;
 
             if (!string.IsNullOrWhiteSpace(deviceId))
@@ -226,12 +244,20 @@ namespace MediaBrowser.Api
             }
         }
 
+        public TranscodingJob GetTranscodingJob(string id)
+        {
+            lock (_activeTranscodingJobs)
+            {
+                return _activeTranscodingJobs.FirstOrDefault(j => j.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
+            }
+        }
+
         /// <summary>
         /// Called when [transcode begin request].
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="type">The type.</param>
-        public void OnTranscodeBeginRequest(string path, TranscodingJobType type)
+        public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type)
         {
             lock (_activeTranscodingJobs)
             {
@@ -239,7 +265,7 @@ namespace MediaBrowser.Api
 
                 if (job == null)
                 {
-                    return;
+                    return null;
                 }
 
                 job.ActiveRequestCount++;
@@ -249,40 +275,27 @@ namespace MediaBrowser.Api
                     job.KillTimer.Dispose();
                     job.KillTimer = null;
                 }
+
+                return job;
             }
         }
 
-        /// <summary>
-        /// Called when [transcode end request].
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="type">The type.</param>
-        public void OnTranscodeEndRequest(string path, TranscodingJobType type)
+        public void OnTranscodeEndRequest(TranscodingJob job)
         {
-            lock (_activeTranscodingJobs)
+            job.ActiveRequestCount--;
+
+            if (job.ActiveRequestCount == 0)
             {
-                var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
+                // The HLS kill timer is long - 1/2 hr. clients should use the manual kill command when stopping.
+                var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : 1800000;
 
-                if (job == null)
+                if (job.KillTimer == null)
                 {
-                    return;
+                    job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
                 }
-
-                job.ActiveRequestCount--;
-
-                if (job.ActiveRequestCount == 0)
+                else
                 {
-                    // The HLS kill timer is long - 1/2 hr. clients should use the manual kill command when stopping.
-                    var timerDuration = type == TranscodingJobType.Progressive ? 1000 : 1800000;
-
-                    if (job.KillTimer == null)
-                    {
-                        job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite);
-                    }
-                    else
-                    {
-                        job.KillTimer.Change(timerDuration, Timeout.Infinite);
-                    }
+                    job.KillTimer.Change(timerDuration, Timeout.Infinite);
                 }
             }
         }
@@ -306,7 +319,6 @@ namespace MediaBrowser.Api
         /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
         /// <returns>Task.</returns>
         /// <exception cref="ArgumentNullException">deviceId</exception>
-        /// <exception cref="System.ArgumentNullException">sourcePath</exception>
         internal Task KillTranscodingJobs(string deviceId, Func<string, bool> deleteFiles, bool acquireLock)
         {
             if (string.IsNullOrEmpty(deviceId))
@@ -324,8 +336,7 @@ namespace MediaBrowser.Api
         /// <param name="deleteFiles">The delete files.</param>
         /// <param name="acquireLock">if set to <c>true</c> [acquire lock].</param>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException">deviceId</exception>
-        internal async Task KillTranscodingJobs(Func<TranscodingJob,bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
+        internal async Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles, bool acquireLock)
         {
             var jobs = new List<TranscodingJob>();
 
@@ -542,6 +553,17 @@ namespace MediaBrowser.Api
         public object ProcessLock = new object();
 
         public bool HasExited { get; set; }
+
+        public string Id { get; set; }
+
+        public float? Framerate { get; set; }
+        public double? CompletionPercentage { get; set; }
+
+        public long? BytesDownloaded { get; set; }
+        public long? BytesTranscoded { get; set; }
+        
+        public long? TranscodingPositionTicks { get; set; }
+        public long? DownloadPositionTicks { get; set; }
     }
 
     /// <summary>

+ 1 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -169,7 +169,7 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 47 - 16
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -90,10 +90,11 @@ namespace MediaBrowser.Api.Playback
         /// Gets the command line arguments.
         /// </summary>
         /// <param name="outputPath">The output path.</param>
+        /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="state">The state.</param>
         /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
         /// <returns>System.String.</returns>
-        protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
+        protected abstract string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding);
 
         /// <summary>
         /// Gets the type of the transcoding job.
@@ -122,7 +123,7 @@ namespace MediaBrowser.Api.Playback
 
             var outputFileExtension = GetOutputFileExtension(state);
 
-            var data = GetCommandLineArguments("dummy\\dummy", state, false);
+            var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
 
             data += "-" + (state.Request.DeviceId ?? string.Empty);
 
@@ -782,9 +783,10 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the input argument.
         /// </summary>
+        /// <param name="transcodingJobId">The transcoding job identifier.</param>
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetInputArgument(StreamState state)
+        protected string GetInputArgument(string transcodingJobId, StreamState state)
         {
             if (state.InputProtocol == MediaProtocol.File &&
                state.RunTimeTicks.HasValue &&
@@ -795,6 +797,8 @@ namespace MediaBrowser.Api.Playback
                 {
                     var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/mediabrowser/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
 
+                    url += "&transcodingJobId=" + transcodingJobId;
+
                     return string.Format("\"{0}\"", url);
                 }
             }
@@ -897,7 +901,7 @@ namespace MediaBrowser.Api.Playback
         /// <param name="cancellationTokenSource">The cancellation token source.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.InvalidOperationException">ffmpeg was not found at  + MediaEncoder.EncoderPath</exception>
-        protected async Task StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
+        protected async Task<TranscodingJob> StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
         {
             if (!File.Exists(MediaEncoder.EncoderPath))
             {
@@ -908,7 +912,8 @@ namespace MediaBrowser.Api.Playback
 
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
 
-            var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
+            var transcodingId = Guid.NewGuid().ToString("N");
+            var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
 
             if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
             {
@@ -938,7 +943,8 @@ namespace MediaBrowser.Api.Playback
                 EnableRaisingEvents = true
             };
 
-            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
+            var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
+                transcodingId,
                 TranscodingJobType,
                 process,
                 state.Request.DeviceId,
@@ -957,7 +963,7 @@ namespace MediaBrowser.Api.Playback
             var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
             await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
 
-            process.Exited += (sender, args) => OnFfMpegProcessExited(process, state, outputPath);
+            process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
 
             try
             {
@@ -976,16 +982,18 @@ namespace MediaBrowser.Api.Playback
             process.BeginOutputReadLine();
 
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
+            StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
 
             // Wait for the file to exist before proceeeding
             while (!File.Exists(outputPath))
             {
                 await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
             }
+
+            return transcodingJob;
         }
 
-        private async void StartStreamingLog(StreamState state, Stream source, Stream target)
+        private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
         {
             try
             {
@@ -995,7 +1003,7 @@ namespace MediaBrowser.Api.Playback
                     {
                         var line = await reader.ReadLineAsync().ConfigureAwait(false);
 
-                        ParseLogLine(line, state);
+                        ParseLogLine(line, transcodingJob, state);
 
                         var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
 
@@ -1009,10 +1017,12 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        private void ParseLogLine(string line, StreamState state)
+        private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state)
         {
             float? framerate = null;
             double? percent = null;
+            TimeSpan? transcodingPosition = null;
+            long? bytesTranscoded = null;
 
             var parts = line.Split(' ');
 
@@ -1051,13 +1061,36 @@ namespace MediaBrowser.Api.Playback
 
                         var percentVal = currentMs / totalMs;
                         percent = 100 * percentVal;
+
+                        transcodingPosition = val;
+                    }
+                }
+                else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
+                {
+                    var size = part.Split(new[] { '=' }, 2).Last();
+
+                    int? scale = null;
+                    if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
+                    {
+                        scale = 1024;
+                        size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
+                    }
+
+                    if (scale.HasValue)
+                    {
+                        long val;
+                        
+                        if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
+                        {
+                            bytesTranscoded = val * scale.Value;
+                        }
                     }
                 }
             }
 
             if (framerate.HasValue || percent.HasValue)
             {
-                ApiEntryPoint.Instance.ReportTranscodingProgress(state, framerate, percent);
+                ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded);
             }
         }
 
@@ -1170,12 +1203,10 @@ namespace MediaBrowser.Api.Playback
         /// Processes the exited.
         /// </summary>
         /// <param name="process">The process.</param>
+        /// <param name="job">The job.</param>
         /// <param name="state">The state.</param>
-        /// <param name="outputPath">The output path.</param>
-        private void OnFfMpegProcessExited(Process process, StreamState state, string outputPath)
+        private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
         {
-            var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType);
-
             if (job != null)
             {
                 job.HasExited = true;

+ 14 - 14
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -88,10 +88,11 @@ namespace MediaBrowser.Api.Playback.Hls
             }
 
             var playlist = state.OutputFilePath;
+            TranscodingJob job;
 
             if (File.Exists(playlist))
             {
-                ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
+                job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
             }
             else
             {
@@ -100,14 +101,14 @@ namespace MediaBrowser.Api.Playback.Hls
                 {
                     if (File.Exists(playlist))
                     {
-                        ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
+                        job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
                     }
                     else
                     {
                         // If the playlist doesn't already exist, startup ffmpeg
                         try
                         {
-                            await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
+                            job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
                         }
                         catch
                         {
@@ -137,7 +138,10 @@ namespace MediaBrowser.Api.Playback.Hls
                 }
                 finally
                 {
-                    ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+                    if (job != null)
+                    {
+                        ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
+                    }
                 }
             }
 
@@ -162,7 +166,10 @@ namespace MediaBrowser.Api.Playback.Hls
             }
             finally
             {
-                ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+                if (job != null)
+                {
+                    ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
+                }
             }
         }
 
@@ -241,14 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
             }
         }
 
-        /// <summary>
-        /// Gets the command line arguments.
-        /// </summary>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
-        /// <returns>System.String.</returns>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
         {
             var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
 
@@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
                 itsOffset,
                 inputModifier,
-                GetInputArgument(state),
+                GetInputArgument(transcodingJobId, state),
                 threads,
                 GetMapArgs(state),
                 GetVideoArguments(state),

+ 43 - 20
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
@@ -107,11 +108,14 @@ namespace MediaBrowser.Api.Playback.Hls
             var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
 
             var segmentPath = GetSegmentPath(playlistPath, index);
+            var segmentLength = state.SegmentLength;
+
+            TranscodingJob job = null;
 
             if (File.Exists(segmentPath))
             {
-                ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
-                return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
+                job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
+                return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
             }
 
             await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
@@ -119,8 +123,8 @@ namespace MediaBrowser.Api.Playback.Hls
             {
                 if (File.Exists(segmentPath))
                 {
-                    ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
-                    return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
+                    job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
+                    return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
                 }
                 else
                 {
@@ -141,7 +145,7 @@ namespace MediaBrowser.Api.Playback.Hls
                             var startSeconds = index * state.SegmentLength;
                             request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks;
 
-                            await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
+                            job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
                         }
                         catch
                         {
@@ -165,7 +169,8 @@ namespace MediaBrowser.Api.Playback.Hls
             }
 
             Logger.Info("returning {0}", segmentPath);
-            return await GetSegmentResult(playlistPath, segmentPath, index, cancellationToken).ConfigureAwait(false);
+            job = job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType.Hls);
+            return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
         }
 
         public int? GetCurrentTranscodingIndex(string playlist)
@@ -258,12 +263,17 @@ namespace MediaBrowser.Api.Playback.Hls
             return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts");
         }
 
-        private async Task<object> GetSegmentResult(string playlistPath, string segmentPath, int segmentIndex, CancellationToken cancellationToken)
+        private async Task<object> GetSegmentResult(string playlistPath,
+            string segmentPath,
+            int segmentIndex,
+            int segmentLength,
+            TranscodingJob transcodingJob,
+            CancellationToken cancellationToken)
         {
             // If all transcoding has completed, just return immediately
             if (!IsTranscoding(playlistPath))
             {
-                return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
+                return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
             }
 
             var segmentFilename = Path.GetFileName(segmentPath);
@@ -277,7 +287,7 @@ namespace MediaBrowser.Api.Playback.Hls
                     // If it appears in the playlist, it's done
                     if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
                     {
-                        return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
+                        return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
                     }
                 }
             }
@@ -286,7 +296,7 @@ namespace MediaBrowser.Api.Playback.Hls
             //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath);
             //if (currentTranscodingIndex > segmentIndex)
             //{
-            //    return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
+            //return GetSegmentResult(segmentPath, segmentIndex);
             //}
 
             // Wait for the file to stop being written to, then stream it
@@ -317,7 +327,27 @@ namespace MediaBrowser.Api.Playback.Hls
                 await Task.Delay(100, cancellationToken).ConfigureAwait(false);
             }
 
-            return ResultFactory.GetStaticFileResult(Request, segmentPath, FileShare.ReadWrite);
+            return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
+        }
+
+        private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
+        {
+            var segmentEndingSeconds = (1 + index) * segmentLength;
+            var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
+
+            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+            {
+                Path = segmentPath,
+                FileShare = FileShare.ReadWrite,
+                OnComplete = () =>
+                {
+                    if (transcodingJob != null)
+                    {
+                        transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
+                    }
+
+                }
+            });
         }
 
         private bool IsTranscoding(string playlistPath)
@@ -621,14 +651,7 @@ namespace MediaBrowser.Api.Playback.Hls
             return args;
         }
 
-        /// <summary>
-        /// Gets the command line arguments.
-        /// </summary>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
-        /// <returns>System.String.</returns>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
         {
             var threads = GetNumberOfThreads(state, false);
 
@@ -639,7 +662,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
                 inputModifier,
-                GetInputArgument(state),
+                GetInputArgument(transcodingJobId, state),
                 threads,
                 GetMapArgs(state),
                 GetVideoArguments(state),

+ 16 - 22
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -63,7 +63,17 @@ namespace MediaBrowser.Api.Playback.Hls
 
         public object Get(GetHlsPlaylist request)
         {
-            OnBeginRequest(request.PlaylistId);
+            var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
+
+            foreach (var playlist in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*.m3u8")
+                .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
+                .ToList())
+            {
+                if (!string.IsNullOrEmpty(playlist))
+                {
+                    ExtendPlaylistTimer(playlist);
+                }
+            }
 
             var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
 
@@ -93,32 +103,16 @@ namespace MediaBrowser.Api.Playback.Hls
             return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
         }
 
-        /// <summary>
-        /// Called when [begin request].
-        /// </summary>
-        /// <param name="playlistId">The playlist id.</param>
-        protected void OnBeginRequest(string playlistId)
-        {
-            var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
-
-            foreach (var playlist in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*.m3u8")
-                .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
-                .ToList())
-            {
-                if (!string.IsNullOrEmpty(playlist))
-                {
-                    ExtendPlaylistTimer(playlist);
-                }
-            }
-        }
-
         private async void ExtendPlaylistTimer(string playlist)
         {
-            ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
+            var job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
 
             await Task.Delay(20000).ConfigureAwait(false);
 
-            ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+            if (job != null)
+            {
+                ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
+            }
         }
     }
 }

+ 8 - 14
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -81,18 +81,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
 
-            OnBeginRequest(request.PlaylistId);
-
-            return ResultFactory.GetStaticFileResult(Request, file);
-        }
-
-        /// <summary>
-        /// Called when [begin request].
-        /// </summary>
-        /// <param name="playlistId">The playlist id.</param>
-        protected void OnBeginRequest(string playlistId)
-        {
-            var normalizedPlaylistId = playlistId.Replace("-low", string.Empty);
+            var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty);
 
             foreach (var playlist in Directory.EnumerateFiles(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, "*.m3u8")
                 .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
@@ -100,15 +89,20 @@ namespace MediaBrowser.Api.Playback.Hls
             {
                 ExtendPlaylistTimer(playlist);
             }
+
+            return ResultFactory.GetStaticFileResult(Request, file);
         }
 
         private async void ExtendPlaylistTimer(string playlist)
         {
-            ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
+            var job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
 
             await Task.Delay(20000).ConfigureAwait(false);
 
-            ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
+            if (job != null)
+            {
+                ApiEntryPoint.Instance.OnTranscodeEndRequest(job);
+            }
         }
 
         /// <summary>

+ 2 - 10
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -55,15 +55,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        /// <summary>
-        /// Gets the command line arguments.
-        /// </summary>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
-        /// <returns>System.String.</returns>
-        /// <exception cref="System.InvalidOperationException">Only aac and mp3 audio codecs are supported.</exception>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
         {
             var audioTranscodeParams = new List<string>();
 
@@ -92,7 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
                 inputModifier,
-                GetInputArgument(state),
+                GetInputArgument(transcodingJobId, state),
                 threads,
                 vn,
                 string.Join(" ", audioTranscodeParams.ToArray()),

+ 83 - 12
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -142,10 +142,9 @@ namespace MediaBrowser.Api.Playback.Progressive
             var outputPath = state.OutputFilePath;
             var outputPathExists = File.Exists(outputPath);
 
-            var isStatic = request.Static ||
-                           (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
+            var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
 
-            AddDlnaHeaders(state, responseHeaders, isStatic);
+            AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
 
             // Static stream
             if (request.Static)
@@ -154,6 +153,10 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
+                    var job = string.IsNullOrEmpty(request.TranscodingJobId) ?
+                        null :
+                        ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId);
+
                     var limits = new List<long>();
                     if (state.InputBitrate.HasValue)
                     {
@@ -172,7 +175,13 @@ namespace MediaBrowser.Api.Playback.Progressive
                     }
 
                     // Take the greater of the above to methods, just to be safe
-                    var throttleLimit = limits.Count > 0 ? limits.Max() : 0;
+                    var throttleLimit = limits.Count > 0 ? limits.First() : 0;
+
+                    // Pad to play it safe
+                    var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit);
+
+                    // Don't even start evaluating this until at least two minutes have content have been consumed
+                    var targetGap = throttleLimit * 120;
 
                     return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
@@ -182,17 +191,17 @@ namespace MediaBrowser.Api.Playback.Progressive
                         Path = state.MediaPath,
                         Throttle = request.Throttle,
 
-                        // Pad by 20% to play it safe
-                        ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit),
+                        ThrottleLimit = bytesPerSecond,
 
-                        // 3.5 minutes
-                        MinThrottlePosition = throttleLimit * 210
+                        MinThrottlePosition = targetGap,
+
+                        ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
                     });
                 }
             }
 
             // Not static but transcode cache file exists
-            if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
+            if (isTranscodeCached)
             {
                 var contentType = state.GetMimeType(outputPath);
 
@@ -225,6 +234,67 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
         }
 
+        private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks;
+
+        private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job)
+        {
+            var bytesDownloaded = job.BytesDownloaded ?? 0;
+            var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
+            var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
+
+            var path = job.Path;
+
+            if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
+            {
+                // Progressive Streaming - byte-based consideration
+
+                try
+                {
+                    var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
+
+                    // Estimate the bytes the transcoder should be ahead
+                    double gapFactor = _gapLengthInTicks;
+                    gapFactor /= transcodingPositionTicks;
+                    var targetGap = bytesTranscoded * gapFactor;
+
+                    var gap = bytesTranscoded - bytesDownloaded;
+
+                    if (gap < targetGap)
+                    {
+                        //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+                        return 0;
+                    }
+
+                    //Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
+                }
+                catch
+                {
+                    //Logger.Error("Error getting output size");
+                }
+            }
+            else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
+            {
+                // HLS - time-based consideration
+
+                var targetGap = _gapLengthInTicks;
+                var gap = transcodingPositionTicks - downloadPositionTicks;
+
+                if (gap < targetGap)
+                {
+                    //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
+                    return 0;
+                }
+
+                //Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
+            }
+            else
+            {
+                //Logger.Debug("No throttle data for " + path);
+            }
+
+            return originalBytesPerSecond;
+        }
+
         /// <summary>
         /// Gets the static remote stream result.
         /// </summary>
@@ -325,17 +395,18 @@ namespace MediaBrowser.Api.Playback.Progressive
             await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
             try
             {
+                TranscodingJob job;
+
                 if (!File.Exists(outputPath))
                 {
-                    await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
+                    job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
                 }
                 else
                 {
-                    ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
+                    job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
                     state.Dispose();
                 }
 
-                var job = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
                 var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
 
                 result.Options["Content-Type"] = contentType;

+ 26 - 3
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -1,7 +1,8 @@
-using System;
+using System.Threading;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Model.Logging;
 using ServiceStack.Web;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
@@ -73,7 +74,10 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
             finally
             {
-                ApiEntryPoint.Instance.OnTranscodeEndRequest(Path, TranscodingJobType.Progressive);
+                if (_job != null)
+                {
+                    ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
+                }
             }
         }
     }
@@ -83,6 +87,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         private readonly IFileSystem _fileSystem;
         private readonly TranscodingJob _job;
 
+        private long _bytesWritten = 0;
+
         public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
         {
             _fileSystem = fileSystem;
@@ -98,7 +104,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
                 while (eofCount < 15)
                 {
-                    await fs.CopyToAsync(outputStream).ConfigureAwait(false);
+                    await CopyToAsyncInternal(fs, outputStream, 81920, CancellationToken.None).ConfigureAwait(false);
 
                     var fsPosition = fs.Position;
 
@@ -123,5 +129,22 @@ namespace MediaBrowser.Api.Playback.Progressive
                 }
             }
         }
+
+        private async Task CopyToAsyncInternal(Stream source, Stream destination, int bufferSize, CancellationToken cancellationToken)
+        {
+            byte[] array = new byte[bufferSize];
+            int count;
+            while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
+            {
+                await destination.WriteAsync(array, 0, count, cancellationToken).ConfigureAwait(false);
+
+                _bytesWritten += count;
+
+                if (_job != null)
+                {
+                    _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
+                }
+            }
+        }
     }
 }

+ 2 - 9
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -84,14 +84,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        /// <summary>
-        /// Gets the command line arguments.
-        /// </summary>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
-        /// <returns>System.String.</returns>
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, string transcodingJobId, StreamState state, bool isEncoding)
         {
             // Get the output codec name
             var videoCodec = state.OutputVideoCodec;
@@ -110,7 +103,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
                 inputModifier,
-                GetInputArgument(state),
+                GetInputArgument(transcodingJobId, state),
                 keyFrame,
                 GetMapArgs(state),
                 GetVideoArguments(state, videoCodec),

+ 1 - 0
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -72,6 +72,7 @@ namespace MediaBrowser.Api.Playback
         public string Params { get; set; }
 
         public bool Throttle { get; set; }
+        public string TranscodingJobId { get; set; }
     }
 
     public class VideoStreamRequest : StreamRequest

+ 3 - 3
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -205,7 +205,7 @@ namespace MediaBrowser.Controller.Entities
             list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false));
             list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false));
             list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, query);
         }
@@ -283,7 +283,7 @@ namespace MediaBrowser.Controller.Entities
             list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false));
             list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false));
             //list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false));
 
             return GetResult(list, query);
         }
@@ -301,7 +301,7 @@ namespace MediaBrowser.Controller.Entities
             list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false));
             list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false));
             list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
+            //list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false));
 
             return GetResult(list, query);
         }

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -363,7 +363,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
     <PreBuildEvent>
     </PreBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 3 - 0
MediaBrowser.Controller/Net/StaticResultOptions.cs

@@ -21,6 +21,9 @@ namespace MediaBrowser.Controller.Net
         public bool Throttle { get; set; }
         public long ThrottleLimit { get; set; }
         public long MinThrottlePosition { get; set; }
+        public Func<long, long, long> ThrottleCallback { get; set; }
+
+        public Action OnComplete { get; set; }
 
         public StaticResultOptions()
         {

+ 1 - 13
MediaBrowser.Dlna/Ssdp/Datagram.cs

@@ -22,8 +22,6 @@ namespace MediaBrowser.Dlna.Ssdp
         /// </summary>
         public int SendCount { get; private set; }
 
-        public bool HandleBindError { get; set; }
-
         private readonly ILogger _logger;
 
         public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount)
@@ -44,17 +42,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
                 if (FromEndPoint != null)
                 {
-                    try
-                    {
-                        client.Bind(FromEndPoint);
-                    }
-                    catch
-                    {
-                        if (!HandleBindError)
-                        {
-                            throw;
-                        }
-                    }
+                    client.Bind(FromEndPoint);
                 }
 
                 client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result =>

+ 3 - 7
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -124,22 +124,18 @@ namespace MediaBrowser.Dlna.Ssdp
             IPEndPoint localAddress,
             int sendCount = 1)
         {
-            SendDatagram(header, values, _ssdpEndp, localAddress, false, sendCount);
+            SendDatagram(header, values, _ssdpEndp, localAddress, sendCount);
         }
 
         public void SendDatagram(string header,
             Dictionary<string, string> values,
             IPEndPoint endpoint,
             IPEndPoint localAddress,
-            bool handleBindError,
             int sendCount = 1)
         {
             var msg = new SsdpMessageBuilder().BuildMessage(header, values);
 
-            var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount)
-            {
-                HandleBindError = handleBindError
-            };
+            var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount);
 
             if (_messageQueue.Count == 0)
             {
@@ -175,7 +171,7 @@ namespace MediaBrowser.Dlna.Ssdp
                     values["ST"] = d.Type;
                     values["USN"] = d.USN;
 
-                    SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), true);
+                    SendDatagram(header, values, endpoint, null);
 
                     if (_config.GetDlnaConfiguration().EnableDebugLogging)
                     {

+ 1 - 1
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -91,7 +91,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 1 - 1
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -372,7 +372,7 @@
 xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
 )</PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <Import Project="Fody.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 1 - 1
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -211,7 +211,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 2 - 2
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -378,8 +378,8 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (album != null)
             {
-                dto.Album = item.Name;
-                dto.AlbumId = item.Id.ToString("N");
+                dto.Album = album.Name;
+                dto.AlbumId = album.Id.ToString("N");
             }
         }
 

+ 9 - 34
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -102,14 +102,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             return result;
         }
 
-        private bool SupportsCompression
-        {
-            get
-            {
-                return true;
-            }
-        }
-
         /// <summary>
         /// Gets the optimized result.
         /// </summary>
@@ -127,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 throw new ArgumentNullException("result");
             }
 
-            var optimizedResult = SupportsCompression ? requestContext.ToOptimizedResult(result) : result;
+            var optimizedResult = requestContext.ToOptimizedResult(result);
 
             if (responseHeaders != null)
             {
@@ -471,7 +463,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                     {
                         Throttle = options.Throttle,
                         ThrottleLimit = options.ThrottleLimit,
-                        MinThrottlePosition = options.MinThrottlePosition
+                        MinThrottlePosition = options.MinThrottlePosition,
+                        ThrottleCallback = options.ThrottleCallback,
+                        OnComplete = options.OnComplete
                     };
                 }
 
@@ -488,39 +482,20 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 {
                     Throttle = options.Throttle,
                     ThrottleLimit = options.ThrottleLimit,
-                    MinThrottlePosition = options.MinThrottlePosition
+                    MinThrottlePosition = options.MinThrottlePosition,
+                    ThrottleCallback = options.ThrottleCallback,
+                    OnComplete = options.OnComplete
                 };
             }
 
             string content;
-            long originalContentLength = 0;
 
             using (var stream = await factoryFn().ConfigureAwait(false))
             {
-                using (var memoryStream = new MemoryStream())
-                {
-                    await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-                    memoryStream.Position = 0;
-
-                    originalContentLength = memoryStream.Length;
-
-                    using (var reader = new StreamReader(memoryStream))
-                    {
-                        content = await reader.ReadToEndAsync().ConfigureAwait(false);
-                    }
-                }
-            }
-
-            if (!SupportsCompression)
-            {
-                responseHeaders["Content-Length"] = originalContentLength.ToString(UsCulture);
-
-                if (isHeadRequest)
+                using (var reader = new StreamReader(stream))
                 {
-                    return GetHttpResult(new byte[] { }, contentType);
+                    content = await reader.ReadToEndAsync().ConfigureAwait(false);
                 }
-
-                return new HttpResult(content, contentType);
             }
 
             var contents = content.Compress(requestedCompressionType);

+ 25 - 12
MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -27,6 +27,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         public bool Throttle { get; set; }
         public long ThrottleLimit { get; set; }
         public long MinThrottlePosition;
+        public Func<long, long, long> ThrottleCallback { get; set; }
+        public Action OnComplete { get; set; }
 
         /// <summary>
         /// The _options
@@ -167,7 +169,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             {
                 responseStream = new ThrottledStream(responseStream, ThrottleLimit)
                 {
-                    MinThrottlePosition = MinThrottlePosition
+                    MinThrottlePosition = MinThrottlePosition,
+                    ThrottleCallback = ThrottleCallback
                 };
             }
             var task = WriteToAsync(responseStream);
@@ -182,22 +185,32 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <returns>Task.</returns>
         private async Task WriteToAsync(Stream responseStream)
         {
-            // Headers only
-            if (IsHeadRequest)
+            try
             {
-                return;
-            }
+                // Headers only
+                if (IsHeadRequest)
+                {
+                    return;
+                }
 
-            using (var source = SourceStream)
-            {
-                // If the requested range is "0-", we can optimize by just doing a stream copy
-                if (RangeEnd >= TotalContentLength - 1)
+                using (var source = SourceStream)
                 {
-                    await source.CopyToAsync(responseStream).ConfigureAwait(false);
+                    // If the requested range is "0-", we can optimize by just doing a stream copy
+                    if (RangeEnd >= TotalContentLength - 1)
+                    {
+                        await source.CopyToAsync(responseStream).ConfigureAwait(false);
+                    }
+                    else
+                    {
+                        await CopyToAsyncInternal(source, responseStream, Convert.ToInt32(RangeLength), CancellationToken.None).ConfigureAwait(false);
+                    }
                 }
-                else
+            }
+            finally
+            {
+                if (OnComplete != null)
                 {
-                    await CopyToAsyncInternal(source, responseStream, Convert.ToInt32(RangeLength), CancellationToken.None).ConfigureAwait(false);
+                    OnComplete();
                 }
             }
         }

+ 11 - 1
MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs

@@ -39,6 +39,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         public bool Throttle { get; set; }
         public long ThrottleLimit { get; set; }
         public long MinThrottlePosition;
+        public Func<long, long, long> ThrottleCallback { get; set; }
+        public Action OnComplete { get; set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
@@ -85,7 +87,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             {
                 responseStream = new ThrottledStream(responseStream, ThrottleLimit)
                 {
-                    MinThrottlePosition = MinThrottlePosition
+                    MinThrottlePosition = MinThrottlePosition,
+                    ThrottleCallback = ThrottleCallback
                 };
             }
             var task = WriteToAsync(responseStream);
@@ -113,6 +116,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 throw;
             }
+            finally
+            {
+                if (OnComplete != null)
+                {
+                    OnComplete();
+                }
+            }
         }
     }
 }

+ 31 - 15
MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs

@@ -15,6 +15,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// </summary>
         public const long Infinite = 0;
 
+        public Func<long, long, long> ThrottleCallback { get; set; }
+        
         #region Private members
         /// <summary>
         /// The base stream.
@@ -278,22 +280,42 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         }
         #endregion
 
-        #region Protected methods
-        /// <summary>
-        /// Throttles for the specified buffer size in bytes.
-        /// </summary>
-        /// <param name="bufferSizeInBytes">The buffer size in bytes.</param>
-        protected void Throttle(int bufferSizeInBytes)
+        private bool ThrottleCheck(int bufferSizeInBytes)
         {
             if (_bytesWritten < MinThrottlePosition)
             {
-                return;
+                return false;
             }
 
             // Make sure the buffer isn't empty.
             if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
             {
-                return;
+                return false;
+            }
+
+            if (ThrottleCallback != null)
+            {
+                var val = ThrottleCallback(_maximumBytesPerSecond, _bytesWritten);
+
+                if (val == 0)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        #region Protected methods
+        /// <summary>
+        /// Throttles for the specified buffer size in bytes.
+        /// </summary>
+        /// <param name="bufferSizeInBytes">The buffer size in bytes.</param>
+        protected void Throttle(int bufferSizeInBytes)
+        {
+            if (!ThrottleCheck(bufferSizeInBytes))
+            {
+                return ;
             }
 
             _byteCount += bufferSizeInBytes;
@@ -332,13 +354,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
         protected async Task ThrottleAsync(int bufferSizeInBytes, CancellationToken cancellationToken)
         {
-            if (_bytesWritten < MinThrottlePosition)
-            {
-                return;
-            }
-
-            // Make sure the buffer isn't empty.
-            if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
+            if (!ThrottleCheck(bufferSizeInBytes))
             {
                 return;
             }

+ 1 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -501,7 +501,7 @@
   </ItemGroup>
   <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

+ 1 - 1
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -2136,7 +2136,7 @@
     <PostBuildEvent>
     </PostBuildEvent>
   </PropertyGroup>
-  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+  <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">