I'm serving video from an MVC3 site, with the controller action that returns the video returning a FilePathResult, and when trying to play back in the browser, I'm seeing some frustrating issues, regardless of my using video.js or mediaelement.js.
- Chrome doesn't let you change the position using progressbar, nor does it allow you to replay the video once it has completed
- IE9 seems relatively fine
- Firefox doesn't show the elapsed/remaining time correctly
However, if I just give a relative path to the file being hosted, it all works fine. The videos need to be available only to users who belong to certain roles, so that isn't really an option.
The Action:
[Authorize]
public ActionResult Video(string fileName)
{
var pathBase = Server.MapPath("~/Downloads/Videos/");
var filePath = pathBase + fileName;
var contentType = ContentType(fileName);
return new FilePathResult(filePath, contentType) { FileDownloadName = fileName };
}
The Razor:
<!-- @t = the video entity -->
<video width="640" height="360" id="@t.Id" poster="@Url.Action("Video", "Download", new { fileName = @t.Poster })" controls="controls" preload="none">
<!-- MP4 source must come first for iOS -->
<source src="@Url.Action("Video", "Download", new { fileName = @t.Mp4 })" type='video/mp4' />
<!-- WebM for Firefox 4 and Opera -->
<source src="@Url.Action("Video", "Download", new { fileName = @t.WebM })" type='video/webm' />
<!-- OGG for Firefox 3 -->
<source src="@Url.Action("Video", "Download", new { fileName = @t.Ogv })" type='video/ogg' />
<!-- Fallback flash player for no-HTML5 browsers with JavaScript turned off -->
<object width="640" height="360" type="application/x-shockwave-flash" data="@Url.Content("~/Content/flashmediaelement.swf")">
<param name="movie" value="@Url.Content("~/Content/flashmediaelement.swf")" />
<param name="flashvars" value="controls=true&poster=@Url.Action("Video", "Download", new { fileName = @t.Poster })&file=@Url.Action("Video", "Download", new { fileName = @t.Mp4 })" />
<!-- Image fall back for non-HTML5 browser with JavaScript turned off and no Flash player installed -->
<img src="@Url.Action("Video", "Download", new { fileName = @t.Poster })" width="640" height="360" alt="@t.Title"
title="No video playback capabilities" />
</object>
</video>
I ended up writing an HTTP Handler to deal with these extensions, though it seems Chrome's issue is to do with my handler not supporting Range requests.
I used the following blog post to help me out: http://blogs.visigo.com/chriscoulson/easy-handling-of-http-range-requests-in-asp-net/. The solution (modified by me to include content type, as well as some basic security) is as follows:
public void ProcessRequest(HttpContext context)
{
if (!context.Request.RequestContext.HttpContext.User.Identity.IsAuthenticated)
context.Response.Redirect("~");
var path =
context.Request.RequestContext.HttpContext.Server.MapPath(
context.Request.AppRelativeCurrentExecutionFilePath);
long size, start, end, length, fp = 0;
using (StreamReader reader = new StreamReader(path))
{
size = reader.BaseStream.Length;
start = 0;
end = size - 1;
length = size;
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
context.Response.AddHeader("Accept-Ranges", "0-" + size);
context.Response.ContentType = "video/mp4";
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
{
long anotherStart = start;
long anotherEnd = end;
string[] arr_split =
context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] {Convert.ToChar("=")});
string range = arr_split[1];
// Make sure the client hasn't sent us a multibyte range
if (range.IndexOf(",") > -1)
{
// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if (range.StartsWith("-"))
{
// The n-number of the last bytes is requested
anotherStart = size - Convert.ToInt64(range.Substring(1));
}
else
{
arr_split = range.Split(new char[] {Convert.ToChar("-")});
anotherStart = Convert.ToInt64(arr_split[0]);
long temp = 0;
anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp))
? Convert.ToInt64(arr_split[1])
: size;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
anotherEnd = (anotherEnd > end) ? end : anotherEnd;
// Validate the requested range and return an error if it's not correct.
if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
{
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
start = anotherStart;
end = anotherEnd;
length = end - start + 1; // Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
context.Response.StatusCode = 206;
}
}
// Notify the client the byte range we'll be outputting
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
context.Response.AddHeader("Content-Length", length.ToString());
// Start buffered download
context.Response.WriteFile(path, fp, length);
context.Response.Flush();
}
Thanks for your answer!
I used something similar:
internal static void StreamVideo(string fullpath, HttpContextBase context)
{
long size, start, end, length, fp = 0;
using (StreamReader reader = new StreamReader(fullpath))
{
size = reader.BaseStream.Length;
start = 0;
end = size - 1;
length = size;
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
context.Response.AddHeader("Accept-Ranges", "0-" + size);
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
{
long anotherStart = start;
long anotherEnd = end;
string[] arr_split = context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] { Convert.ToChar("=") });
string range = arr_split[1];
// Make sure the client hasn't sent us a multibyte range
if (range.IndexOf(",") > -1)
{
// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if (range.StartsWith("-"))
{
// The n-number of the last bytes is requested
anotherStart = size - Convert.ToInt64(range.Substring(1));
}
else
{
arr_split = range.Split(new char[] { Convert.ToChar("-") });
anotherStart = Convert.ToInt64(arr_split[0]);
long temp = 0;
anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
anotherEnd = (anotherEnd > end) ? end : anotherEnd;
// Validate the requested range and return an error if it's not correct.
if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
{
context.Response.ContentType = MimeMapping.GetMimeMapping(fullpath);
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
throw new HttpException(416, "Requested Range Not Satisfiable");
}
start = anotherStart;
end = anotherEnd;
length = end - start + 1; // Calculate new content length
fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
context.Response.StatusCode = 206;
}
}
// Notify the client the byte range we'll be outputting
context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
context.Response.AddHeader("Content-Length", length.ToString());
// Start buffered download
context.Response.WriteFile(fullpath, fp, length);
context.Response.End();
}
来源:https://stackoverflow.com/questions/11837681/html5-video-from-mvc3-action-not-working-correctly