HTML5 + video Tag + iPhone, iPad = iHaveHeadache!

Here at no.inc we try to keep things fresh, support new technologies and devices. So recently we have a client who wants some videos on their site, and we decide to make it work with both PC’s and mobile devices (and specifically the iPhone and iPad).

The Approach

Well, since flash 9.0.115 flash supports h.264 video encoding, the iPhone and iPad support html5 and the <video> tag, and also support h.264 encoding. So the idea is to encode the videos in h.264, detect some mobile devices (iPad, iPhone) and serve up the same file in 2 different ‘players’. Now I know what you’re saying, most modern browsers also support html5 and the <video> tag, so we could just deliver it like that. Well … IE9 isn’t out yet, and IE8 and below don’t support it, besides, I’m not quite sure html5 and video have become a standard and gotten all the kinks out, so we’re sticking with flash for the PC.

First Round Success

This round was easy, basically encode our videos to h264, write a flash video player in as3, have it take the video file as a flashvar argument and serve it up, swfObject to embed it.

<?php
 /* if $iphone is false, insert the swfObject code that will replace the div with our flash player */
 if(!$iphone):
 ?>
<script type="text/javascript">
 var flashvars =
 {
  auto: 0,
  preview: "/images/<?=$brand?>/vid-pre-<?=$vidId?>.jpg",
  skindir: "swfs%2f",
  video: "<?=("/videos/$brand/$vidId.mp4")?>",
  width: "640",
  height: "360",
  type: "mp4"
 };

 var params =
 {
  menu: "false",
  wmode: "transparent"
 };

 var attributes =
 {
  id: "aVid",
  name: "aVid"
 };

 swfobject.embedSWF("/swfs/player.swf", "player", "502", (282 + 40), "9.0.115", "/swfs/expressInstall.swf", flashvars, params, attributes);
 </script>
<?php
 endif;
 ?>

Now we just need to detect a mobile device, and serve up different content pointing to the same video:

<?php
/* this goes in the header */
$agent = $_SERVER['HTTP_USER_AGENT'];
$iphone =
 ( (strpos($agent, 'iPhone') != false) ||
 (strpos($agent, 'iPad') != false) ||
 ((strpos($agent, 'AppleWebKit') != false) && (strpos($agent, 'Mobile') != false)) ||
 ((strpos($agent, 'AppleWebKit') != false) && (strpos($agent, 'webOS') != false)) ||
 (isset($_GET['iphone']))
 ) ? 1:0;
?>

<?php
 if($iphone):
 ?>
 <video id="aVid" width="502" height="282" controls="controls" poster="images/<?php echo"$brand"; ?>/vid-pre-<?=$vidId?>.jpg">
 <source src="videos/<?=$brand?>/<?=$vidId?>.mp4" type="video/mp4"></source>
 </video>
 <?php
 else:
 ?>
 <div id="player">
 <p><a href="http://www.adobe.com/go/getflashplayer"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /></a></p>
 </div>
 <?php
 endif;
 ?>

So what we’re looking at above is, we added the code to give us a true or false statement in the variable $iphone if we detect a mobile browser that can do html5 and the video tag.  If $iphone is true, we output the video tag code pointing to our ${vidId}.mp4, otherwise we output the div that swfobject will replace (and inside the div is the info to get your flash player if you don’t have it).

and we’ll change the doctype too if we’re serving up html5:

<?php
if ($iphone):
?>
<!DOCTYPE html>
<?php
else:
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<?php
endif;
?>

So this works, like a champ… for the most part.

* First of all, the iPhone doesn’t appear to even fetch the file if  file extension is not .mp4.  Flash wants to make its h264 file extension as .f4v, but its ok with them named .flv, or .mov, or probably anything!

* Second, encoding a video using adobe’s h264 encoder doesn’t mean it works with the iPhone, however, encoding a video for the iPhone works with flash.  I recommend using handbrake with the iPhone setting and ‘Web Optimized’ checked.

