Streaming for Apple iPhone/Ipad

Finally got this working.You’ve got that right ,Lack of Documentation

“Apple iPhone uses HTTP byte-ranges for requesting audio and video files. First, the Safari Web Browser requests the content, and if it's an audio or video file it opens it's media player. The media player then requests the first 2 bytes of the content, to ensure that the Webserver supports byte-range requests. Then, if it supports them, the iPhone's media player requests the rest of the content by byte-ranges and plays it.” Detail

So at first time IOS device will request byte 0-1 to see if it can accept the partial/chunk streaming.

if successful it will request for the whole content, or partial content. everything is on HTTP_Range server value during the HttpRequest,Patter: Start-End ex (0-1,0-41299).

with proper encoding of the video it will run, note: this code only support single range, not multiple.

it’s all on PHP and I’ve convert it to Asp.net. enjoy

   1: using System;
   2: using System.Web;
   3: using System.IO;
   4: using System.Collections.Generic;
   5: /// <summary>
   6: /// Support Single Range Request Only
   7: /// </summary>
   8: public class VideoStreamer : IHttpHandler {
   9:     
  10:     private void RangeDownload(string fullpath,HttpContext context)
  11:     {
  12:         long size,start,end,length,fp=0;
  13:         using (StreamReader reader = new StreamReader(fullpath))
  14:         {
  15:            
  16:             size = reader.BaseStream.Length;       
  17:             start = 0;
  18:             end = size - 1;
  19:             length = size;
  20:             // Now that we've gotten so far without errors we send the accept range header
  21:             /* At the moment we only support single ranges.
  22:              * Multiple ranges requires some more work to ensure it works correctly
  23:              * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
  24:              *
  25:              * Multirange support annouces itself with:
  26:              * header('Accept-Ranges: bytes');
  27:              *
  28:              * Multirange content must be sent with multipart/byteranges mediatype,
  29:              * (mediatype = mimetype)
  30:              * as well as a boundry header to indicate the various chunks of data.
  31:              */
  32:             context.Response.AddHeader("Accept-Ranges", "0-" + size);
  33:             // header('Accept-Ranges: bytes');
  34:             // multipart/byteranges
  35:             // http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
  36:     
  37:             if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
  38:             {
  39:                 long anotherStart = start;
  40:                 long anotherEnd = end;
  41:                 string[] arr_split = context.Request.ServerVariables["HTTP_RANGE"].Split(new char[] { Convert.ToChar("=") });
  42:                 string range = arr_split[1];
  43:  
  44:                 // Make sure the client hasn't sent us a multibyte range
  45:                 if (range.IndexOf(",") > -1)
  46:                 {
  47:                     // (?) Shoud this be issued here, or should the first
  48:                     // range be used? Or should the header be ignored and
  49:                     // we output the whole content?
  50:                     context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
  51:                     throw new HttpException(416, "Requested Range Not Satisfiable");
  52:  
  53:                 }
  54:  
  55:                 // If the range starts with an '-' we start from the beginning
  56:                 // If not, we forward the file pointer
  57:                 // And make sure to get the end byte if spesified
  58:                 if (range.StartsWith("-"))
  59:                 {
  60:                     // The n-number of the last bytes is requested
  61:                     anotherStart = size - Convert.ToInt64(range.Substring(1));
  62:                 }
  63:                 else
  64:                 {
  65:                     arr_split = range.Split(new char[] { Convert.ToChar("-") });
  66:                     anotherStart = Convert.ToInt64(arr_split[0]);
  67:                     long temp = 0;
  68:                     anotherEnd = (arr_split.Length > 1 && Int64.TryParse(arr_split[1].ToString(), out temp)) ? Convert.ToInt64(arr_split[1]) : size;
  69:                 }
  70:                 /* Check the range and make sure it's treated according to the specs.
  71:                  * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  72:                  */
  73:                 // End bytes can not be larger than $end.
  74:                 anotherEnd = (anotherEnd > end) ? end : anotherEnd;
  75:                 // Validate the requested range and return an error if it's not correct.
  76:                 if (anotherStart > anotherEnd || anotherStart > size - 1 || anotherEnd >= size)
  77:                 {
  78:  
  79:                     context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
  80:                     throw new HttpException(416, "Requested Range Not Satisfiable");
  81:                 }
  82:                 start = anotherStart;
  83:                 end = anotherEnd;
  84:  
  85:                 length = end - start + 1; // Calculate new content length
  86:                 fp = reader.BaseStream.Seek(start, SeekOrigin.Begin);
  87:                 context.Response.StatusCode = 206;
  88:             }
  89:         }
  90:             // Notify the client the byte range we'll be outputting
  91:         context.Response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + size);
  92:         context.Response.AddHeader("Content-Length", length.ToString());
  93:             // Start buffered download
  94:             context.Response.WriteFile(fullpath, fp, length);
  95:             context.Response.End();
  96:          
  97:     }
  98:  
  99:     public void ProcessRequest(HttpContext context)
 100:     {
 101:         //context.Response.Headers.Clear();
 102:         string filename = context.Request["fn"];
 103:         string mimetype = "video/mp4";
 104:         string fullpath=context.Server.MapPath("video/output/"+filename);        
 105:         if (System.IO.File.Exists(fullpath))
 106:         {
 107:             
 108:             context.Response.ContentType = mimetype;
 109:             if (!String.IsNullOrEmpty(context.Request.ServerVariables["HTTP_RANGE"]))
 110:             {
 111:                 //request for chunk
 112:                 RangeDownload(fullpath,context);
 113:             }
 114:             else
 115:             { 
 116:                 //ask for all 
 117:                     long fileLength = File.OpenRead(fullpath).Length;
 118:                     context.Response.AddHeader("Content-Length", fileLength.ToString());
 119:                     context.Response.WriteFile(fullpath);             
 120:                 
 121:                 
 122:             }
 123:         }
 124:         else
 125:         {
 126:             throw new HttpException(404, "Video Not Found Path:"+fullpath);
 127:         }
 128:     }
 129:  
 130:     public bool IsReusable {
 131:         get {
 132:             return false;
 133:         }
 134:     }
 135:  
 136: }

 

