Browse Source

bring back support for byte ranged requests

LukePulverenti 12 years ago
parent
commit
e5592bd220

+ 2 - 2
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -18,8 +18,8 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
-    [Route("/Audio/{Id}/segments/{SegmentId}.mp3", "GET")]
-    [Route("/Audio/{Id}/segments/{SegmentId}.aac", "GET")]
+    [Route("/Audio/{Id}/segments/{SegmentId}/stream.mp3", "GET")]
+    [Route("/Audio/{Id}/segments/{SegmentId}/stream.aac", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsAudioSegment
     {

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

@@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback.Hls
             // The segement paths within the playlist are phsyical, so strip that out to make it relative
             fileText = fileText.Replace(Path.GetDirectoryName(playlist) + Path.DirectorySeparatorChar, string.Empty);
 
-            fileText = fileText.Replace(SegmentFilePrefix, "segments/");
+            fileText = fileText.Replace(SegmentFilePrefix, "segments/").Replace(".ts", "/stream.ts").Replace(".aac", "/stream.aac").Replace(".mp3", "/stream.mp3");
 
             // Even though we specify target duration of 9, ffmpeg seems unable to keep all segments under that amount
             fileText = fileText.Replace("#EXT-X-TARGETDURATION:9", "#EXT-X-TARGETDURATION:10");

+ 2 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
     }
 
-    [Route("/Videos/{Id}/segments/{SegmentId}.ts", "GET")]
+    [Route("/Videos/{Id}/segments/{SegmentId}/stream.ts", "GET")]
     [ServiceStack.ServiceHost.Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
     public class GetHlsVideoSegment
     {
@@ -35,6 +35,7 @@ namespace MediaBrowser.Api.Playback.Hls
             var file = SegmentFilePrefix + request.SegmentId + Path.GetExtension(Request.PathInfo);
 
             file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
+            Logger.Info(file);
 
             return ToStaticFileResult(file);
         }

+ 1 - 7
MediaBrowser.Installer/App.xaml.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
 
 namespace MediaBrowser.Installer
 {

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

@@ -87,15 +87,11 @@
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
-    <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
-    <Reference Include="System.Web" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Core" />
-    <Reference Include="System.Xml.Linq" />
-    <Reference Include="System.Data.DataSetExtensions" />
     <Reference Include="System.Xaml">
       <RequiredTargetFramework>4.0</RequiredTargetFramework>
     </Reference>

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using System.Net;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Logging;
@@ -257,6 +258,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 var stream = await factoryFn().ConfigureAwait(false);
 
+                var httpListenerResponse = (HttpListenerResponse) Response.OriginalResponse;
+                httpListenerResponse.SendChunked = false;
+
+                if (IsRangeRequest)
+                {
+                    return new RangeRequestWriter(Request.Headers, httpListenerResponse, stream);
+                }
+
+                httpListenerResponse.ContentLength64 = stream.Length;
                 return new StreamWriter(stream);
             }
 

+ 172 - 0
MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -0,0 +1,172 @@
+using ServiceStack.Service;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+    public class RangeRequestWriter : IStreamWriter
+    {
+        /// <summary>
+        /// Gets or sets the source stream.
+        /// </summary>
+        /// <value>The source stream.</value>
+        public Stream SourceStream { get; set; }
+        public HttpListenerResponse Response { get; set; }
+        public NameValueCollection RequestHeaders { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamWriter" /> class.
+        /// </summary>
+        /// <param name="requestHeaders">The request headers.</param>
+        /// <param name="response">The response.</param>
+        /// <param name="source">The source.</param>
+        public RangeRequestWriter(NameValueCollection requestHeaders, HttpListenerResponse response, Stream source)
+        {
+            RequestHeaders = requestHeaders;
+            Response = response;
+            SourceStream = source;
+        }
+
+        /// <summary>
+        /// The _requested ranges
+        /// </summary>
+        private List<KeyValuePair<long, long?>> _requestedRanges;
+        /// <summary>
+        /// Gets the requested ranges.
+        /// </summary>
+        /// <value>The requested ranges.</value>
+        protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
+        {
+            get
+            {
+                if (_requestedRanges == null)
+                {
+                    _requestedRanges = new List<KeyValuePair<long, long?>>();
+
+                    // Example: bytes=0-,32-63
+                    var ranges = RequestHeaders["Range"].Split('=')[1].Split(',');
+
+                    foreach (var range in ranges)
+                    {
+                        var vals = range.Split('-');
+
+                        long start = 0;
+                        long? end = null;
+
+                        if (!string.IsNullOrEmpty(vals[0]))
+                        {
+                            start = long.Parse(vals[0]);
+                        }
+                        if (!string.IsNullOrEmpty(vals[1]))
+                        {
+                            end = long.Parse(vals[1]);
+                        }
+
+                        _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
+                    }
+                }
+
+                return _requestedRanges;
+            }
+        }
+
+        /// <summary>
+        /// Writes to.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        public void WriteTo(Stream responseStream)
+        {
+            Response.Headers["Accept-Ranges"] = "bytes";
+            Response.StatusCode = 206;
+            
+            var task = WriteToAsync(responseStream);
+
+            Task.WaitAll(task);
+        }
+
+        /// <summary>
+        /// Writes to async.
+        /// </summary>
+        /// <param name="responseStream">The response stream.</param>
+        /// <returns>Task.</returns>
+        private Task WriteToAsync(Stream responseStream)
+        {
+            var requestedRange = RequestedRanges.First();
+
+            var totalLength = SourceStream.Length;
+
+            // If the requested range is "0-", we can optimize by just doing a stream copy
+            if (!requestedRange.Value.HasValue)
+            {
+                return ServeCompleteRangeRequest(requestedRange, responseStream, totalLength);
+            }
+
+            // This will have to buffer a portion of the content into memory
+            return ServePartialRangeRequest(requestedRange.Key, requestedRange.Value.Value, responseStream, totalLength);
+        }
+
+        /// <summary>
+        /// Handles a range request of "bytes=0-"
+        /// This will serve the complete content and add the content-range header
+        /// </summary>
+        /// <param name="requestedRange">The requested range.</param>
+        /// <param name="responseStream">The response stream.</param>
+        /// <param name="totalContentLength">Total length of the content.</param>
+        /// <returns>Task.</returns>
+        private Task ServeCompleteRangeRequest(KeyValuePair<long, long?> requestedRange, Stream responseStream, long totalContentLength)
+        {
+            var rangeStart = requestedRange.Key;
+            var rangeEnd = totalContentLength - 1;
+            var rangeLength = 1 + rangeEnd - rangeStart;
+
+            // Content-Length is the length of what we're serving, not the original content
+            Response.ContentLength64 = rangeLength;
+            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
+
+            if (rangeStart > 0)
+            {
+                SourceStream.Position = rangeStart;
+            }
+
+            return SourceStream.CopyToAsync(responseStream);
+        }
+
+        /// <summary>
+        /// Serves a partial range request
+        /// </summary>
+        /// <param name="rangeStart">The range start.</param>
+        /// <param name="rangeEnd">The range end.</param>
+        /// <param name="responseStream">The response stream.</param>
+        /// <param name="totalContentLength">Total length of the content.</param>
+        /// <returns>Task.</returns>
+        private async Task ServePartialRangeRequest(long rangeStart, long rangeEnd, Stream responseStream, long totalContentLength)
+        {
+            var rangeLength = 1 + rangeEnd - rangeStart;
+
+            // Content-Length is the length of what we're serving, not the original content
+            Response.ContentLength64 = rangeLength;
+            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
+
+            SourceStream.Position = rangeStart;
+
+            // Fast track to just copy the stream to the end
+            if (rangeEnd == totalContentLength - 1)
+            {
+                await SourceStream.CopyToAsync(responseStream).ConfigureAwait(false);
+            }
+            else
+            {
+                // Read the bytes we need
+                var buffer = new byte[Convert.ToInt32(rangeLength)];
+                await SourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
+
+                await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false);
+            }
+        }
+    }
+}

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

@@ -109,6 +109,7 @@
     <Compile Include="HttpServer\HttpResultFactory.cs" />
     <Compile Include="HttpServer\HttpServer.cs" />
     <Compile Include="HttpServer\NativeWebSocket.cs" />
+    <Compile Include="HttpServer\RangeRequestWriter.cs" />
     <Compile Include="HttpServer\ServerFactory.cs" />
     <Compile Include="HttpServer\StreamWriter.cs" />
     <Compile Include="HttpServer\SwaggerService.cs" />