Selaa lähdekoodia

Re-worked async actions in BaseHandler, and changed AudioBitRate to AudioBitRates.

LukePulverenti Luke Pulverenti luke pulverenti 13 vuotta sitten
vanhempi
sitoutus
24d2c441b3

+ 122 - 122
MediaBrowser.Api/HttpHandlers/AudioHandler.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Net;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Logging;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net.Handlers;
@@ -12,54 +13,8 @@ using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class AudioHandler : BaseHandler
+    public class AudioHandler : BaseMediaHandler<Audio>
     {
-        private Audio _LibraryItem;
-        /// <summary>
-        /// Gets the library item that will be played, if any
-        /// </summary>
-        private Audio LibraryItem
-        {
-            get
-            {
-                if (_LibraryItem == null)
-                {
-                    string id = QueryString["id"];
-
-                    if (!string.IsNullOrEmpty(id))
-                    {
-                        _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as Audio;
-                    }
-                }
-
-                return _LibraryItem;
-            }
-        }
-
-        public override bool CompressResponse
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        protected override bool IsAsyncHandler
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        public override string ContentType
-        {
-            get
-            {
-                return MimeTypes.GetMimeType("." + GetOutputFormat());
-            }
-        }
-
         public IEnumerable<string> AudioFormats
         {
             get
@@ -75,115 +30,70 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        public int? AudioBitRate
+        public IEnumerable<int> AudioBitRates
         {
             get
             {
-                string val = QueryString["audiobitrate"];
-
-                if (string.IsNullOrEmpty(val))
-                {
-                    return null;
-                }
-
-                return int.Parse(val);
-            }
-        }
-
-        public int? AudioChannels
-        {
-            get
-            {
-                string val = QueryString["audiochannels"];
-
-                if (string.IsNullOrEmpty(val))
-                {
-                    return null;
-                }
-
-                return int.Parse(val);
-            }
-        }
-
-        public int? AudioSampleRate
-        {
-            get
-            {
-                string val = QueryString["audiosamplerate"];
+                string val = QueryString["audioformats"];
 
                 if (string.IsNullOrEmpty(val))
                 {
-                    return 44100;
+                    return new int[] { };
                 }
 
-                return int.Parse(val);
+                return val.Split(',').Select(v => int.Parse(v));
             }
         }
 
-        public override void ProcessRequest(HttpListenerContext ctx)
+        private int? GetMaxAcceptedBitRate(string audioFormat)
         {
-            HttpListenerContext = ctx;
-
-            if (!RequiresTranscoding())
-            {
-                new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
-                return;
-            }
+            int index = AudioFormats.ToList().IndexOf(audioFormat);
 
-            base.ProcessRequest(ctx);
+            return AudioBitRates.ElementAtOrDefault(index);
         }
 
         /// <summary>
         /// Determines whether or not the original file requires transcoding
         /// </summary>
-        private bool RequiresTranscoding()
+        protected override bool RequiresConversion()
         {
+            string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty);
+
             // If it's not in a format the consumer accepts, return true
-            if (!AudioFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
+            if (!AudioFormats.Any(f => currentFormat.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
             {
                 return true;
             }
 
+            int? bitrate = GetMaxAcceptedBitRate(currentFormat);
+
             // If the bitrate is greater than our desired bitrate, we need to transcode
-            if (AudioBitRate.HasValue)
+            if (bitrate.HasValue && bitrate.Value < LibraryItem.BitRate)
             {
-                if (AudioBitRate.Value < LibraryItem.BitRate)
-                {
-                    return true;
-                }
+                return true;
             }
 
             // If the number of channels is greater than our desired channels, we need to transcode
-            if (AudioChannels.HasValue)
+            if (AudioChannels.HasValue && AudioChannels.Value < LibraryItem.Channels)
             {
-                if (AudioChannels.Value < LibraryItem.Channels)
-                {
-                    return true;
-                }
+                return true;
             }
 
             // If the sample rate is greater than our desired sample rate, we need to transcode
-            if (AudioSampleRate.HasValue)
+            if (AudioSampleRate.HasValue && AudioSampleRate.Value < LibraryItem.SampleRate)
             {
-                if (AudioSampleRate.Value < LibraryItem.SampleRate)
-                {
-                    return true;
-                }
+                return true;
             }
 
             // Yay
             return false;
         }
 
-        private string GetOutputFormat()
+        /// <summary>
+        /// Gets the format we'll be converting to
+        /// </summary>
+        protected override string GetOutputFormat()
         {
-            string format = AudioFormats.FirstOrDefault(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase));
-
-            if (!string.IsNullOrWhiteSpace(format))
-            {
-                return format;
-            }
-
             return AudioFormats.First();
         }
 
@@ -194,9 +104,13 @@ namespace MediaBrowser.Api.HttpHandlers
         {
             List<string> audioTranscodeParams = new List<string>();
 
-            if (AudioBitRate.HasValue)
+            string outputFormat = GetOutputFormat();
+
+            int? bitrate = GetMaxAcceptedBitRate(outputFormat);
+
+            if (bitrate.HasValue)
             {
-                audioTranscodeParams.Add("-ab " + AudioBitRate.Value);
+                audioTranscodeParams.Add("-ab " + bitrate.Value);
             }
 
             if (AudioChannels.HasValue)
@@ -209,12 +123,12 @@ namespace MediaBrowser.Api.HttpHandlers
                 audioTranscodeParams.Add("-ar " + AudioSampleRate.Value);
             }
 
-            audioTranscodeParams.Add("-f " + GetOutputFormat());
+            audioTranscodeParams.Add("-f " + outputFormat);
 
             return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -";
         }
 
-        protected async override void WriteResponseToOutputStream(Stream stream)
+        protected async override Task WriteResponseToOutputStream(Stream stream)
         {
             ProcessStartInfo startInfo = new ProcessStartInfo();
 
@@ -227,7 +141,7 @@ namespace MediaBrowser.Api.HttpHandlers
             startInfo.WorkingDirectory = ApiService.FFMpegDirectory;
             startInfo.Arguments = GetAudioArguments();
 
-            Logger.LogInfo("Audio Handler Transcode: " + ApiService.FFMpegPath + " " + startInfo.Arguments);
+            Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
 
             Process process = new Process();
             process.StartInfo = startInfo;
@@ -244,10 +158,96 @@ namespace MediaBrowser.Api.HttpHandlers
             }
             finally
             {
-                DisposeResponseStream();
-
                 process.Dispose();
             }
         }
     }