You can than use it on your html 5 video tag.

   1: <video width="340" height="285" id="htmlvidplayer" controls="" poster="/video/output/53_big.jpg" tabindex="0">
   2: <source id="mp4" src="services/VideoStreamer.ashx?fn=/video/output/53.mp4" type="video/mp4">
   3: </source>
   4: </video>

or directly Redirect to that url, because the devices have those built in quicktime player.

   1: <a href="services/VideoStreamer.ashx?fn=/video/output/53.mp4>
   2: <img src="video/output/53_big.jpg"/>
   3: </a>

Related to how to build Cross browser video. well in my case it's only IOS use HTML5 or Desktop Browser use flash.
this is the Javascript Example:

   1: function LoadMediaPlayer(fileName, thumbnailPic) {
   2:     var videocontainerid='mediaplayer';
   3:     var x = document.getElementById('htmlvidplayer');
   4:     if(x)
   5:     document.getElementById(videocontainerid).removeChild(x);
   6:     if (!isIOS() && !isAndroid()) {
   7:     var so = new SWFObject("../mplayer/TFMP13.swf", "mediaplayer", "480", "345", "8", "#000000");
   8:     so.addParam("allowFullScreen", "false");
   9:     so.addVariable("MediaLink", fileName);
  10:     so.addVariable("image", thumbnailPic); // preview image
  11:     so.addVariable("imageScaleType", "1");
  12:     so.addVariable("logo", "../images/tf_logo_small.png"); // logo image file
  13:     so.addVariable("logoLink", "http://www.something.com"); // when logo is clicke
  14:     so.addVariable("logoTarget", "_blank"); // target frame when logo is clicked
  15:     so.addVariable("playOnStart", "false");
  16:     so.addVariable("startVolume", "50"); // 0-100
  17:     so.write(videocontainerid);
  18:     }
  19:     
  20: else {
  21:     //use video tag
  22:     fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
  23:     var v = document.createElement("video");
  24:     v.id = 'htmlvidplayer';
  25:     v.width = '480';
  26:     v.height = '345';
  27:     v.controls = true;
  28:     v.poster = thumbnailPic;
  29:     var mp4 = document.createElement('source');
  30:     mp4.id = 'mp4'; mp4.src = '/services/VideoStreamer.ashx?fn=' + fileName; mp4.type = 'video/mp4';
  31:     v.appendChild(mp4);
  32:     
  33:     var g = document.getElementById(videocontainerid);
  34:     g.appendChild(v);
  35:     }
  36:  }

It’s Hosted on Codeplex

Share this post: | | | |
Published Wednesday, June 8, 2011 1:14 PM by cipto

Comments

No Comments