Переглянути джерело

Merge pull request #1159 from Bond-009/streamjob

Trying to make sense of the streaming code
Anthony Lavado 6 роки тому
батько
коміт
f631b2ecdc

+ 7 - 8
MediaBrowser.Api/ApiEntryPoint.cs

@@ -415,7 +415,7 @@ namespace MediaBrowser.Api
         public void OnTranscodeEndRequest(TranscodingJob job)
         {
             job.ActiveRequestCount--;
-            //Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
+            Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
             if (job.ActiveRequestCount <= 0)
             {
                 PingTimer(job, false);
@@ -428,7 +428,7 @@ namespace MediaBrowser.Api
                 throw new ArgumentNullException(nameof(playSessionId));
             }
 
-            //Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
+            Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
 
             List<TranscodingJob> jobs;
 
@@ -443,7 +443,7 @@ namespace MediaBrowser.Api
             {
                 if (isUserPaused.HasValue)
                 {
-                    //Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
+                    Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
                     job.IsUserPaused = isUserPaused.Value;
                 }
                 PingTimer(job, true);
@@ -601,7 +601,6 @@ namespace MediaBrowser.Api
                     {
                         Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
 
-                        //process.Kill();
                         process.StandardInput.WriteLine("q");
 
                         // Need to wait because killing is asynchronous
@@ -701,7 +700,7 @@ namespace MediaBrowser.Api
             {
                 try
                 {
-                    //Logger.LogDebug("Deleting HLS file {0}", file);
+                    Logger.LogDebug("Deleting HLS file {0}", file);
                     _fileSystem.DeleteFile(file);
                 }
                 catch (FileNotFoundException)
@@ -840,12 +839,12 @@ namespace MediaBrowser.Api
             {
                 if (KillTimer == null)
                 {
-                    //Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+                    Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
                     KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
                 }
                 else
                 {
-                    //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
                     KillTimer.Change(intervalMs, Timeout.Infinite);
                 }
             }
@@ -864,7 +863,7 @@ namespace MediaBrowser.Api
                 {
                     var intervalMs = PingTimeout;
 
-                    //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+                    Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
                     KillTimer.Change(intervalMs, Timeout.Infinite);
                 }
             }

+ 84 - 105
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -8,7 +8,6 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -32,6 +30,8 @@ namespace MediaBrowser.Api.Playback
     /// </summary>
     public abstract class BaseStreamingService : BaseApiService
     {
+        protected virtual bool EnableOutputInSubFolder => false;
+
         /// <summary>
         /// Gets or sets the application paths.
         /// </summary>
@@ -65,15 +65,25 @@ namespace MediaBrowser.Api.Playback
         protected IFileSystem FileSystem { get; private set; }
 
         protected IDlnaManager DlnaManager { get; private set; }
+
         protected IDeviceManager DeviceManager { get; private set; }
+
         protected ISubtitleEncoder SubtitleEncoder { get; private set; }
+
         protected IMediaSourceManager MediaSourceManager { get; private set; }
+
         protected IJsonSerializer JsonSerializer { get; private set; }
 
         protected IAuthorizationContext AuthorizationContext { get; private set; }
 
         protected EncodingHelper EncodingHelper { get; set; }
 
+        /// <summary>
+        /// Gets the type of the transcoding job.
+        /// </summary>
+        /// <value>The type of the transcoding job.</value>
+        protected abstract TranscodingJobType TranscodingJobType { get; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
@@ -112,12 +122,6 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
 
-        /// <summary>
-        /// Gets the type of the transcoding job.
-        /// </summary>
-        /// <value>The type of the transcoding job.</value>
-        protected abstract TranscodingJobType TranscodingJobType { get; }
-
         /// <summary>
         /// Gets the output file extension.
         /// </summary>
@@ -133,31 +137,24 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
         {
-            var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
             var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
 
-            data += "-" + (state.Request.DeviceId ?? string.Empty);
-            data += "-" + (state.Request.PlaySessionId ?? string.Empty);
+            data += "-" + (state.Request.DeviceId ?? string.Empty)
+                 + "-" + (state.Request.PlaySessionId ?? string.Empty);
 
-            var dataHash = data.GetMD5().ToString("N");
+            var filename = data.GetMD5().ToString("N");
+            var ext = outputFileExtension.ToLowerInvariant();
+            var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
 
             if (EnableOutputInSubFolder)
             {
-                return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+                return Path.Combine(folder, filename, filename + ext);
             }
 
-            return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+            return Path.Combine(folder, filename + ext);
         }
 
-        protected virtual bool EnableOutputInSubFolder => false;
-
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        protected virtual string GetDefaultH264Preset()
-        {
-            return "superfast";
-        }
+        protected virtual string GetDefaultH264Preset() => "superfast";
 
         private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
         {
@@ -171,7 +168,6 @@ namespace MediaBrowser.Api.Playback
                 var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
                 {
                     OpenToken = state.MediaSource.OpenToken
-
                 }, cancellationTokenSource.Token).ConfigureAwait(false);
 
                 EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
@@ -209,22 +205,16 @@ namespace MediaBrowser.Api.Playback
             if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
                 var auth = AuthorizationContext.GetAuthorizationInfo(Request);
-                if (auth.User != null)
+                if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding)
                 {
-                    if (!auth.User.Policy.EnableVideoPlaybackTranscoding)
-                    {
-                        ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
+                    ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
 
-                        throw new ArgumentException("User does not have access to video transcoding");
-                    }
+                    throw new ArgumentException("User does not have access to video transcoding");
                 }
             }
 
             var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
 
-            var transcodingId = Guid.NewGuid().ToString("N");
-            var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
-
             var process = new Process()
             {
                 StartInfo = new ProcessStartInfo()
@@ -239,7 +229,7 @@ namespace MediaBrowser.Api.Playback
                     RedirectStandardInput = true,
 
                     FileName = MediaEncoder.EncoderPath,
-                    Arguments = commandLineArgs,
+                    Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true),
                     WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
 
                     ErrorDialog = false
@@ -250,7 +240,7 @@ namespace MediaBrowser.Api.Playback
             var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
                 state.Request.PlaySessionId,
                 state.MediaSource.LiveStreamId,
-                transcodingId,
+                Guid.NewGuid().ToString("N"),
                 TranscodingJobType,
                 process,
                 state.Request.DeviceId,
@@ -261,27 +251,26 @@ namespace MediaBrowser.Api.Playback
             Logger.LogInformation(commandLineLogMessage);
 
             var logFilePrefix = "ffmpeg-transcode";
-            if (state.VideoRequest != null)
+            if (state.VideoRequest != null
+                && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
-                    && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     logFilePrefix = "ffmpeg-directstream";
                 }
-                else if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                else
                 {
                     logFilePrefix = "ffmpeg-remux";
                 }
             }
 
             var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
-            Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
 
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
-            state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
+            Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
 
             var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
-            await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
+            await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
 
             process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
 
@@ -298,13 +287,10 @@ namespace MediaBrowser.Api.Playback
                 throw;
             }
 
-            // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-            //process.BeginOutputReadLine();
-
             state.TranscodingJob = transcodingJob;
 
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
+            _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
 
             // Wait for the file to exist before proceeeding
             while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@@ -368,25 +354,16 @@ namespace MediaBrowser.Api.Playback
             Logger.LogDebug("Disposing stream resources");
             state.Dispose();
 
-            try
+            if (process.ExitCode == 0)
             {
-                Logger.LogInformation("FFMpeg exited with code {0}", process.ExitCode);
+                Logger.LogInformation("FFMpeg exited with code 0");
             }
-            catch
+            else
             {
-                Logger.LogError("FFMpeg exited with an error.");
+                Logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
             }
 
-            // This causes on exited to be called twice:
-            //try
-            //{
-            //    // Dispose the process
-            //    process.Dispose();
-            //}
-            //catch (Exception ex)
-            //{
-            //    Logger.LogError(ex, "Error disposing ffmpeg.");
-            //}
+            process.Dispose();
         }
 
         /// <summary>