This setup works and we’re happy with it.  But here’s where the headache comes in.

The headache

First let me explain the problem:  While testing the iPhone, and the PC everything seemed fine.  Then while thinking we are done, we notice the iPad is behaving irregular.  Basically with this setup the iPad has some odd race condition or something, the <video> tag loads in the video, and the poster comes in and shows a preview of the movie, but the play button is gone.  It’s not related to the controls argument on the video tag, its not the actual poster, something was just messed up.

Upon diagnosis of adding/removing/changing things around we found that removing the included external javascript files caused it to work properly.  Well that doesn’t make sense really, so we changed the js files, changed the contents of the js, replaced the js with empty files… basically if the js files were included (and external js), it would hide the play button.  It doesn’t matter WHAT the js is, if its garbage, jQuery, an alert… if you include external js, you lose play.

We found that moving the <javascript> tags to the bottom of the file (we were thinking race condition, or socket limitation or something), this fixed it… the js was there, the js worked, the play button was there, the video plays! YAY! ….

That is until we went back to the iPhone.  Now this is where things got really weird.  The newly discovered iPhone bug was some how related to the change of the iPad code, but it made less sense than the iPad bug.  Now the iPad’s bug was it would hide the play button.  The iPhone bug would give the ‘invalid video codec’ symbol.  What ?  Invalid codec ?  You just played this file earlier…  While checking the webserver logs, the iPhone doesn’t even attempt to fetch the .mp4 file and check its codec, it just says invalid codec, and you get none.

Trying to figure out the culprit of this one took about 2 days of headaches.  We stripped the html down and found it’s somehow related to the stylesheet…. not the contents of the stylesheet, the inclusion of an external stylesheet.

Let me give you an example.

<link rel="stylesheet" href="css/all.css" type="text/css" media="all" />

You can remove type and media arguments without any difference.  If you change the rel or remove it, it suddenly works again!, if you remove the href, it suddenly works again!, if you point the href to a empty file, or just put in ‘fisdanfois’ gibberish, it doesn’t work… the file doesn’t have to be valid it can be a 404, it just simply won’t work if you have that link tag.  We ended up with a small test file… that consisted of nothing really, as plain as below:

<!DOCTYPE HTML>
<html>
<head>
 <link rel='stylesheet' href='css/all.css'/>
</head>
<body>
 <video controls>
 <source src='vidoes/video.mp4'></source>
 </video>
</body>
</html>

That html above, does not work.  If you remove the <link> tag, it works, if you change rel= to something it works…

So after these 2 long days of debugging, we didn’t put 2 and 2 together until we backtracked far enough to undo the iPad fix.  Undoing the iPad fix made it work again, but we were puzzled because the <link> tag was there, and it worked! … so it must have been directly related to the <javascript> tag movement.  Now it’s not that the iPhone doesn’t like <javascript> placed somewhere, it simply doesn’t want to have a <link> tag in the header, without a <javascript> tag pointing to an external file….

I can’t explain it, all I know is what I seen.  Sure, it makes no sense, it has to be some bug with the iPhone, and iPad for that matter, different bugs, both related to something in the <head>.  We already spent too much time on this fiasco, so the only solution was to specifically look for the iPad’s agent string, and move the js if its an iPad.  I know, it’s lame but I have to move on.

// in the top of the php file, after the $iphone detect
$ipad = strpos($agent, 'iPad') != false;

// in the head
<?php if(!$ipad): ?>
<javascript includes>
<?php endif; ?>

// at the bottom of the file
<?php if($ipad): ?>
<javascript includes>
<?php endif; ?>

Well there you go, I hope apple fixes this bug, but until then feel free to just do what we did, maybe you can save a few hairs from falling out.