+
+    public abstract class BaseMediaHandler<T> : BaseHandler
+        where T : BaseItem, new()
+    {
+        private T _LibraryItem;
+        /// <summary>
+        /// Gets the library item that will be played, if any
+        /// </summary>
+        protected T LibraryItem
+        {
+            get
+            {
+                if (_LibraryItem == null)
+                {
+                    string id = QueryString["id"];
+
+                    if (!string.IsNullOrEmpty(id))
+                    {
+                        _LibraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as T;
+                    }
+                }
+
+                return _LibraryItem;
+            }
+        }
+
+        public int? AudioChannels
+        {
+            get
+            {
+                string val = QueryString["audiochannels"];
+
+                if (string.IsNullOrEmpty(val))
+                {
+                    return null;
+                }
+
+                return int.Parse(val);
+            }
+        }
+
+        public int? AudioSampleRate
+        {
+            get
+            {
+                string val = QueryString["audiosamplerate"];
+
+                if (string.IsNullOrEmpty(val))
+                {
+                    return 44100;
+                }
+
+                return int.Parse(val);
+            }
+        }
+
+        public override string ContentType
+        {
+            get
+            {
+                return MimeTypes.GetMimeType("." + GetOutputFormat());
+            }
+        }
+
+        public override bool CompressResponse
+        {
+            get
+            {
+                return false;
+            }
+        }
+
+        public override void ProcessRequest(HttpListenerContext ctx)
+        {
+            HttpListenerContext = ctx;
+
+            if (!RequiresConversion())
+            {
+                new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
+                return;
+            }
+
+            base.ProcessRequest(ctx);
+        }
+
+        protected abstract string GetOutputFormat();
+        protected abstract bool RequiresConversion();
+    }
 }

