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(); }
Leave a Reply