I'm trying to use an ashx handler as an HTML5 video source. I can achieve this, but I can not move forward further on the video that what has already buffered.
I can see on the network tab using a standard MP4 source that seeking forward creates another request, but using the handler it does not.
Some of the videos are more than 1 GB.
This is what I have to get the videos working so far:
public void ProcessRequest(HttpContext context)
{
context.Response.Buffer = false;
context.Response.ContentType = "video/mp4";
FileInfo file = new FileInfo(path);
int len = (int)file.Length, bytes;
context.Response.AppendHeader("content-length", len.ToString());
byte[] buffer = new byte[1024];
Stream outStream = context.Response.OutputStream;
using (Stream stream = File.OpenRead(path))
{
while (len > 0 && (bytes = stream.Read(buffer, 0, buffer.Length)) > 0)
{
outStream.Write(buffer, 0, bytes);
len -= bytes;
}
}
}
Taken from Marck Gravells post Best way to stream files in ASP.NET:
<video id="video-player" class="video-js vjs-default-skin" controls
preload="auto" poster="MY_VIDEO_POSTER.jpg"
data-setup="{}">
<source src="/Handlers/VideoHandler.ashx" type='video/mp4'>
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a web browser that
<a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
</p>
</video>
using video.js, but the same is happening if I call the handler directly in the browser.
How can I fix this problem? It is more than likely I missing something simple.
UPDATE
After looking further I know now that I need 'Accept-Ranges'.
I have now got
context.Response.ContentType = "video/mp4";
var request = HttpContext.Current.Request;
FileInfo file = new FileInfo(filePath);
var responseLength = (int)file.Length;
var buffer = new byte[1024];
var startIndex = 0;
if (request.Headers["Range"] != null)
{
var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)");
startIndex = Parse<int>(match.Groups[1].Value);
responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? responseLength) - startIndex;
context.Response.StatusCode = (int)HttpStatusCode.PartialContent;
context.Response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + responseLength;
}
context.Response.Headers["Accept-Ranges"] = "bytes";
context.Response.Headers["Content-Length"] = responseLength.ToString();
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Buffer = false;
with help from Supporting resumable HTTP-downloads through an ASHX handler?, but now the write is giving me issues with the error
HttpException was unhandled by user code The remote host closed the connection. The error code is 0x800704CD.
I have tried:
using (Stream stream = File.OpenRead(filePath))
{
stream.Seek(startIndex, SeekOrigin.Begin);
while ((responseLength - startIndex) > 0 && (bytes = stream.Read(buffer, 0, buffer.Length)) > 0)
{
response.OutputStream.Write(buffer, 0, bytes);
responseLength -= bytes;
}
}
and
using (Stream stream = File.OpenRead(filePath))
{
stream.Seek(startIndex, SeekOrigin.Begin);
int bufferLength = buffer.Length, bytesRead;
while (responseLength > bufferLength && (bytesRead = stream.Read(buffer, 0, bufferLength)) > 0)
{
response.OutputStream.Write(buffer, 0, bytesRead);
responseLength -= bytesRead;
}
while (responseLength > 0 && (bytesRead = stream.Read(buffer, 0, responseLength)) > 0)
{
response.OutputStream.Write(buffer, 0, bytesRead);
responseLength -= bytesRead;
}
}
How can I fix this problem?
That last error was down to Chrome.
The following works fine in Firefox.
If someone knows why Chrome cancels the connection then please share.
public void ProcessRequest(HttpContext context)
{
context.Response.AddHeader("content-disposition", "filename=" + Path.GetFileName(filePath));
context.Response.ContentType = "video/mp4";
var request = HttpContext.Current.Request;
FileInfo file = new FileInfo(filePath);
var responseLength = (int)file.Length;
var buffer = new byte[512];
var startIndex = 0;
if (request.Headers["Range"] != null )
{
var match = Regex.Match(request.Headers["Range"], @"bytes=(\d*)-(\d*)");
startIndex = Parse<int>(match.Groups[1].Value);
responseLength = (Parse<int?>(match.Groups[2].Value) + 1 ?? responseLength) - startIndex;
context.Response.StatusCode = (int)HttpStatusCode.PartialContent;
context.Response.Headers["Content-Range"] = "bytes " + startIndex + "-" + (startIndex + responseLength - 1) + "/" + responseLength;
}
context.Response.Headers["Accept-Ranges"] = "bytes";
context.Response.Headers["Content-Length"] = responseLength.ToString();
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.BufferOutput = false;
FileStream fileStram = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using (fileStram)
{
fileStram.Seek(startIndex, SeekOrigin.Begin);
int bytesRead = fileStram.Read(buffer, 0, buffer.Length);
while (bytesRead > 0)
{
context.Response.OutputStream.Write(buffer, 0, bytesRead);
bytesRead = fileStram.Read(buffer, 0, buffer.Length);
}
}
}
public static T Parse<T>(object value)
{
// Convert value to string to allow conversion from types like float to int
// converter.IsValid only works since .NET4 but still returns invalid values for a few cases like NULL for Unit and not respecting locale for date validation
try { return (T)System.ComponentModel.TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(value.ToString()); }
catch (Exception) { return default(T); }
}
There is no need to open and read file. I followed that trick by just adding "Accept-Ranges"
header and translate file by context.Response.TransmitFile
.
Now the video is playing and seeking on every browser e.g Chrome, Firefox
if (fi.Extension == ".mp4")
{
//Reset and set response headers. The Accept-Ranges Bytes header is important to allow resuming videos.
context.Response.AddHeader("Accept-Ranges", "bytes");
}
docRepo.AddDocumentView(FileDetail.ID);
context.Response.TransmitFile(HttpContext.Current.Server.MapPath(General.PathConstants.Documents + Id + fi.Extension));
User contributions licensed under CC BY-SA 3.0