@@ -439,55 +416,55 @@ namespace MediaBrowser.Api.Playback
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
+                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 7)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
+                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 8)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.VideoBitRate = int.Parse(val, UsCulture);
+                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 9)
                 {
-                    request.AudioBitRate = int.Parse(val, UsCulture);
+                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
                 }
                 else if (i == 10)
                 {
-                    request.MaxAudioChannels = int.Parse(val, UsCulture);
+                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
                 }
                 else if (i == 11)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.MaxFramerate = float.Parse(val, UsCulture);
+                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 12)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.MaxWidth = int.Parse(val, UsCulture);
+                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 13)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.MaxHeight = int.Parse(val, UsCulture);
+                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 14)
                 {
-                    request.StartTimeTicks = long.Parse(val, UsCulture);
+                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
                 }
                 else if (i == 15)
                 {
@@ -500,14 +477,14 @@ namespace MediaBrowser.Api.Playback
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.MaxRefFrames = int.Parse(val, UsCulture);
+                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 17)
                 {
                     if (videoRequest != null)
                     {
-                        videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture);
+                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
                     }
                 }
                 else if (i == 18)
@@ -556,7 +533,7 @@ namespace MediaBrowser.Api.Playback
                 }
                 else if (i == 26)
                 {
-                    request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
+                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
                 }
                 else if (i == 27)
                 {
@@ -643,16 +620,25 @@ namespace MediaBrowser.Api.Playback
                 return null;
             }
 
