Sfoglia il codice sorgente

restore nuget targets for mono build

Luke Pulverenti 10 anni fa
parent
commit
60d1d5cdee
27 ha cambiato i file con 395 aggiunte e 250 eliminazioni
  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">