16 Responses to “HTML5 + video Tag + iPhone, iPad = iHaveHeadache!”

  1. Heff says:

    Wow…that’s ridiculous. Sorry for your pain, but thanks for posting this so I don’t have to go through it. :) Someone on videojs.com showed me this link, so I’ll be sure to link back.

    As far as encoding, I’m fairly certain the reason Adobe’s encoder doesn’t work is because of the h.264 profile. h.264 has 3 profiles: baseline, main, and high. iPhones require baseline. And I’m guessing handbrake uses that for the web optimized videos. I work for Zencoder, and we have baseline on by default for this reason.

  2. Awesome, that’s one of the most bizzare bugs ever and I’ve been strugling with it for the last 2 hours without success. I guess good practices “keep your JS at the bottom of the file” can sometimes bite you in the ass.

  3. Marvin says:

    Thank you for the saved headache, it is very ridiculous no regard for compatibility. Makes you wonder what the developers really do when they test and approve their product. Its really a shame, i guess in the end if the client wants this sort of compatibility you have to charge them an arm and a legal. Again thank you for your hard work and keep us updated if any changes are made.

  4. Laz says:

    Very interesting!

    One question though: according to specifications, on the iPad the play button placeholder should never be there:
    http://developer.apple.com/safari/library/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/AudioandVideoTagBasics/AudioandVideoTagBasics.html

    It does appear in my sites too (sporadically, not always, sigh), but I wonder if that is what it’s supposed to do or some kind of weird bug itself.

  5. paul says:

    Are you referring to “On the desktop and iPad, the first frame of a video displays as soon as it becomes available. There is no placeholder.” ? I think they are saying that the entire blue block will not hold its place, however the play button should still show. Otherwise how would you ever know where to click on the video to start playback ? It’s definitely a bug with the inclusion of external js / css files, the fact the behavior changes depending on where you put the tags, and the tag pairs of js / css is ridiculous.

  6. Simon Davies says:

    I found this and felt aaarrgh!

    Anyway what I did and seems to have worked is the following:

    if “iPhone”
    {place video tage info here}
    else if “iPad”
    {Holding image}
    end

    so basically I surrounded an image(Screengrab) with a play icon added to it, to simulate the effect then added a simple a href link to the mp4 and this seemed to work.

    So until a solid solution is fixed this should work

    Si

  7. Simon Davies says:

    sorry guys, the [Holding image] was meant to be this :

    [a href="link to your .mp4 file"]HOLDING IMAGE[a]

  8. karl says:

    HTML5 is not a mature standare , most of modern devices do not support it , so be careful when using it. But ipad is definately supports it

  9. Kevin Tuskey says:

    I ran into a similar error, but I think the culprit is that iPad doesn’t play nice when there is more than one source format listed. I was building a Flash video player that degraded to an HTML5 video element for iOS devices, and I fixed the bug when I included just one MP4 video source call within the video tag itself rather than inside a separate source tag embedded inside the video tag.

    http://www.circlesstudio.com/clients/fcc/growth_accelerator/

    I had to make the hard decision to omit the OGG format for the sake of iPad playback.

  10. Reinhardt says:

    You saved me hours of frustration and a potentially missed deadline. Thank you!

  11. Matt Hall says:

    @karl i guess “support” is debatable….

  12. jeff says:

    I had the same problem with links to external css files, but solved it by replacing the

    tags with

    statements. Moving my css files to the bottom of the file meant the css was not applied to the webpage.

  13. jeff says:

    Whoops. Should have been

    by replacing the link tags

    with php require statements

  14. Sebastian says:

    While an old post I thought I’d share my tiny bit of insight into the same problem.

    The iPhone does not need (nor requires) the controls attribute and is ALWAYS forced to play in full-screen mode automatically. The iphone will recognize video and automatically handle this for you.

    This does not apply to the iPad as it will play content in its video element (and dimensions), but it does require that the controls attribute is present. Otherwise the video is loaded and visible, but not playable.

    The bigger questions is how will other platforms handle this element as they become more popular and a viable alternative to the iPad.

Leave a Reply