01 July 2010

Automatically put HTML5 videos in Fancybox

Here's a tricky one: Safari 5 and the iPad have issues with HTML5 video tags if those objects move. They show "black holes" on the screen, especially inside content rotators. Solution? Do a "popup" (via Lightbox, Fancybox, etc.) to handle the video, while leaving the video as is on the other browsers.

Adding to the complexity of the problem is the fact that Safari strips off the poster attribute from the video tag; not sure why. So I ended up using an img inside the video tag; we'll see the reason below. Here's the video tag as it appears in the HTML; the closing source tags are to prevent IE from throwing an error:
<video  id="vid-1" width="372" height="209"
poster="global/vid/historydetectives-poster.jpg" controls onPlaying="stopSlider()">
<source src="global/vid/historydetectives.mov"></source>
<source src="global/vid/historydetectives.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'></source>
<source src="global/vid/historydetectives2.ogg" type='application/ogg'></source>
<img src="global/vid/historydetectives-poster.jpg" />
</video>
To get Safari/iPad to pop these up in a Fancybox, we follow these steps:
  1. Loop over all the video tags on the page. In this case, their IDs all start with "vid-".
  2. Get each video's HTML; for this we have to get the parent element.
  3. Get the img tag's src attribute (Safari stripped out the poster attribute on the video tag).
  4. Generate some HTML for a linked image, with its src set to the img tag above.
  5. Ensure the link has a unique ID.
  6. Replace the video tag with the generated HTML.
  7. Instantiate a Fancybox for the video and set its content to the video's HTML.
Here's the code:
$(window).load(function () {
if (jQuery.browser.safari) {
var videoTag = "";
var posterPath = "";
var replacement = "";
var boxId = "";
var el = "";

/* Fetch all the video tags with ID
starting with 'vid-'.
*/
$("[id^='vid-']").each(function(){
// Extra precaution: reset variables.
videoTag = "";
posterPath = "";
replacement = "";
boxId = "";
el = "";

// Get a reference to this element.
el = $('[id='+this.id+']');

/*
To get the enclosing video tag,
fetch the parent's HTML.
*/
videoTag = el.parent().html();

/*
Get the path to the video's poster image.
Safari strips off the poster attribute, so
we use an image within the video tag to hold
the image path.
*/
posterPath = el.parent().find("img").attr("src");

/*
We're manually instantiating and setting
properties on a Fancybox, so we add "_manual"
to the ID.
*/
boxId = this.id + "_manual";

/*
Concatenate some HTML: We'll create an image tag
with its source set to the image tag inside the video.
Wrap it in an anchor tag and set its ID to what we
created above so we can attach it to the Fancybox.
*/
replacement = "<a title='' id='" + boxId +
"' href='javascript:;'><img src='" +
posterPath + "' style='float:left; padding-left:5px; '/></a>";

/*
Replace the video element with the concatenated HTML
we just created.
*/
el.parent().replaceWith(replacement);

/*
Instantiate a Fancybox and attach it to the
linked image. Set its 'content' to the video
tag's HTML.
*/
$("[id="+boxId+"]").fancybox(
{
'content' : videoTag,
'autoDimensions' :true,
'padding' : 9,
'showCloseButton' : true,
'enableEscapeButton': true
}
); // end click function
}); // end each function
} // end if
else {
/*
This next line is needed only if we use an HTML5 video
player library (such as JW HTML5 Player from LongTail
Video). These libraries often include their own controls
and if we don't remove the 'controls' attribute from
the video tag, the other HTML5-ready browsers produce
"double" controls. We're including the 'controls'
attribute on the video tag for the iPad to be happy;
without that attribute, the video pops up with a black
screen on Fancybox. So we strip it from the video tag.
*/
$("[id^='vid-']").each(function(){
$('[id='+this.id+']').removeAttr("controls");
});
} // end else
} // end load function
One other caveat: We can't call the following in the each function:
$("[id="+boxId+"]").click(function()
{
$.fancybox( ....
The reason? We need to instantiate a Fancybox for each of the videos.

I've documented additional adventures and solutions with the content rotator and HTML5 video in these posts:

No comments: