diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-09-02 22:30:24 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2014-09-02 22:30:24 -0400 |
| commit | 60d1d5cdee642ee9d5be7e91be5caeb9a1a756df (patch) | |
| tree | 67dcd2270977afafc596a1c4568cdaa4cf9d26dc | |
| parent | a3d553a7fbe93fe416c940e80d5e511a2d753f25 (diff) | |
restore nuget targets for mono build
27 files changed, 395 insertions, 250 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index c51d9e7c0..b49fbce48 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/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> diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index b0131b5c2..5cb9ebb1b 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/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"> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 19f9db636..5cae6b010 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/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; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index bd17dc0b2..a3a86ba2e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/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), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 134c28524..40ec89f55 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/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), diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index f28352588..12b2b8dae 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/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); + } } } } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 346af016e..3581b9e17 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/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> diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 6fae65ffc..ae592c428 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/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()), diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index f0ad6ce5c..50929e6f3 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/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; diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index a62f3dc9f..6e77e5eab 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/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); + } + } + } } } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 639381164..f82de5a6a 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/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), diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 0de8c28a9..7568afa46 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/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 diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index f11ac70ca..bc97a43b7 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/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); } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 2ad950a4f..1813d9f08 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/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"> diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index fde08c269..5bb2c9a5c 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/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() { diff --git a/MediaBrowser.Dlna/Ssdp/Datagram.cs b/MediaBrowser.Dlna/Ssdp/Datagram.cs index 45f571b4b..2554d33c6 100644 --- a/MediaBrowser.Dlna/Ssdp/Datagram.cs +++ b/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 => diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index bee4a3a56..bac920ecb 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/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) { diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index cc753298c..22873d910 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/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"> diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 082f64bd9..a760cba05 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/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. diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 995547ce6..b69a4bc2f 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/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"> diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 922287f6e..7f04790de 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/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"); } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 9997cfbdb..e13e27d5a 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/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); diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs index 657545069..7a143c8d9 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/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(); } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs index 28fc094f7..1ca5f1204 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs +++ b/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(); + } + } } } } diff --git a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs b/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs index 067e53571..4bde30dac 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/ThrottledStream.cs +++ b/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; } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a03cae78c..313063673 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/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"> diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 864a2f70f..32cb88218 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/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">
|