-            if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
+            const string Npt = "npt=";
+            if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
             {
                 throw new ArgumentException("Invalid timeseek header");
             }
-            value = value.Substring(4).Split(new[] { '-' }, 2)[0];
+            int index = value.IndexOf('-');
+            if (index == -1)
+            {
+                value = value.Substring(Npt.Length);
+            }
+            else
+            {
+                value = value.Substring(Npt.Length, index);
+            }
 
             if (value.IndexOf(':') == -1)
             {
                 // Parses npt times in the format of '417.33'
-                if (double.TryParse(value, NumberStyles.Any, UsCulture, out var seconds))
+                if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
                 {
                     return TimeSpan.FromSeconds(seconds).Ticks;
                 }
@@ -667,7 +653,7 @@ namespace MediaBrowser.Api.Playback
 
             foreach (var time in tokens)
             {
-                if (double.TryParse(time, NumberStyles.Any, UsCulture, out var digit))
+                if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
                 {
                     secondsSum += digit * timeFactor;
                 }
@@ -707,7 +693,7 @@ namespace MediaBrowser.Api.Playback
             var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
                                     string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
 
-            var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
+            var state = new StreamState(MediaSourceManager, TranscodingJobType)
             {
                 Request = request,
                 RequestedUrl = url,
@@ -728,13 +714,10 @@ namespace MediaBrowser.Api.Playback
             //    state.SegmentLength = 6;
             //}
 
-            if (state.VideoRequest != null)
+            if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
             {
-                if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
-                {
-                    state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                    state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
-                }
+                state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+                state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
             }
 
             if (!string.IsNullOrWhiteSpace(request.AudioCodec))
@@ -779,12 +762,12 @@ namespace MediaBrowser.Api.Playback
                     var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
 
                     mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
-                       ? mediaSources.First()
-                       : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
+                       ? mediaSources[0]
+                       : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
 
                     if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
                     {
-                        mediaSource = mediaSources.First();
+                        mediaSource = mediaSources[0];
                     }
                 }
             }
@@ -834,11 +817,11 @@ namespace MediaBrowser.Api.Playback
                 if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     var resolution = ResolutionNormalizer.Normalize(
-                        state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
-                        state.VideoStream == null ? (int?)null : state.VideoStream.Width,
-                        state.VideoStream == null ? (int?)null : state.VideoStream.Height,
+                        state.VideoStream?.BitRate,
+                        state.VideoStream?.Width,
+                        state.VideoStream?.Height,
                         state.OutputVideoBitrate.Value,
-                        state.VideoStream == null ? null : state.VideoStream.Codec,
+                        state.VideoStream?.Codec,
                         state.OutputVideoCodec,
                         videoRequest.MaxWidth,
                         videoRequest.MaxHeight);
@@ -846,17 +829,13 @@ namespace MediaBrowser.Api.Playback
                     videoRequest.MaxWidth = resolution.MaxWidth;
                     videoRequest.MaxHeight = resolution.MaxHeight;
                 }
-
-                ApplyDeviceProfileSettings(state);
-            }
-            else
-            {
-                ApplyDeviceProfileSettings(state);
             }
 
+            ApplyDeviceProfileSettings(state);
+
             var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
                 ? GetOutputFileExtension(state)
-                : ("." + state.OutputContainer);
+                : ('.' + state.OutputContainer);
 
             var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
 
@@ -970,18 +949,18 @@ namespace MediaBrowser.Api.Playback
             responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
             responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
 
-            if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
+            if (state.RunTimeTicks.HasValue)
             {
-                if (state.RunTimeTicks.HasValue)
+                if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
                 {
                     var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
                     responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
                 }
-            }
 
-            if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null)
-            {
-                AddTimeSeekResponseHeaders(state, responseHeaders);
+                if (!isStaticallyStreamed && profile != null)
+                {
+                    AddTimeSeekResponseHeaders(state, responseHeaders);
+                }
             }
 
             if (profile == null)
