Skip to content

Instantly share code, notes, and snippets.

@oec2003
Created December 23, 2015 14:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oec2003/85f4a3fbfdf675674ee3 to your computer and use it in GitHub Desktop.
Save oec2003/85f4a3fbfdf675674ee3 to your computer and use it in GitHub Desktop.
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment