HTML5 video - ashx handler - seek

3

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?

html
video
video-streaming
html5-video
ashx
asked on Stack Overflow Jun 23, 2014 by BenG • edited May 23, 2017 by Community

2 Answers

5

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); }
    }
answered on Stack Overflow Jun 25, 2014 by BenG • edited Apr 13, 2017 by BenG
1

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));
answered on Stack Overflow Dec 3, 2020 by Sheeraz ALI • edited Dec 3, 2020 by try catch finally

User contributions licensed under CC BY-SA 3.0