@@ -1046,8 +1025,8 @@ namespace MediaBrowser.Api.Playback
 
         private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
         {
-            var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
-            var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
+            var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
+            var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
 
             responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
             responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);

+ 31 - 63
MediaBrowser.Api/Playback/StreamState.cs

@@ -1,17 +1,14 @@
 using System;
-using System.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
 
 namespace MediaBrowser.Api.Playback
 {
     public class StreamState : EncodingJobInfo, IDisposable
     {
-        private readonly ILogger _logger;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private bool _disposed = false;
 
         public string RequestedUrl { get; set; }
 
@@ -30,11 +27,6 @@ namespace MediaBrowser.Api.Playback
 
         public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
 
-        /// <summary>
-        /// Gets or sets the log file stream.
-        /// </summary>
-        /// <value>The log file stream.</value>
-        public Stream LogFileStream { get; set; }
         public IDirectStreamProvider DirectStreamProvider { get; set; }
 
         public string WaitForPath { get; set; }
@@ -72,6 +64,7 @@ namespace MediaBrowser.Api.Playback
                     {
                         return 3;
                     }
+
                     return 6;
                 }
 
@@ -94,82 +87,57 @@ namespace MediaBrowser.Api.Playback
 
         public string UserAgent { get; set; }
 
-        public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
-            : base(transcodingType)
-        {
-            _mediaSourceManager = mediaSourceManager;
-            _logger = logger;
-        }
-
         public bool EstimateContentLength { get; set; }
+
         public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
 
         public bool EnableDlnaHeaders { get; set; }
 
-        public override void Dispose()
-        {
-            DisposeTranscodingThrottler();
-            DisposeLogStream();
-            DisposeLiveStream();
+        public DeviceProfile DeviceProfile { get; set; }
 
-            TranscodingJob = null;
+        public TranscodingJob TranscodingJob { get; set; }
+
+        public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
+            : base(transcodingType)
+        {
+            _mediaSourceManager = mediaSourceManager;
         }
 
-        private void DisposeTranscodingThrottler()
+        public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
         {
-            if (TranscodingThrottler != null)
-            {
-                try
-                {
-                    TranscodingThrottler.Dispose();
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error disposing TranscodingThrottler");
-                }
+            ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+        }
 
-                TranscodingThrottler = null;
-            }
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
-        private void DisposeLogStream()
+        protected virtual void Dispose(bool disposing)
         {
-            if (LogFileStream != null)
+            if (_disposed)
             {
-                try
-                {
-                    LogFileStream.Dispose();
-                }
-                catch (Exception ex)
-                {
-                    _logger.LogError(ex, "Error disposing log stream");
-                }
-
-                LogFileStream = null;
+                return;
             }
-        }
 
-        private async void DisposeLiveStream()
-        {
-            if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
+            if (disposing)
             {
-                try
-                {
-                    await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
-                }
-                catch (Exception ex)
+                // REVIEW: Is this the right place for this?
+                if (MediaSource.RequiresClosing
+                    && string.IsNullOrWhiteSpace(Request.LiveStreamId)
+                    && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
                 {
-                    _logger.LogError(ex, "Error closing media source");
+                    _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
                 }
+
+                TranscodingThrottler?.Dispose();
             }
-        }
 
-        public DeviceProfile DeviceProfile { get; set; }
+            TranscodingThrottler = null;
+            TranscodingJob = null;
 
-        public TranscodingJob TranscodingJob;
-        public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
-        {
-            ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+            _disposed = true;
         }
     }
 }

+ 23 - 35
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -374,14 +374,14 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     if (AudioStream != null)
                     {
                         return AudioStream.SampleRate;
                     }
                 }
-
                 else if (BaseRequest.AudioSampleRate.HasValue)
                 {
                     // Don't exceed what the encoder supports
@@ -397,7 +397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     if (AudioStream != null)
                     {
@@ -405,13 +406,6 @@ namespace MediaBrowser.Controller.MediaEncoding
                     }
                 }
 
-                //else if (BaseRequest.AudioSampleRate.HasValue)
-                //{
-                //    // Don't exceed what the encoder supports
-                //    // Seeing issues of attempting to encode to 88200
-                //    return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
-                //}
-
                 return null;
             }
         }
@@ -446,7 +440,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.BitDepth;
                 }
@@ -463,7 +458,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.RefFrames;
                 }
@@ -479,7 +475,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
                 }
@@ -545,7 +542,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.CodecTag;
                 }
@@ -558,7 +556,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.IsAnamorphic;
                 }
