using ServiceStack.Service; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.HttpServer { public class RangeRequestWriter : IStreamWriter { /// /// Gets or sets the source stream. /// /// The source stream. private Stream SourceStream { get; set; } private HttpListenerResponse Response { get; set; } private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } /// /// Initializes a new instance of the class. /// /// The range header. /// The response. /// The source. /// if set to true [is head request]. public RangeRequestWriter(string rangeHeader, HttpListenerResponse response, Stream source, bool isHeadRequest) { RangeHeader = rangeHeader; Response = response; SourceStream = source; IsHeadRequest = isHeadRequest; } /// /// The _requested ranges /// private List> _requestedRanges; /// /// Gets the requested ranges. /// /// The requested ranges. protected IEnumerable> RequestedRanges { get { if (_requestedRanges == null) { _requestedRanges = new List>(); // Example: bytes=0-,32-63 var ranges = RangeHeader.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(start, end)); } } return _requestedRanges; } } /// /// Writes to. /// /// The response stream. public void WriteTo(Stream responseStream) { Response.Headers["Accept-Ranges"] = "bytes"; Response.StatusCode = 206; var task = WriteToAsync(responseStream); Task.WaitAll(task); } /// /// Writes to async. /// /// The response stream. /// Task. private async Task WriteToAsync(Stream responseStream) { using (var source = SourceStream) { 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) { await ServeCompleteRangeRequest(source, requestedRange, responseStream, totalLength).ConfigureAwait(false); } // This will have to buffer a portion of the content into memory await ServePartialRangeRequest(source, requestedRange.Key, requestedRange.Value.Value, responseStream, totalLength).ConfigureAwait(false); } } /// /// Handles a range request of "bytes=0-" /// This will serve the complete content and add the content-range header /// /// The source stream. /// The requested range. /// The response stream. /// Total length of the content. /// Task. private Task ServeCompleteRangeRequest(Stream sourceStream, KeyValuePair 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); // Headers only if (IsHeadRequest) { return Task.FromResult(true); } if (rangeStart > 0) { sourceStream.Position = rangeStart; } return sourceStream.CopyToAsync(responseStream); } /// /// Serves a partial range request /// /// The source stream. /// The range start. /// The range end. /// The response stream. /// Total length of the content. /// Task. private async Task ServePartialRangeRequest(Stream sourceStream, 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); // Headers only if (IsHeadRequest) { return; } 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); } } } }