using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Web.Configuration; using System.Web.Http; namespace Fw.WebAPI.Controllers.FileAPI { [RoutePrefix("api/file/media")] public class MediaAPIController : ApiController { #region Fields // This will be used in copying input stream to output stream. public const int ReadStreamBufferSize = 1024 * 1024; // We have a read-only dictionary for mapping file extensions and MIME names. public static readonly IReadOnlyDictionary<string, string> MimeNames; // We will discuss this later. public static readonly IReadOnlyCollection<char> InvalidFileNameChars; // Where are your videos located at? Change the value to any folder you want. public static readonly string InitialDirectory; #endregion #region Constructors static MediaAPIController() { var mimeNames = new Dictionary<string, string>(); mimeNames.Add(".mp3", "audio/mpeg"); // List all supported media types; mimeNames.Add(".mp4", "video/mp4"); mimeNames.Add(".ogg", "application/ogg"); mimeNames.Add(".ogv", "video/ogg"); mimeNames.Add(".oga", "audio/ogg"); mimeNames.Add(".wav", "audio/x-wav"); mimeNames.Add(".webm", "video/webm"); MimeNames = new ReadOnlyDictionary<string, string>(mimeNames); InvalidFileNameChars = Array.AsReadOnly(Path.GetInvalidFileNameChars()); InitialDirectory = WebConfigurationManager.AppSettings["InitialDirectory"]; } #endregion #region Actions [Route("Play/{id}")] [HttpGet] public HttpResponseMessage Play(string id) { //从Gridfs中取文件流 IDFSHandle dsfHandle = new GridFSHandle(); Stream stream=dsfHandle.GetFile(id); // This can prevent some unnecessary accesses. // These kind of file names won‘t be existing at all. if (stream==null) throw new HttpResponseException(HttpStatusCode.NotFound); long totalLength = stream.Length; RangeHeaderValue rangeHeader = base.Request.Headers.Range; HttpResponseMessage response = new HttpResponseMessage(); response.Headers.AcceptRanges.Add("bytes"); // The request will be treated as normal request if there is no Range header. if (rangeHeader == null || !rangeHeader.Ranges.Any()) { response.StatusCode = HttpStatusCode.OK; response.Content = new PushStreamContent((outputStream, httpContent, transpContext) => { using (outputStream) // Copy the file to output stream straightforward. try { stream.CopyTo(outputStream, ReadStreamBufferSize); } catch (Exception error) { Debug.WriteLine(error); } }, GetMimeNameFromExt("mp4")); response.Content.Headers.ContentLength = totalLength; return response; } long start = 0, end = 0; // 1. If the unit is not ‘bytes‘. // 2. If there are multiple ranges in header value. // 3. If start or end position is greater than file length. if (rangeHeader.Unit != "bytes" || rangeHeader.Ranges.Count > 1 || !TryReadRangeItem(rangeHeader.Ranges.First(), totalLength, out start, out end)) { response.StatusCode = HttpStatusCode.RequestedRangeNotSatisfiable; response.Content = new StreamContent(Stream.Null); // No content for this status. response.Content.Headers.ContentRange = new ContentRangeHeaderValue(totalLength); response.Content.Headers.ContentType = GetMimeNameFromExt("mp4"); return response; } var contentRange = new ContentRangeHeaderValue(start, end, totalLength); // We are now ready to produce partial content. response.StatusCode = HttpStatusCode.PartialContent; response.Content = new PushStreamContent((outputStream, httpContent, transpContext) => { using (outputStream) // Copy the file to output stream in indicated range. CreatePartialContent(stream, outputStream, start, end); }, GetMimeNameFromExt("mp4")); response.Content.Headers.ContentLength = end - start + 1; response.Content.Headers.ContentRange = contentRange; return response; } #endregion #region Others private static bool AnyInvalidFileNameChars(string fileName) { return InvalidFileNameChars.Intersect(fileName).Any(); } private static MediaTypeHeaderValue GetMimeNameFromExt(string ext) { string value; if (MimeNames.TryGetValue(ext.ToLowerInvariant(), out value)) return new MediaTypeHeaderValue(value); else return new MediaTypeHeaderValue(MediaTypeNames.Application.Octet); } private static bool TryReadRangeItem(RangeItemHeaderValue range, long contentLength, out long start, out long end) { if (range.From != null) { start = range.From.Value; if (range.To != null) end = range.To.Value; else end = contentLength - 1; } else { end = contentLength - 1; if (range.To != null) start = contentLength - range.To.Value; else start = 0; } return (start < contentLength && end < contentLength); } private static void CreatePartialContent(Stream inputStream, Stream outputStream, long start, long end) { int count = 0; long remainingBytes = end - start + 1; long position = start; byte[] buffer = new byte[ReadStreamBufferSize]; inputStream.Position = start; do { try { if (remainingBytes > ReadStreamBufferSize) count = inputStream.Read(buffer, 0, ReadStreamBufferSize); else count = inputStream.Read(buffer, 0, (int)remainingBytes); outputStream.Write(buffer, 0, count); } catch (Exception error) { Debug.WriteLine(error); break; } position = inputStream.Position; remainingBytes = end - position + 1; } while (position <= end); } #endregion } }