@@ -571,14 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                var codec = OutputVideoCodec;
-
-                if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.Codec;
                 }
 
-                return codec;
+                return OutputVideoCodec;
             }
         }
 
@@ -586,14 +583,12 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                var codec = OutputAudioCodec;
-
-                if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return AudioStream?.Codec;
                 }
 
-                return codec;
+                return OutputAudioCodec;
             }
         }
 
@@ -601,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             get
             {
-                if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (BaseRequest.Static
+                    || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return VideoStream?.IsInterlaced;
                 }
@@ -636,6 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 {
                     return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
                 }
+
                 return GetMediaStreamCount(MediaStreamType.Video, 1);
             }
         }
@@ -648,17 +645,12 @@ namespace MediaBrowser.Controller.MediaEncoding
                 {
                     return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
                 }
+
                 return GetMediaStreamCount(MediaStreamType.Audio, 1);
             }
         }
 
-        public int HlsListSize
-        {
-            get
-            {
-                return 0;
-            }
-        }
+        public int HlsListSize => 0;
 
         private int? GetMediaStreamCount(MediaStreamType type, int limit)
         {
@@ -677,10 +669,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         {
             Progress.Report(percentComplete.Value);
         }
-
-        public virtual void Dispose()
-        {
-        }
     }
 
     /// <summary>

+ 4 - 3
MediaBrowser.Controller/MediaEncoding/JobLogger.cs

@@ -3,6 +3,7 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Threading.Tasks;
 using MediaBrowser.Model.Extensions;
 using Microsoft.Extensions.Logging;
 
@@ -18,10 +19,11 @@ namespace MediaBrowser.Controller.MediaEncoding
             _logger = logger;
         }
 
-        public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
+        public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
         {
             try
             {
+                using (target)
                 using (var reader = new StreamReader(source))
                 {
                     while (!reader.EndOfStream && reader.BaseStream.CanRead)
@@ -97,8 +99,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                     {
                         var currentMs = startMs + val.TotalMilliseconds;
 
-                        var percentVal = currentMs / totalMs;
-                        percent = 100 * percentVal;
+                        percent = 100.0 * currentMs / totalMs;
 
                         transcodingPosition = val;
                     }

+ 11 - 9
MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs

@@ -13,7 +13,8 @@ namespace MediaBrowser.Model.Dlna
             _profile = profile;
         }
 
-        public string BuildImageHeader(string container,
+        public string BuildImageHeader(
+            string container,
             int? width,
             int? height,
             bool isDirectStream,
@@ -28,8 +29,7 @@ namespace MediaBrowser.Model.Dlna
                             DlnaFlags.InteractiveTransferMode |
                             DlnaFlags.DlnaV15;
 
-            string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
-             DlnaMaps.FlagsToString(flagValue));
+            string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue));
 
             ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container,
                 width,
@@ -37,7 +37,7 @@ namespace MediaBrowser.Model.Dlna
 
             if (string.IsNullOrEmpty(orgPn))
             {
-                orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+                orgPn = mediaProfile?.OrgPn;
             }
 
             if (string.IsNullOrEmpty(orgPn))
@@ -50,7 +50,8 @@ namespace MediaBrowser.Model.Dlna
             return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
         }
 
-        public string BuildAudioHeader(string container,
+        public string BuildAudioHeader(
+            string container,
             string audioCodec,
             int? audioBitrate,
             int? audioSampleRate,
@@ -102,7 +103,8 @@ namespace MediaBrowser.Model.Dlna
             return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
         }
 
-        public List<string> BuildVideoHeader(string container,
+        public List<string> BuildVideoHeader(
+            string container,
             string videoCodec,
             string audioCodec,
             int? width,
@@ -206,7 +208,7 @@ namespace MediaBrowser.Model.Dlna
             return contentFeatureList;
         }
 
-        private string GetImageOrgPnValue(string container, int? width, int? height)
+        private static string GetImageOrgPnValue(string container, int? width, int? height)
         {
             MediaFormatProfile? format = new MediaFormatProfileResolver()
                 .ResolveImageFormat(container,
@@ -216,7 +218,7 @@ namespace MediaBrowser.Model.Dlna
             return format.HasValue ? format.Value.ToString() : null;
         }
 
-        private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
+        private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
         {
             MediaFormatProfile? format = new MediaFormatProfileResolver()
                 .ResolveAudioFormat(container,
@@ -227,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
             return format.HasValue ? format.Value.ToString() : null;
         }
 
-        private string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
+        private static string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
         {
             return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp);
         }