As you’ve probably read somewhere on the interwebs, HTML5 is bringing native video support to browsers. This will enable us to embed a video file directly in our HTML, much like a SWF or image.
Background
You may have also heard that there’s currently a big controversy over what kind of video files will be supported. The defacto standard is MP4/H.264, which is used by Adobe in their Flash video format, and by huge media sites like YouTube. Mozilla, the makers of Firefox, refuse to support the MP4/H.264 standard because it isn’t open-source and free from licensing constraints.
Turns out H.264 is not public domain. Although the company that owns the H.264 patent has temporarily agreed to waive royalty fees for the next decade or so, they reserve the right to charge fees later on. Mozilla says no way, we will only support a video format that is free from licensing issues and has no patent holders (because patent holders can decide to sue some day). Mozilla supports the completely free/open-source Ogg format.
Apple and Adobe, already knee-deep in MP4/H.264 with their Quicktime and Flash video products, vow to press on with H.264. Google also supports H.264 because YouTube relies on it, and because Google’s new Chrome browser is based on the WebKit project, which has Apple as a main code contributor. In case you haven’t noticed, Apple, Adobe and Google have pretty much cornered the internet video market the past few years, so if they’re throwing their support behind H.264, you can count on it being around for a while. Not to mention that many mobile devices, including the iPhone and most Android phones, have hardware that is designed specifically to support H.264 video, enabling smoother playback and less battery drain than non-dedicated hardware.
(For what it’s worth, Opera is in agreement with Mozilla and supports Ogg. However, not many people seem to pay attention to Opera these days, so they don’t appear to have much influence in this race. Microsoft has endorsed H.264 with it’s upcoming IE9 browser, but it won’t be available for some time.)
The problem
Firefox and Opera are essentially forcing websites to offer two versions of each video: an Ogg version and an MP4 version. In my opinion — and the opinion of many others — this simply will not do. Providing two different video files is not realistic, Ogg’s quality is inferior to H.264, and many computers and mobile devices have direct hardware support for H.264 but not Ogg. In reality, without MP4 support, HTML5 video is rendered useless for most site developers in Firefox and Opera.
The most logical workaround is to code <video>
elements to work for MP4 and have a Flash Player-based fallback for older browsers and browsers that only support Ogg. Since the <video>
element is designed to allow for fallback content, just like the <object>
element, we can do this:
<video id="myvideo" width="480" height="360" controls>
<source src="/video/file.m4v" type="video/mp4"></source>
<object data="flash-video-player.swf" type="application/x-shockwave-flash" width="480" height="360">
<param value="flash-video-player.swf" name="movie"/>
<param value="file=/video/file.m4v" name="flashvars"/>
</object>
</video>
Code language: HTML, XML (xml)
This works fine in Safari, Chrome, Internet Explorer and older versions of Firefox and Opera that don’t support the <video>
element. However, Firefox 3.6 and Opera 10.5 do something very irritating: even though they KNOW their <video>
doesn’t support “video/MP4”, they load the <video>
element anyway. Which is … like … OMG so duh … because the video can’t possibly play!
If the <video>
element is loaded, Firefox and Opera will never load the fallback <object>
containing the Flash-based fallback.
Because this behavior cannot be fixed with markup, we’re forced find a scripted workaround (notice that we haven’t used a single bit of JavaScript yet). Thankfully, there’s a pretty straightforward solution: Delete the <video>
element in Firefox and Opera.
A Solution
Here’s a simple script that will detect whether HTML 5 video is supported, and if it is, will check to see if MP4 is supported. If HTML5 video is supported but MP4 is NOT supported, the script deletes the specified <video>
element but leaves the Flash fallback in its place.
var detectVideoSupport = function (){
var detect = document.createElement('video') || false;
this.html5 = detect && typeof detect.canPlayType !== "undefined";
this.mp4 = this.html5 && (detect.canPlayType("video/mp4") === "maybe" || detect.canPlayType("video/mp4") === "probably");
this.ogg = this.html5 && (detect.canPlayType("video/ogg") === "maybe" || detect.canPlayType("video/ogg") === "probably");
return this;
};
var replaceVideoWithObject = function (video_id){
if(!video_id){ return false; }
var video = document.getElementById(video_id);
if(video){
var obj = video.getElementsByTagName("object")[0];
if(obj){
var obj_copy = obj.cloneNode(true);
video.parentNode.insertBefore(obj_copy, video);
video.parentNode.removeChild(video);
}
}
};
window.onload = function (){
var video = detectVideoSupport();
//Both Opera and Firefox support OGG but lack MP4 support
if(video.ogg && !video.mp4){
replaceVideoWithObject("myvideo");
}
};
Code language: JavaScript (javascript)
A few notes
Tested successfully in:
- Windows XP: Firefox 3.0, Firefox 3.5.8, Internet Explorer 6, Internet Explorer 8, Google Chrome 4.1.2
- Windows Vista: Internet Explorer 7
- Mac OS X (10.6): Firefox 3.6, Safari 4.01, Chrome 5 (beta), Opera 10.1, Opera 10.5b
(Note: IE6, IE7 & IE8 give an unexplained “object required” error in the demo page, but everything works fine. I will investigate as time permits.)
This demo uses the JW Media Player as the Flash fallback video player, but you can use any Flash-based video player on your page.
This demo doesn’t do any Flash Player version detection to ensure the visitor has a supported version of Flash Player. If you need to add Flash Player version detection, you can use SWFObject to embed your Flash file.
Update April 3, 2010: This post was updated to add Opera 10.5 to the list of non-behaving browsers and remove Firefox user agent sniffing.
Comments
Tom wrote on March 22, 2010 at 2:04 am:
Useful code, but doesn't this take us back to the bad old days of browser detection to get around flaws in a particular browser (not naming any names IE6)?
I guess its inevitable whilst a standard emerges, just makes me feel that if workarounds like this prevail then
a) Firefox won't be under any pressure to offer H.264
and
b) Adobe still wins as everyone will use Flash as a fall back… inevitabley meaning that lazy people will just Flash to encapsulate their video as it means all their problems are solved in one.
philip wrote on March 22, 2010 at 9:54 am:
I completely understand your point, but we're already doing browser sniffing on a grand scale via frameworks like jQuery, YUI, Dojo and MooTools. The difference with the frameworks is that the sniffing is done behind a curtain, so you never have to think about it.
Personally, I believe Mozilla is feeling a ton of pressure to support H.264 and will cave. With almost everyone skipping OGG as a video format, Firefox becomes less relevant for people's needs. Mozilla may be non-profit, but they don't want to become irrelevant, esp. with IE9 on its way and Chrome stealing its user base.
RE: Adobe and lazy people, the solution presented in this post (which will be revised soon, stay tuned!) enables savvy developers to use HTML5 video as a first option and Flash as a fallback. That means the videos work on mobile devices (iPhone, Android etc) without requiring Flash. I think that's a strong enough incentive to get developers to choose HTML5 first — and why Apple refuses to put Flash Player on iPhones.
RE: the "bad old days," Browser Wars II has been afoot for quite some time now; the difference this time is that we're making efforts to make it less painful for end users by providing useful alternatives or scripted workarounds.
For example, HTML5 features are not evenly implemented in the various browsers, so there is a <em>lot</em> of detection going on at the moment for features such as local storage, HTML5 form elements, CSS3 support and so on. This pipwerks blog uses the famous 'HTML5 shim' to enable me to use HTML5 page elements such as <code>article</code> and <code>section</code> in older browsers.
Steven wrote on March 26, 2010 at 8:18 am:
As of jQuery 1.3, the library contains no more browser sniffing (preferring feature detection). See http://docs.jquery.com/Release:jQuery_1.3
This script does a nice bit of feature detection (good), but then falls back on browser sniffing (bad). Of course finding a way to detect this "feature" is the challenge …
So far, this is the only solution I've found – thanks!
philip wrote on March 26, 2010 at 8:30 am:
Believe me, I hate browser sniffing as much as the next person, but it's sometimes required. On a side note, MooTools has recently shifted positions and will use browser sniffing instead of feature detection in its next release. This is due to the difficulties they've had with feature detection across versions of browsers, esp. Firefox (3.6 broke their existing feature detection).
zcorpan wrote on April 3, 2010 at 12:58 am:
Firefox does what the spec requires. Opera, Safari and Chrome do as well. The reason you saw the fallback in Opera is because you tested Opera 10.10 which doesn't support video. Try Opera 10.50.
Why are you sniffing for Firefox? It's not needed. If you just remove the sniffing, it works everywhere.
BTW, type is not a valid attribute on video. You might want the source element.
philip wrote on April 3, 2010 at 9:47 am:
Opera 10.5 behaves identically to Firefox 3.6 — it fails to show the fallback content if the video element is present.
As for the 'type' attribute, I was unaware it wasn't official (oops) but further testing proves that simply using the <code>source</code> element as you recommend does NOT make a difference. Here are test pages to prove it:
<ul>
<li><a href="http://lab.pipwerks.com/video/flash-fallback/no-type-attribute.html" rel="nofollow ugc">Original example, minus scripting and with 'type' attribute removed from <code>video</code> element</a>.</li>
<li><a href="http://lab.pipwerks.com/video/flash-fallback/source-element.html" rel="nofollow ugc">Revised example with <code>source</code> element, including 'type' attribute to specify "video/mp4"</a>.</li>
<li><a href="http://lab.pipwerks.com/video/flash-fallback/source-without-type-attribute.html" rel="nofollow ugc">Revised example with <code>source</code> element, <strong>excluding</strong> 'type' attribute</a>.</li>
</ul>All three of these test pages fail in Firefox 3.6 and Opera 10.5b (both Mac OS X). If I remove the script fix, the Flash fallback does not display.
On a side note, I have already created a revised version of the script that takes Opera into account. <del>I haven't had time to post it yet, but will try and post it this weekend.</del> The edits have been incorporated into this post and accompanying demo files.
<strong>Update:</strong> The demo page that left out the 'type' attribute in the <code>source</code> element failed when I tested in Safari on the iPhone. So it appears the safest route is to use the <code>video</code> element with a <code>source</code> child element, and be sure the <code>source</code> element includes the 'type' attribute.
Steven wrote on April 6, 2010 at 9:02 am:
Thanks for the update!
Flv Player wrote on April 9, 2010 at 3:22 am:
Nice work…
Thanks for sharing this information.
Zach wrote on April 16, 2010 at 12:53 pm:
This is great. The only problem is that it does not display on the iPad. Not sure why. Trying to figure out a solution.
philip wrote on April 16, 2010 at 1:21 pm:
Unfortunately, I am not privileged enough to test on an iPad… yet. 😉 It works on my iPhone and iPod Touch, though it displays a link to the Apple Quicktime video player rather than displaying the video in the HTML page itself.
Thanks for the info, and please let me know if you make any discoveries!
Klipolis wrote on May 8, 2010 at 4:38 pm:
Thank you help! 🙂
I modified a line and it's works:
//document.body.removeChild(video);
video.parentNode.removeChild(video);
Steven wrote on August 3, 2010 at 2:21 pm:
I tested this on the iPhone Simulator in iPad mode, and it worked, so I think you're safe!
I was using this method for a client today, and since I was already using jQuery on their site I came up with a little jQuery snippet to remove the video element (hopefully the comment system doesn't mash this code):
$(function(){
vid = $('#myvideo');
if(vid.length && !vid.get(0).currentSrc)
{
vid.children('object').unwrap()
}
})It turns out you can check for the 'currentSrc' attribute on the actual video element (once the DOM is loaded). If it has no currentSrc, that means it couldn't find a supported format to play, so it gets "unwrapped".
It seems like this method of detection would be the most broad solution to the problem – you can include whatever video types you like, and if the browser doesn't support them, it will fall back to Flash.
Wiley Wiggins wrote on August 4, 2010 at 11:29 am:
I am completely stymied as to why this isn't working for me. I can see it working in firefox in your example, but I still get an ugly OGG "X" in my firefox instead of the flash fallback.
http://wileywiggins.com/catalog/
Wiley Wiggins wrote on August 4, 2010 at 12:05 pm:
fixed it! sorry!
Wiley Wiggins wrote on August 4, 2010 at 12:18 pm:
I'm loathe to post again, but I can't delete my past pending comments and I'm still fighting with this code.
Right now what happens in firefox is that it displays the flash fallback and then underneath, displays a broken ogg-less html5 player window. Has anyone else had this issue? It could be a quirk of the css that I use, but I don't have any styling on the video tag itself.
philip wrote on August 4, 2010 at 12:21 pm:
can you post a link? without seeing your code, we have no way of knowing what's going on.
Wiley Wiggins wrote on August 4, 2010 at 12:31 pm:
Interesting, I made the:
//document.body.removeChild(video);
video.parentNode.removeChild(video);
modification someone above recommended and it fixed it.My page is here: http://wileywiggins.com/catalog/
with document.body.removeChild(video); in there it would inject the flash fallback, but the broken video element would still stay in the dom.
I have a second video on the page that I would like to do the same thing to, what's the correct way to add a second id to your javascript (sorry, I am a novice javascripter).
philip wrote on August 4, 2010 at 12:36 pm:
yes, the <code>video.parentNode.removeChild(video);</code> edit is a good one, I keep forgetting to update my script.
if you have multiple videos, just invoke <code> replaceVideoWithObject</code> for each one.
<pre>
window.onload = function (){
var video = detectVideoSupport();
if(video.ogg && !video.mp4){
replaceVideoWithObject("myFirstVideoID");
replaceVideoWithObject("mySecondVideoID");
replaceVideoWithObject("myThirdVideoID");
}
};
</pre>
Wiley Wiggins wrote on August 4, 2010 at 1:01 pm:
easy enough, thanks!
J-Flex wrote on August 17, 2010 at 7:16 pm:
Why not go the distance and fork for presenting an Ogg version?
philip wrote on August 17, 2010 at 7:18 pm:
@j-flex the point of the post is that supporting Ogg (or any system that requires maintaining two versions of a video) is unrealistic for most people.
Useful code, but doesn’t this take us back to the bad old days of browser detection to get around flaws in a particular browser (not naming any names IE6)?
I guess its inevitable whilst a standard emerges, just makes me feel that if workarounds like this prevail then
a) Firefox won’t be under any pressure to offer H.264
and
b) Adobe still wins as everyone will use Flash as a fall back… inevitabley meaning that lazy people will just Flash to encapsulate their video as it means all their problems are solved in one.
I completely understand your point, but we’re already doing browser sniffing on a grand scale via frameworks like jQuery, YUI, Dojo and MooTools. The difference with the frameworks is that the sniffing is done behind a curtain, so you never have to think about it.
Personally, I believe Mozilla is feeling a ton of pressure to support H.264 and will cave. With almost everyone skipping OGG as a video format, Firefox becomes less relevant for people’s needs. Mozilla may be non-profit, but they don’t want to become irrelevant, esp. with IE9 on its way and Chrome stealing its user base.
RE: Adobe and lazy people, the solution presented in this post (which will be revised soon, stay tuned!) enables savvy developers to use HTML5 video as a first option and Flash as a fallback. That means the videos work on mobile devices (iPhone, Android etc) without requiring Flash. I think that’s a strong enough incentive to get developers to choose HTML5 first — and why Apple refuses to put Flash Player on iPhones.
RE: the “bad old days,” Browser Wars II has been afoot for quite some time now; the difference this time is that we’re making efforts to make it less painful for end users by providing useful alternatives or scripted workarounds.
For example, HTML5 features are not evenly implemented in the various browsers, so there is a lot of detection going on at the moment for features such as local storage, HTML5 form elements, CSS3 support and so on. This pipwerks blog uses the famous ‘HTML5 shim’ to enable me to use HTML5 page elements such as
article
andsection
in older browsers.As of jQuery 1.3, the library contains no more browser sniffing (preferring feature detection). See http://docs.jquery.com/Release:jQuery_1.3
This script does a nice bit of feature detection (good), but then falls back on browser sniffing (bad). Of course finding a way to detect this “feature” is the challenge …
So far, this is the only solution I’ve found – thanks!
Believe me, I hate browser sniffing as much as the next person, but it’s sometimes required. On a side note, MooTools has recently shifted positions and will use browser sniffing instead of feature detection in its next release. This is due to the difficulties they’ve had with feature detection across versions of browsers, esp. Firefox (3.6 broke their existing feature detection).
Firefox does what the spec requires. Opera, Safari and Chrome do as well. The reason you saw the fallback in Opera is because you tested Opera 10.10 which doesn’t support video. Try Opera 10.50.
Why are you sniffing for Firefox? It’s not needed. If you just remove the sniffing, it works everywhere.
BTW, type is not a valid attribute on video. You might want the source element.
Opera 10.5 behaves identically to Firefox 3.6 — it fails to show the fallback content if the video element is present.
As for the ‘type’ attribute, I was unaware it wasn’t official (oops) but further testing proves that simply using the
source
element as you recommend does NOT make a difference. Here are test pages to prove it:video
element.source
element, including ‘type’ attribute to specify “video/mp4”.source
element, excluding ‘type’ attribute.All three of these test pages fail in Firefox 3.6 and Opera 10.5b (both Mac OS X). If I remove the script fix, the Flash fallback does not display.
On a side note, I have already created a revised version of the script that takes Opera into account.
I haven’t had time to post it yet, but will try and post it this weekend.The edits have been incorporated into this post and accompanying demo files.Update: The demo page that left out the ‘type’ attribute in the
source
element failed when I tested in Safari on the iPhone. So it appears the safest route is to use thevideo
element with asource
child element, and be sure thesource
element includes the ‘type’ attribute.Thanks for the update!
Nice work…
Thanks for sharing this information.
This is great. The only problem is that it does not display on the iPad. Not sure why. Trying to figure out a solution.
Unfortunately, I am not privileged enough to test on an iPad… yet. 😉 It works on my iPhone and iPod Touch, though it displays a link to the Apple Quicktime video player rather than displaying the video in the HTML page itself.
Thanks for the info, and please let me know if you make any discoveries!
Thank you help! 🙂
I modified a line and it’s works:
//document.body.removeChild(video);
video.parentNode.removeChild(video);
I tested this on the iPhone Simulator in iPad mode, and it worked, so I think you’re safe!
I was using this method for a client today, and since I was already using jQuery on their site I came up with a little jQuery snippet to remove the video element (hopefully the comment system doesn’t mash this code):
$(function(){
vid = $(‘#myvideo’);
if(vid.length && !vid.get(0).currentSrc)
{
vid.children(‘object’).unwrap()
}
})
It turns out you can check for the ‘currentSrc’ attribute on the actual video element (once the DOM is loaded). If it has no currentSrc, that means it couldn’t find a supported format to play, so it gets “unwrapped”.
It seems like this method of detection would be the most broad solution to the problem – you can include whatever video types you like, and if the browser doesn’t support them, it will fall back to Flash.
I am completely stymied as to why this isn’t working for me. I can see it working in firefox in your example, but I still get an ugly OGG “X” in my firefox instead of the flash fallback.
http://wileywiggins.com/catalog/
fixed it! sorry!
I’m loathe to post again, but I can’t delete my past pending comments and I’m still fighting with this code.
Right now what happens in firefox is that it displays the flash fallback and then underneath, displays a broken ogg-less html5 player window. Has anyone else had this issue? It could be a quirk of the css that I use, but I don’t have any styling on the video tag itself.
can you post a link? without seeing your code, we have no way of knowing what’s going on.
Interesting, I made the:
//document.body.removeChild(video);
video.parentNode.removeChild(video);
modification someone above recommended and it fixed it.
My page is here: http://wileywiggins.com/catalog/
with document.body.removeChild(video); in there it would inject the flash fallback, but the broken video element would still stay in the dom.
I have a second video on the page that I would like to do the same thing to, what’s the correct way to add a second id to your javascript (sorry, I am a novice javascripter).
yes, the
video.parentNode.removeChild(video);
edit is a good one, I keep forgetting to update my script.if you have multiple videos, just invoke
replaceVideoWithObject
for each one.easy enough, thanks!
Why not go the distance and fork for presenting an Ogg version?
@j-flex the point of the post is that supporting Ogg (or any system that requires maintaining two versions of a video) is unrealistic for most people.