02 February 2011

Dynamically replace HTML5 video with Flash in Fancybox

For my current project, I needed to dynamically (based on whether the device supported HTML5 or not) replace HTML5 video tags with a Flash player. What made this more challenging was that the video player needed to be in a Fancybox.

A day's worth of coding resulted in the following JavaScript function. It's heavily commented so it should be self-explanatory. Drop me a comment if something doesn't make sense. You need to include the latest jQuery library for this to work.

function flashPopup(){
// Declare some variables.
var el = "";
var vidFileName = "";
var posterPath = "";
var placeHolderPosterPath = "";
var replacement = "";
var imgTitle = "";
var videoTag = "";
var boxId = "";
var flashId = "";
var dotPosition = "";
var swf = ""

// Loop over each video tag.
$("video").each(function(){
// Reset the variables to empty.
el = "";
vidFileName = "";
posterPath = "";
imgTitle = "";
placeHolderPosterPath = "";
replacement = "";
videoTag = "";
flashId = "";
swf = "";

// Get a reference to the current object.
el = $(this);

// Set some values we'll use shortly.
boxId = this.id + "_flashBox";
flashId = this.id + "_flashPlayer";

/*
Set the ID attribute of the first DIV in this element's parent's
parent. This "box" will have the Fancybox attached to it.
*/
el.parent().parent().find("div:first").attr("id",boxId);


/*
Fetch the MP4/M4V video from the <source> tags. Need to go to the
parent of the current tag to find them.
*/
el.parent().find("source").each(function(){
if ($(this).attr("src").indexOf("m4v") != -1 ||
$(this).attr("src").indexOf("mp4") != -1){
vidFileName = $(this).attr("src").substring($(this).attr("src").lastIndexOf("/")+1);
}
});

/*
IE (< 9) and older browsers use the Flash player, which overlays a 'Play' button
on the poster image by default; so we use a poster image that doesn't have
a play button. Otherwise we'd end up with a play button on top of a play
button.

We have an img tag inside the <video> tag which we'll use (by adding "-ie" to the
filename) for the Flash player's image attribute. The next two lines find the file
and its path.
*/
dotPosition = el.parent().find("img").attr("src").lastIndexOf(".");
posterPath = el.parent().find("img").attr("src").substring(0,dotPosition) + "-ie" + el.parent().find("img").attr("src").substring(dotPosition);

/*
Use the same image but this time don't add "-ie" to its name. This will be for the
main placeholder linked image; when the user clicks it, the Fancybox pops up with
the Flash player.
*/
placeHolderPosterPath = el.parent().find("img").attr("src");

/*
Fetch the image's title attribute, which we'll use for the linked image's title.
This will appear as the title on the Fancybox.
*/
imgTitle = el.parent().find("img").attr("title");

/*
Concatenate the linked image that will take the place of the <video> tag.
*/
replacement = "<a title='" + imgTitle + "' id='" + boxId + "' href='javascript:;'><img src='" +
placeHolderPosterPath + "' style='float:left; padding-left:5px; '/></a>"

// Replace the parent of the current element with the linked image HTML.
el.parent().replaceWith(replacement);

/*
The swfobject library can't be used with Fancybox (as far as I know). but
we can pass a Flash player of our own design into the Fancybox as its content.
Here, we concatenate the Flash player and point to the swfobject player.swf
file. We'll still use that player, but build our own OBJECT and EMBED tags.
*/
swf = "<object id='" + flashId + "' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' width='372' height='209'>" +
"<param name='movie' value='global/vid/player.swf' />" +
"<param name='quality' value='high' />" +
"<param name='wmode' value='opaque' />" +
"<param name='swfversion' value='6.0.65.0' />" +
"<param name='expressinstall' value='global/vid/expressInstall.swf' />" +
"<!--[if !IE]>--> " +
"<object type='application/x-shockwave-flash' data='global/vid/player.swf' width='372' height='209'> " +
"<!--<![endif]--> " +
"<param name='quality' value='high' /> " +
"<param name='wmode' value='opaque' /> " +
"<param name='swfversion' value='6.0.65.0' /> " +
"<param name=flashvars value='file=" + vidFileName + "&autostart=false&image=" + posterPath + "'>" +
"<param name='expressinstall' value='global/vid/expressInstall.swf' />" +
"<div>" +
"<h4>Content on this page requires a newer version of Adobe Flash Player.</h4>" +
"<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' width='112' height='33' /></a></p>" +
"</div>" +
" <!--[if !IE]>--> " +
" </object> " +
" <!--<![endif]--> " +
" </object> ";

/*
Now attach a Fancybox to this item and set its attributes. Note that
we need to disable the autoDimensions for SWF files or you'll get a
long, narrow box showing. We then set the width and height (which
usually requires some trial-and-error).

The two functions for onComplete and onClosed are custom code that
stop/start the AnythingSlider when the user opens the Fancybox and
closes it, respectively.

This entire function acts as an onClick handler for the object to
which it's attached (hence the "end click function" comment).
*/
$("[id="+boxId+"]").fancybox(
{
'content' : swf,
'autoDimensions' :false,
'height' :236,
'width' :375,
'padding' : 5,
'showCloseButton' : true,
'enableEscapeButton': true ,
'titlePosition' : 'outside',
'onComplete' : function() {stopSlider()},
'onClosed' : function() {startSlider()}
}
); // end click function
});
}

