0

I am storing video files in my sql server using a filetable and I want to watch them in a web browser. An aspx web server is handling the request. The VideoHandler.ashx handler class is for processing the request and sending the data to the browser. This is the code for streaming the video files:

            long size, start, end, length, fp = 0;
            using (StreamReader reader = new StreamReader(fullpath))
            {
                size = reader.BaseStream.Length;
                start = 0;
                end = size - 1;
                length = size;

                context.Response.AddHeader("Accept-Ranges", "0-" + size);

                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)
                    {
                        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;
                    }

                    // 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;
                    reader.Dispose();
                    reader.Close();
                }
            }
            // 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();
        }

The code is working fine as long as the video file is not too big. For example when streaming a video that has the size of 72MB everything is working fine. The problem occurs when I want to stream a video that has the size of 612MB. Then the application throws the exception:

"System.OutOfMemoryException"
Operation: GET/VideoHandler.ashx

This is the stacktrace:

System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
    at  System.Web.Hosting.IIS7WorkerRequest.SendResponseFromFileStream (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
    at  System.Web.Hosting.IIS7WorkerRequest.SendResponseFromFile (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
    at  System.Web.HttpFileResponseElement.System.Web.IHttpResponseElement.Send (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
    at  System.Web.HttpWriter.Send (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
    at  System.Web.HttpResponse.UpdateNativeResponse (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)
    at  System.Web.HttpRuntime.FinishRequestNotification (System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a)

Unfortunately I can't figure out where the problem is and what causes the exception... What I observed when watching the diagnostic tool is, that when the request comes, the process memory is not increasing but the exception is thrown. Also, the server/application is not crashing.

4
  • Does it crash on an exact line? have you tried manually writing the stream in chunks to the Response? since you already opened a File handle, so you don't have to open the file twice. Maybe the WriteFile tries to reads the whole file in first before writing away? Commented Apr 11, 2018 at 8:57
  • It doesn't crash at a specific point. When I'm debugging the code starting from the first line it runs till the end without any error. But at the second time the error occurs immediately. Commented Apr 11, 2018 at 9:13
  • Perhaps this can shed some light: support.microsoft.com/en-us/help/812406/… they provide a sample in VB which should be trivial to translate Commented Apr 11, 2018 at 9:20
  • Thank you very much for the link. This helped me a lot in understanding where the problem can be :) Commented Apr 11, 2018 at 9:42

1 Answer 1

1

Due to Mr. Clontea's link I was able to find a solution that is now working. All I had to do was changing the StreamReader to a FileStream. With this code you can stream videos at your browser and seek through it.

        long size, start, end, length, fp = 0;
        byte[] buffer = new byte[10000];
        int ilength = 0;

        using (Stream reader = new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            size = reader.Length;
            start = 0;
            end = size - 1;
            length = size;

            context.Response.AddHeader("Accept-Ranges", "0-" + size);

            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)
                {
                    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;
                }

                // 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.Seek(start, SeekOrigin.Begin);
                context.Response.StatusCode = 206;

                ilength = reader.Read(buffer, 0, 10000);
                reader.Dispose();
                reader.Close();
            }
        }
        // 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.OutputStream.Write(buffer, 0, ilength);
        HttpContext.Current.ApplicationInstance.CompleteRequest();
    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.