Sharing HTML5 videos with everyone
We've developed a single embed code for HTML5 and Flash video alike.
Embed codes let you paste some HTML from Youtube, Vimeo, and thousands of other video sites into any text field, and include that video on your own page. Online video's tremendous growth in recent years has been fueled by sharing, and we want to make it as seamless as possible as we transition to HTML5.
When <iframe> can't be used, our <object> method lets you accomplish the same task, across a plethora of browsers.
You can test out our embeddable player on Vodpod.com
Challenges
We're dealing with a huge variety of browsers and hardware, with varying support for HTML, Flash, and HTML5 video. We want a single web page and a single embed tag to work seamlessly across all these platforms.
Moreover, publishers demand custom player controls, obfuscated video sources, and advertising. We can't just hand out a <video> tag; we need full support for javascript to control the player chrome.
Finally, we're facing incompatible interpretations of the standard from IE and Firefox, which complicates the embed code itself.
If you are a CMS author, allow users to include <iframe> tags in their posts. Barring that, at least allow <object>. "But iframes are insecure!" Remember that <object> tags referencing an HTML document are equivalent to <iframe>: both introduce a nested browsing context. When <iframe> becomes commonly accepted, we can forgo the complicated fallbacks used here in favor of a single, concise tag.
In a nutshell
We use an <object> tag to reference an HTML document. IE interprets the tag as a normal Flash embed. Other browsers load the data attribute, which references an HTML document. That document includes a full HTML5 player with javascript, and may include advertising, source obfuscation, content controls, etc. The player uses javascript to fall back to a flash player when HTML5/codec support is unavailable. It also falls back to native controls for certain browsers where JS controls are prohibited in iframes.
This technique works across all browsers, allows publishers to retain control over the chrome of their players on both the client and server side, and does not require encoding videos in every format under the sun.
You can also adopt this embedding strategy without having implemented any HTML5 video features; just refer to an HTML page containing only your current flash player. When you're ready to add HTML5 support, all embedded videos will work seamlessly.
The player
Here's an example of our HTML5 player.
<div id="video">
<video preload poster="http://path.to/thumbnail.jpg" style="width: 100%; height: 100%;">
<source src="http://path.to/video.web.h264" type='video/mp4; codecs="avc1.64001E,mp4a.40.2"'>
<source src="http://path.to/video.mobile.h264" type='video/mp4; codecs="avc1.42E01E,mp4a.40.2"'>
</video>
<div class="controls">
<button class="play pause"></button>
<div class="progress">
<div class="position"></div>
<div class="slot">
<div class="loaded bar"></div>
<div class="played bar"></div>
</div>
<div class="end"></div>
</div>
</div>
</div>
The buttons, progress bars, and so forth are handled by javascript. You can introduce advertising, logos, content controls, additional chrome, or whatever suits your fancy.
Browsers which do not support html5, or any of the given codecs, need a flash player.
function supports_video() {
var playable = false;
var confidence;
try {
$video.children('source').each(function(i, source) {
confidence = video.canPlayType(source.type);
if (confidence == 'probably' || confidence == 'maybe') {
playable = true;
}
});
} catch (err) {
playable = false;
}
return playable;
}
If this test fails, we replace the entire video <div> with a flash player.
Some players, such as the iPad, don't allow javascript to act on videos inside a nested browsing context, like an iframe or embed. When we detect an iPhone, iPad, or iPod user agent, we hide our own controls, set controls on the <video> tag, and re-inject it into the DOM.
function apple() {
return (navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i));
}
if (apple()) {
controls.hide();
$video.attr('controls', true);
$video.detach();
div.prepend($video);
}
The resulting video player should work on every browser. You can use it for your regular player on video pages. Next, we need to embed this document in another.
The embed tag
<iframe> is exactly what we need: loading the player and its javascript in a nested browsing context. Unfortunately, many content management systems prohibit its use.
Luckily, the object tag is already in wide use for video embedding, and is even more powerful. When an object's data attribute points to an HTML document, the browser can render that document just like an iframe. This behavior was a part of HTML4 thirteen years ago, and persists in HTML5.
<object data="http://path.to/player.html" type="text/html" height="300px" width="400px"></object>
Naturally, every browser except Internet Explorer supports this part of the standard.
However, IE also attaches special significance to an object's classid attribute. If we specify the Flash plugin's classid, IE interprets the object as a flash video and ignores the data altogether. We also introduce <param> tags for the flash player's source and options.
<object data="http://path.to/player.html" type="text/html" height="300px" width="400px"
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
<param name="movie" value="http://path.to/movie_player.swf?file=http://path.to/video.mp4&…">
<param name="allowfullscreen" value="true">
</object>
Chrome and other webkit browsers ignore the classid attribute and load the HTML document instead; IE uses the classid attribute and ignores data. Firefox chooses to ignore the entire object if a classid is present, rendering the fallback content instead.
For you, special Firefox, we include a second copy of the <object> tag as the fallback content, only without the classid.
<object data="http://path.to/player.html" type="text/html" height="300px" width="400px"
classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
<param name="movie" value="http://path.to/movie_player.swf?file=http://path.to/video.mp4&…">
<param name="allowfullscreen" value="true">
<object data="http://path.to/player.html" height="300px" width="400px"></object>
</object>
You can also save a few bytes by issuing an HTTP redirect for the source of the flash player, adding flashvars and other query parameters to the URL.
We're still testing this technique with various browsers, but it does work under IE 7, IE 8, Firefox 3.5, Safari, Chrome, Chromium, iPhone, and iPad.
IE6 renders *both* the object and its fallback content side by side, sometimes getting one of them wrong in the process. You can use style="_display: none" on the fallback <object> if supporting IE6 is a concern.
Going forward
IE is the big holdout here. This strategy provides flash video to all
current versions of IE. When IE9 arrives, the HTML5 player will work for them,
too. Their implementation of <object> is still in question: will
classid or data and type take
precedence?
HTML5
states that classid takes precedence, but webkit has opted to let <data>
decide for type="text/html"—probably because webkit has no
way to interpret classids reliably. This agrees with HTML 4 which
states classid may be used as an alternative to data.
Eventually, when all current generations of IE are obsolete (or iframes become more acceptable), we can abandon the classid and nested object fallbacks altogether.