Easy Handling of Http Range Requests in ASP.NET

I was recently working on adding video playing capabilities to a client’s existing ASP.NET application. They had video files that had been uploaded by users, and they wanted to allow the users to be able to preview the files on the web. I found MediaElement.js to be a excellent solution for this: As long as there were WEBM and H.264 encoded versions of the video to be played, it would handle playing the video on most browsers/devices and would fall back to a Flash video player if html video was not supported.

I did up a sample page, encoded some sample videos and everything worked great. However when I finally implemented it on the ASP.NET application only the Flash fallback player would play the videos. The HTML 5 player would not play the video.  I finally narrowed down the culprit to the way the video files were returned to the browser. Chrome and the iPhone/iPad require Http Range Requests to be supported for HTML5 video support.  Range requests allow the browser to only request a portion of the file from the server. So for videos, the user could drag the seek slider to the middle of the video and the browser would request the video from that point on, instead of downloading the full file from the server.

IIS 7 supports range requests natively, so if you are simply serving the video files directly from IIS you won’t have any problems. In this case, all file requests were being served by an ASPX page – it looked up information in a database as well as did some authorization checking before returning the file.  Unfortunately range requests are not natively supported from aspx responses – they have to be handled manually.

Scott Mitchell wrote a great article and HTTP Handler to handle this exact scenario, and it looks like a great solution. It’s very detailed and looks like it handles pretty much every edge case. Unfortunately it would require a lot of changes to fit into my client’s existing solution (and I didn’t want to rewrite the client’s solution – as it worked well for every other case), so I searched for a simpler solution.

After a lot of searching I finally found the VideoStreamer project at CodePlex by cipto0382. It includes a great little function that handles Http Range Requests.  I was able to include it in my project with very few modifications and it worked great. Chrome, Iphone and the IPad now played the video served by the ASP.Net Application.

I’ve included the function for reference below. It checks if the HTTP Request is a range request and returns a partial file with the necessary headers if it is. If it’s not a range request, it simply returns the complete file:

RangeDownload by cipto0382


private void RangeDownload(string fullpath,HttpContext 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.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();

}

7 Responses to “Easy Handling of Http Range Requests in ASP.NET”

  1. Don Nell says:

    Thanks you (and Scott) for sharing this.

    I was stuck for almost 3 days in a similar situation having to support video content on both desktop and mobile devices. No issues on any browsers except the IOS devices. Found this article, implemented the function above unchanged and all is well.

    Thanks again.

  2. James Lovett says:

    Hi Chris, thank you for this brilliant post. And thanks to Scott too! This has had me banging my head against a brick wall for weeks!

  3. Thanks says:

    Thank you, this helped me immensly, very useful.

  4. Chris says:

    How do you handle large files. If I use this code on a file larger than int32 I get:

    “The size parameter must be between zero and the maximum Int32 value.
    Parameter name: size
    Actual value was 3413381708.”

    is there away to make it work with large video files?

    Thanks,
    Chris

    • I assume it’s the Response.WriteFile that’s causing the error? You’ll have to add some logic that says if the length is greater than Int32.MaxValue, then set the length to Int32.MaxValue and adjust the ‘end’ variable accordingly.

  5. Karthic says:

    Excellent article. Thanks for the code and references.

Leave a Response

Current day month ye@r *

Trackbacks