Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Streaming large video files .net

I am trying to stream a large file in webforms from an HttpHandler. It doesn't seem to work because its not streaming the file. Instead its reading the file into memory then sends it back to the client. I look all over for a solution and the solution are telling me that they stream the file when they are doing the same thing. My solution that stream is this:

using (Stream fileStream = File.OpenRead(path))
{
    context.Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(360.0));
    context.Response.Cache.SetCacheability(HttpCacheability.Public);
    context.Response.AppendHeader("Content-Type", "video/mp4");
    context.Response.AppendHeader("content-length", file.Length);
    byte[] buffer = new byte[1024];
    while (true)
    {
      if (context.Response.IsClientConnected)
     {
       int bytesRead = fileStream.Read(buffer, 0, buffer.Length);
       if (bytesRead == 0) break;
       context.Response.OutputStream.Write(buffer, 0, bytesRead);
       context.Response.Flush();
     }
     else
     {
       break;
     }

   }
   context.Response.End();
}

What is happening is for small files if I debug the code, it will play the video but not until it reaches the context.Respond.End() line. But for large files this will not work because it is storing the whole file in memory which will bring issues.

like image 700
Jake Avatar asked May 31 '13 17:05

Jake


2 Answers

Maxad's answer is the perfect answer .I also made some changes for .Net Core version:

<video id="myvideo" height="400" width="600" controls>
    <source src="../api/StreamApi/GetStream" type="video/mp4"/>
</video>

    [Route("api/StreamApi/GetStream")]
    [HttpGet]
    public async Task GetStream()
    {
        string filepath = @"C:\temp\car.mp4";
        string filename = Path.GetFileName(filepath);

        System.IO.Stream iStream = null;
        byte[] buffer = new Byte[4096];
        int length;
        long dataToRead;

        try
        {
            // Open the file.
            iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
                        System.IO.FileAccess.Read, System.IO.FileShare.Read);


            // Total bytes to read:
            dataToRead = iStream.Length;

            Response.Headers["Accept-Ranges"] = "bytes";
            Response.ContentType = "application/octet-stream";

            int startbyte = 0;

            if (!String.IsNullOrEmpty(Request.Headers["Range"]))
            {
                string[] range = Request.Headers["Range"].ToString().Split(new char[] { '=', '-' });
                startbyte = Int32.Parse(range[1]);
                iStream.Seek(startbyte, SeekOrigin.Begin);

                Response.StatusCode = 206;
                Response.Headers["Content-Range"] = String.Format(" bytes {0}-{1}/{2}", startbyte, dataToRead - 1, dataToRead);
            }
            var outputStream = this.Response.Body;
            while (dataToRead > 0)
            {
                // Verify that the client is connected.
                if (HttpContext.RequestAborted.IsCancellationRequested == false)
                {
                    // Read the data in buffer.
                    length = await iStream.ReadAsync(buffer, 0, buffer.Length);

                    // Write the data to the current output stream.
                    await outputStream.WriteAsync(buffer, 0, buffer.Length);
                    // Flush the data to the HTML output.
                    outputStream.Flush();

                    buffer = new Byte[buffer.Length];
                    dataToRead = dataToRead - buffer.Length;
                }
                else
                {
                    //prevent infinite loop if user disconnects
                    dataToRead = -1;
                }
            }
        }
        catch (Exception ex)
        {
            // Trap the error, if any.
          
        }
        finally
        {
            if (iStream != null)
            {
                //Close the file.
                iStream.Close();
            }
            Response.Clear();
        }
    }
like image 200
Sadjad Khazaie Avatar answered Oct 19 '22 22:10

Sadjad Khazaie


I had a similar issue, where the video had to be downloaded completely before playing.

I can see you want to stream videos, to be more specific. You have to be careful about the encoding (make sure it is streamable), don't rely on the extension only, because the person who created the file could have build the video in a wierd way, but 99% of the time you should be good. I use mediainfo. In your case should be H.264.

It also depends on browser and what you use to stream (other than backend code). For my case, I used Chrome/Html5 and .webm (VP8/Ogg Vorbis). It is working for files over 1G. Didn't test for bigger than 4G...

The code I use for download of the video:

    public void Video(string folder, string name) {
        string filepath = Server.MapPath(String.Format("{0}{1}", HttpUtility.UrlDecode(folder), name));
        string filename = name;

        System.IO.Stream iStream = null;
        byte[] buffer = new Byte[4096];
        int length;
        long dataToRead;

        try {
            // Open the file.
            iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
                        System.IO.FileAccess.Read, System.IO.FileShare.Read);


            // Total bytes to read:
            dataToRead = iStream.Length;

            Response.AddHeader("Accept-Ranges", "bytes");
            Response.ContentType = MimeType.GetMIMEType(name);

            int startbyte = 0;

            if (!String.IsNullOrEmpty(Request.Headers["Range"])) {
                string[] range = Request.Headers["Range"].Split(new char[] { '=', '-' });
                startbyte = Int32.Parse(range[1]);
                iStream.Seek(startbyte, SeekOrigin.Begin);

                Response.StatusCode = 206;
                Response.AddHeader("Content-Range", String.Format(" bytes {0}-{1}/{2}", startbyte, dataToRead - 1, dataToRead));
            }

            while (dataToRead > 0) {
                // Verify that the client is connected.
                if (Response.IsClientConnected) {
                    // Read the data in buffer.
                    length = iStream.Read(buffer, 0, buffer.Length);

                    // Write the data to the current output stream.
                    Response.OutputStream.Write(buffer, 0, buffer.Length);
                    // Flush the data to the HTML output.
                    Response.Flush();

                    buffer = new Byte[buffer.Length];
                    dataToRead = dataToRead - buffer.Length;
                } else {
                    //prevent infinite loop if user disconnects
                    dataToRead = -1;
                }
            }
        } catch (Exception ex) {
            // Trap the error, if any.
            Response.Write("Error : " + ex.Message);
        } finally {
            if (iStream != null) {
                //Close the file.
                iStream.Close();
            }
            Response.Close();
        }
    }

Make sure your response header contains everything you need.

like image 25
Maxad Avatar answered Oct 20 '22 00:10

Maxad