Showing posts with label mp4 video. Show all posts
Showing posts with label mp4 video. Show all posts

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.

08 July 2010

Getting JW HTML5 Player to work on IE

In my continuing adventures with HTML5 video, I arrived at IE testing, and noticed that IE does not like the JW HTML5 Player (mainly because it's in beta right now). But I have to demo my site and can't wait until Version 1.0 of the player; so, I rolled up my sleeves for another non-trivial challenge.

The problem is that the current versions of IE can't display the HTML5 video tag; IE 9 will address this, but we'll be living with lower versions for some years. And the beta JW HTML5 Player doesn't remove the video tag completely from IE. The answer is to do some jQuery coding to find the video tags and replace them with swfobject elements. There are plenty of gotchas along the way as to file paths and filenames.

Here's the finished code:
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js">
</script>

<script type="text/javascript">
$(document).ready(function(){
// If this is IE...
if (jQuery.browser.msie){
var el = "";
var vidFileName = "";
var posterPath = "";
var videoTag = "";
var boxId = "";
var so = "";
var flashId = "";
var flashVars = "";
var params = "";
var attributes = "";
var anchorId = "";

// For each video element...
$("[id^='vid-']").each(function(){
// Clear out the variables.
el = "";
vidFileName = "";
posterPath = "";
videoTag = "";
flashId = "";
flashVars = "";
params = "";
attributes = "";
anchorId = "";

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

// Set some variables we'll shortly.
boxId = this.id + "_flashBox";
flashId = this.id + "_flashPlayer";
anchorId = this.id + "_anchor";

/*
Set the ID of the first DIV element of the current
object's parent's parent. This will allow us to
reference that DIV later.
*/
el.parent().parent().find("div:first").attr("id",boxId);

/*
Loop over each 'source' tag inside the video; check
the 'source' tag's 'src' attribute. If it contains
'm4v' or 'mp4', then it's usable for the Flash
player in IE.
*/
el.parent().children("source").each(function(){
if ($(this).attr("src").indexOf("m4v") != -1 ||
$(this).attr("src").indexOf("mp4") != -1){
/*
We don't need the filename + path of the video,
only the filename. So we remove the path and
fetch only the filename.
*/
vidFileName = $(this).attr("src").substring($(this).attr("src").lastIndexOf("/")+1);
}
});

// Get the poster's filename and path.
posterPath = el.parent().find("img").attr("src");


// Get a reference to the video's containing DIV.
el = $("[id="+boxId+"]");

// Empty the DIV of all contents.
el.empty();

/*
Append an anchor tag with the anchor ID to the DIV.
We'll replace this anchor tag with the Flash player.
*/
el.append("<a id='" + anchorId + "'></a>");

// Set flashvars, params, and attributes.
flashvars =
{
file: vidFileName,
autostart: 'false',
image: posterPath
};

params =
{
allowfullscreen: 'true',
allowscriptaccess: 'always',
wmode: 'opaque'
};

attributes =
{
id: flashId,
name: flashId
};

/*
Embed the Flash player object using the swfobject.
The embedSWF() method replaces the anchorId element
with the Flash player. For more information on the
various arguments, please see the documentation here:
http://code.google.com/p/swfobject/wiki/documentation
*/
swfobject.embedSWF('global/vid/player.swf', anchorId, '372',
'209', '9.0.124', false, flashvars, params, attributes);
});
}
});
</script>
One gotcha with swfobject: You need to ensure the path of the 'file' that you wish to play is relative to the player.swf or you'll get an error saying 'Can't find file or access denied.' This thread talks about the issue.

In addition, you'll need to add the following to the page to prevent IE from throwing an error:
if (!jQuery.browser.msie){
$('video').jwplayer({
flashplayer:'global/js/jwplayer/player.swf',
skin:'global/js/jwplayer/five/five.xml'
});
}
This ensures that the JW HTML5 Player is fired off only on non-IE browsers. My previous post on HTML5 video and the JW Player has links to all my other posts on the subject.

UPDATE: Please see part 2 of this post for how to get the release version of the player working on IE and other browsers.

24 March 2010

One player to rule them all: An MP4 player solution for iPhone and any browser

For a recent project, I needed a video player that would play nice with the iPhone. Part of the solution was provided by the amazing html5media JavaScript library. However, if you'd like to encode only one video format, MP4, and get it working on Opera 10.5+, it will require some more work.

The html5media library is able to detect and replace the <video> tag with the Flowplayer Flash player for almost all browsers; for some reason, it can't do the same for Opera 10.5. And as this page explains, the HTML5 <video> tag on Opera requires an OGV video; it can't play MP4.

Here's a solution that lets you play MP4 video on the latest iPhone, Firefox 3.6, IE 6 - 8, IE Platform Preview 9, Opera 10.5, Safari 4, and Chrome 4.1. I don't know if it's the most elegant, but it gets the job done.

First, download the free Flowplayer library. Unzip the package and then start an HTML document:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Video Test</title>
<script src="http://html5media.googlecode.com/svn/trunk/src/html5media.min.js">
</script>
<script type="text/javascript" src="js/flowplayer-3.1.4.min.js"></script>
<script type="text/javascript">
// Required for Opera to display the MP4 video.
// Removes take the <video> and <source> elements
// from the DOM, but keeps the other children of
// the <video> tag. In this case, it retains the
// <a> tag which will be used for the Flowplayer.
function fallback(video) {
while (video.firstChild) {
if (video.firstChild instanceof HTMLSourceElement) {
video.removeChild(video.firstChild);
} else {
video.parentNode.insertBefore(video.firstChild, video);
}
}
video.parentNode.removeChild(video);
fixForOpera();
}

// Places a Flowplayer Flash player into the item with ID "player".
function fixForOpera(){
flowplayer("player","js/flowplayer-3.1.5.swf");
}
</script>
</head>
<body >
<video id="vid" width="352" height="264" poster="cat.jpg" controls preload>
<source src="cat.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'
onerror="fallback(this.parentNode)"></source>
<a
href="cat.mp4"
style="display:block;width:352px;height:264px"
id="player">
</a>
</video>
</body>
</html>

The trick is to catch the error thrown by the <video> tag (using its onerror attribute) and remove the tag (using the fallback() function), ensuring that we keep the anchor tag that will hold the Flowplayer object. The fallback() function also calls fixForOpera(), which places the Flowplayer object into the anchor tag with ID player.

This player works for the browsers listed and the iPhone.