+ 6 - 2
MediaBrowser.Api/HttpHandlers/ImageHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
@@ -148,9 +149,12 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        protected override void WriteResponseToOutputStream(Stream stream)
+        protected override Task WriteResponseToOutputStream(Stream stream)
         {
-            ImageProcessor.ProcessImage(ImagePath, stream, Width, Height, MaxWidth, MaxHeight, Quality);
+            return Task.Run(() =>
+            {
+                ImageProcessor.ProcessImage(ImagePath, stream, Width, Height, MaxWidth, MaxHeight, Quality);
+            });
         }
 
         private string GetImagePath()

+ 6 - 2
MediaBrowser.Api/HttpHandlers/JsonHandler.cs

@@ -1,4 +1,5 @@
 using System.IO;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Common.Serialization;
 
@@ -8,9 +9,12 @@ namespace MediaBrowser.Api.HttpHandlers
     {
         protected abstract object ObjectToSerialize { get; }
 
-        protected override void WriteResponseToOutputStream(Stream stream)
+        protected override Task WriteResponseToOutputStream(Stream stream)
         {
-            JsonSerializer.SerializeToStream(ObjectToSerialize, stream);
+            return Task.Run(() =>
+            {
+                JsonSerializer.SerializeToStream(ObjectToSerialize, stream);
+            });
         }
     }
 }