Update: A few requests have come in for a version of the code to allow HTML5 videos on a page to be placed into a Fancybox. Here's the demo, with a very special thanks to Pipsqueak.

Here's the HTML code, with the JavaScript followed a little further down:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Fancybox HTML5 Video</title>
<meta name="description" content="">
<meta name="author" content="alex cougarman">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="js/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.css" type="text/css" />
<!--
In addition to the stylesheet for the Fancybox
component, the following styles control the
look of the page and the linked image.
-->
<style type="text/css">
body {
margin:50px 0px; padding:0px;
text-align:center;
font:11px verdana, arial, helvetica, sans-serif;
}

#main {
width:500px;
margin:0px auto;
text-align:left;
padding:15px;
border:1px dashed #333;
background-color:#eee;
}
.img-link
{
border:1px solid #999;
padding:5px
}
</style>
<!--
Include the jQuery library, followed by the
Fancybox and the Easing libraries.
-->
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/jquery-1.4.3.min.js"></script>
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.pack.js"></script>
<script type="text/javascript" src="js/jquery.fancybox-1.3.4/fancybox/jquery.easing-1.3.pack.js"></script>

<!-- Add inline script code here. -->

</head>
<body>
<div id="main">
<h3>HTML5 Video in a Fancybox</h3>
<div>
<video id="vid-1" width="480" height="360" poster="btf.jpg" controls preload>
<source src="btf.mp4" type="video/mp4">
<source src="btf.ogv" type='video/ogg; codecs="theora, vorbis"'>
</video>
</div>
</div>
</body>
</html>
Replace the line above in the HTML that says "Add inline script code here." with this JavaScript:
<script type="text/javascript">
$(document).ready(function(){
fancyPopup(); // Replace the video tags.
});

/*
Function finds the video tags on the page. For each,
it fetches the video tag's HTML and replaces it with
a linked image that, when clicked, pops up a Fancybox
containing the HTML5 video.
*/
function fancyPopup() {
// Declare some variables.
var el = "";
var posterPath = "";
var replacement = "";
var videoTag = "";
var fancyBoxId = "";
var posterPath = "";
var videoTitle = "";

// Loop over each video tag.
$("video").each(function () {
// Reset the variables to empty.
el = "";
posterPath = "";
replacement = "";
videoTag = "";
fancyBoxId = "";
posterPath = "";
videoTitle = "";

// Get a reference to the current object.
el = $(this);

// Set some values we'll use shortly.
fancyBoxId = this.id + "_fancyBox";
videoTag = el.parent().html(); // This gets the current video tag and stores it.
posterPath = el.attr("poster");
videoTitle = "Play Video " + this.id;

// Concatenate the linked image that will take the place of the <video> tag.
replacement = "<a title='" + videoTitle + "' id='" + fancyBoxId + "' href='javascript:;'><img src='" +
posterPath + "' class='img-link'/></a>"

// Replace the parent of the current element with the linked image HTML.
el.parent().replaceWith(replacement);

/*
Now attach a Fancybox to this item and set its attributes.

This entire function acts as an onClick handler for the object to
which it's attached (hence the "end click function" comment).
*/
$("[id=" + fancyBoxId + "]").fancybox(
{
'content': videoTag,
'title': videoTitle,
'autoDimensions': true,
'padding': 5,
'showCloseButton': true,
'enableEscapeButton': true,
'titlePosition': 'outside',
}); // end click function
});
}
</script>
Replace the two videos (btf.mp4 and btf.ogv) with your own HTML5-compatible videos and try it. Demo, with a very special thanks to Pipsqueak.

Update: The good folks at Long Tail Video provide a great HTML5 video tag reference.

10 comments:

Unknown said...

Hello Alex,

this seems to be a nifty solution!
Would you pls provide a working demo?
Great work. Thx a lot.

Ingo

Alex C said...

You're welcome, Ingo. I'm working on a demo. Haven't figured out how you do that on Blogger.

Pipsqueak said...

Hello Alex,

I tried to follow your code, but I got tripped up with the custom Flash player and the versions of expressinstall.swf. You also have some non-standard approaches -- like an img tag that appears to be inside the video tag (rather than a poster attribute).

I'd love to be able to use HTML5 videos inside Fancybox. My wish is to have several videos on a page and use Fancybox to allow the user to choose among them. But I'm just not following your solution. I second Ingo's wish for a working demo. If you need server space for such, I'd be happy to provide a corner of mine. It's a worthy project!

Thanks!

Alex C said...

If someone has some server area that I can use for posting a demo, that would be great. Thanks!

Alex C said...

I've updated the blog post to now include a function for HTML5 video in Fancybox.

Peter said...

Nice solution...
How can the video autoplay once it pops open?
Thanks mucho!

Alex C said...

For the Flash player, change the &autostart=false to &autostart=true in the flashvars. For the HTML5 VIDEO tag, add the attribute autoplay to where we have the other attributes "controls" and "preload" inside the VIDEO tag in the same way (without any value). Good luck!

Anonymous said...

Video was not working in the fancybox. Please upload the sample code in this blog.

Alex C said...

Please follow the Demo link above for a working example, which contains all the code you need to get it working. Special thanks to Pipsqueak.

Anonymous said...

The video does not start in google chrome, did anyone figure this out yet ?