+ 62 - 0
MediaBrowser.Api/HttpHandlers/VideoHandler.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Net.Handlers;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Api.HttpHandlers
+{
+    class VideoHandler : BaseMediaHandler<Video>
+    {
+        public IEnumerable<string> VideoFormats
+        {
+            get
+            {
+                return QueryString["videoformats"].Split(',');
+            }
+        }
+
+        /// <summary>
+        /// Gets the format we'll be converting to
+        /// </summary>
+        protected override string GetOutputFormat()
+        {
+            return VideoFormats.First();
+        }
+
+        protected override bool RequiresConversion()
+        {
+            // If it's not in a format the consumer accepts, return true
+            if (!VideoFormats.Any(f => LibraryItem.Path.EndsWith(f, StringComparison.OrdinalIgnoreCase)))
+            {
+                return true;
+            }
+            
+            AudioStream audio = LibraryItem.AudioStreams.FirstOrDefault();
+
+            if (audio != null)
+            {
+                // If the number of channels is greater than our desired channels, we need to transcode
+                if (AudioChannels.HasValue && AudioChannels.Value < audio.Channels)
+                {
+                    return true;
+                }
+            }
+
+            // Yay
+            return false;
+        }
+
+        protected override Task WriteResponseToOutputStream(Stream stream)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 4 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -63,6 +63,7 @@
     <Compile Include="HttpHandlers\StudiosHandler.cs" />
     <Compile Include="HttpHandlers\UserConfigurationHandler.cs" />
     <Compile Include="HttpHandlers\UsersHandler.cs" />
+    <Compile Include="HttpHandlers\VideoHandler.cs" />
     <Compile Include="ImageProcessor.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
@@ -87,6 +88,9 @@
   <ItemGroup>
     <EmbeddedResource Include="ffmpeg\ffmpeg.exe" />
   </ItemGroup>
+  <ItemGroup>
+    <Content Include="ffmpeg\readme.txt" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>

+ 4 - 0
MediaBrowser.Api/Plugin.cs

@@ -93,6 +93,10 @@ namespace MediaBrowser.Api
             {
                 return new AudioHandler();
             }
+            else if (localPath.EndsWith("/api/video", StringComparison.OrdinalIgnoreCase))
+            {
+                return new VideoHandler();
+            }
 
             return null;
         }

+ 3 - 0
MediaBrowser.Api/ffmpeg/readme.txt

@@ -0,0 +1,3 @@
+This is the 32-bit static build of ffmpeg, located at:
+
+http://ffmpeg.zeranoe.com/builds/

+ 3 - 2
MediaBrowser.Common/Net/Handlers/BaseEmbeddedResourceHandler.cs

@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Net.Handlers
 {
@@ -48,9 +49,9 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        protected override void WriteResponseToOutputStream(Stream stream)
+        protected override Task WriteResponseToOutputStream(Stream stream)
         {
-            GetEmbeddedResourceStream().CopyTo(stream);
+            return GetEmbeddedResourceStream().CopyToAsync(stream);
         }
 
         protected abstract Stream GetEmbeddedResourceStream();

+ 13 - 24
MediaBrowser.Common/Net/Handlers/BaseHandler.cs

@@ -5,6 +5,7 @@ using System.IO;
 using System.IO.Compression;
 using System.Linq;
 using System.Net;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Logging;
 
 namespace MediaBrowser.Common.Net.Handlers
@@ -36,18 +37,6 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        /// <summary>
-        /// Returns true or false indicating if the handler writes to the stream asynchronously.
-        /// If so the subclass will be responsible for disposing the stream when complete.
-        /// </summary>
-        protected virtual bool IsAsyncHandler
-        {
-            get
-            {
-                return false;
-            }
-        }
-
         protected virtual bool SupportsByteRangeRequests
         {
             get
@@ -246,7 +235,7 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        private void ProcessUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
+        private async void ProcessUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
         {
             long? totalContentLength = TotalContentLength;
 
@@ -277,8 +266,6 @@ namespace MediaBrowser.Common.Net.Handlers
                 CacheResponse(ctx.Response, cacheDuration, LastDateModified);
             }
 
-            PrepareUncachedResponse(ctx, cacheDuration);
-
             // Set the status code
             ctx.Response.StatusCode = StatusCode;
 
@@ -301,9 +288,15 @@ namespace MediaBrowser.Common.Net.Handlers
                     outputStream = CompressedStream;
                 }
 
-                WriteResponseToOutputStream(outputStream);
-
-                if (!IsAsyncHandler)
+                try
+                {
+                    await WriteResponseToOutputStream(outputStream);
+                }
+                catch (Exception ex)
+                {
+                    Logger.LogException(ex);
+                }
+                finally
                 {
                     DisposeResponseStream();
                 }
@@ -315,10 +308,6 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        protected virtual void PrepareUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
-        {
-        }
-
         private void CacheResponse(HttpListenerResponse response, TimeSpan duration, DateTime? dateModified)
         {
             DateTime lastModified = dateModified ?? DateTime.Now;
@@ -328,9 +317,9 @@ namespace MediaBrowser.Common.Net.Handlers
             response.Headers[HttpResponseHeader.LastModified] = lastModified.ToString("r");
         }
 
-        protected abstract void WriteResponseToOutputStream(Stream stream);
+        protected abstract Task WriteResponseToOutputStream(Stream stream);
 
-        protected void DisposeResponseStream()
+        private void DisposeResponseStream()
         {
             if (CompressedStream != null)
             {

+ 1 - 11
MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs

@@ -117,14 +117,6 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        protected override bool IsAsyncHandler
-        {
-            get
-            {
-                return true;
-            }
-        }
-
         public override string ContentType
         {
             get
@@ -133,7 +125,7 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        protected async override void WriteResponseToOutputStream(Stream stream)
+        protected async override Task WriteResponseToOutputStream(Stream stream)
         {
             try
             {
@@ -175,8 +167,6 @@ namespace MediaBrowser.Common.Net.Handlers
                 {
                     FileStream.Dispose();
                 }
-
-                DisposeResponseStream();
             }
         }