Add HTML5 video to your site

Embed native HTML5 video into your pages without plug-ins, and provide Flash-based fallback content for legacy browsers. Opera’s Vadim Makeev shows the way

Want to add video to your site? HTML5 enables us to do this as easily as placing images with an <img> element – and in this tutorial, we’ll show you how the magic is done.

In the example code below, the src attribute points to the video file and controls indicates that we want the browser to show control buttons (play/pause, volume and so on). The value of the optional poster attribute is the path to the static image that the browser will show while the video is loading and before a user hits play. It’s a good idea to pick an appealing frame that will persuade users to view the video. If you don’t use poster, the first frame of the video will be displayed. The code between the opening and closing video tags is fallback content for browsers that have no HTML5 video support.

<video src="m/video.ogv" controls poster="m/video.jpg" width="854" height="480"><p>Your browser can’t play HTML5 video. <a href="m/video.ogv"> Download it</a> instead.</p></video>

As you can see, the syntax here is beautifully simple. Well, that’s the theory. To have real cross-browser video, we need to include two files: one encoded as Ogg Theora and one in H.264 format. To specify more than one source for a video element, we drop the src attribute and instead include all the alternative formats with <source> elements:

<video controls poster="m/video.jpg" width="854" height="480"><source src="m/video.ogv" type="video/ogg"><source src="m/video.mp4" type="video/mp4">...</video>

The type attribute in this case tells the browser about the container format (and optionally the codec) of the video. See for more details and for a demo.

Serving Flash to older browsers

For extra compatibility with less recent browsers, let’s put the old-style Flash embedding code inside the video element as fallback, so that if the browser doesn’t know anything about the beauty of HTML5 native video, it defaults to the Flash fallback content inside the tags.

Flash supports playback of H.264 video, so all we need is our ready-made Flash player (at m/player.swf), and we’ll pass it the URL for the H.264 version of our video as a parameter. It’s important to note that the path to the MP4 needs to be either absolute or relative to the location of the SWF file – for simplicity, we’ve placed the player and the videos in the same directory.

<object type="application/x-shockwave-flash" data="m/player.swf" width="854" height="504"><param name="allowfullscreen" value="true"><param name="allowscriptaccess" value="always"><param name="flashvars" value="file=video.mp4"><!--[if IE]><!--><param name="movie" value="m/player.swf"><!--<![endif]--><img src="m/video.jpg" width="854" height="480" alt="Video"><p>Your browser can’t play HTML5 video. <a href="m/video.ogv">Download it</a> instead.</p></object>

So, if we combine all of this together, our final code looks like this:

<video controls poster="m/video.jpg" width="854" height="480"><source src="m/video.ogv" type="video/ogg"><source src="m/video.mp4" type="video/mp4"><object type="application/x-shockwave-flash" data="m/player.swf" width="854" height="504"><param name="allowfullscreen" value="true"><param name="allowscriptaccess" value="always"><param name="flashvars" value="file=video.mp4"><!--[if IE]><!--><param name="movie" value="m/player.swf"><!--<![endif]--><img src="m/video.jpg" width="854" height="480" alt="Video"><p>Your browser can’t play HTML5 video. <a href="m/video.ogv"> Download it</a> instead.</p></object></video>

Make your own controls

Everything looks fine now: older browsers will show the Flash player, modern browsers will use their native video capabilities to show whichever format video they can support. As our video element has the controls attribute set, browsers with native video will use their default playback controls. For many, that will be enough, but it might be a problem that the default controls look slightly different from browser to browser, or don’t fit in with the design of your site.

All it takes to control a video is some straightforward JavaScript. Here we've added two basic buttons, one to play the video and one to mute the sound

So, let’s try to create our own HTML-based panel that can control the video via JavaScript. We’ll keep it simple and just include a Play and Mute button. The most logical choice for these is to use the trusty <button> element with corresponding text inside. We’ll add these into the DOM using script, so users without JavaScript won’t be bothered with the functionless buttons that they’d get if we put these elements in the markup. For a similar reason, we’ll continue to specify the controls attribute in the markup and remove it with our script, so native controls remain available for users without scripting capability.

There are nine steps to our script: check for video support; get all video containers on the page; get every single video instance; get and create all needed elements; turn off native video controls; customise Play button behaviour; customise Mute button behaviour; add custom controls to the video container; and initialise function on page load.

<script> function init() { // 1. Check for video support if( !document.createElement('video').canPlayType ) return; // 2. Get all video containers on the page var videos = document.querySelectorAll( '' ), videosLength = videos.length; // 3. Get every single video instance for( var i=0; i < videosLength; i++ ) { var root = videos[i]; // 4. Get and create all needed elements video = root.querySelector( 'video' ), play = document.createElement( 'button' ), mute = document.createElement( 'button' ); // 5. Turn off native video controls video.controls = false; // 6. Customise Play button behaviour play.innerHTML = play.title = 'Play'; play.onclick = function() { if( video.paused ) {; play.innerHTML = play.title = 'Pause'; } else { video.pause(); play.innerHTML = play.title = 'Play'; } } // 7. Customise Mute button behaviour mute.innerHTML = mute.title = 'Mute'; mute.onclick = function() { if( video.muted ) { video.muted = false; mute.innerHTML = mute.title = 'Mute'; } else { video.muted = true; mute.innerHTML = mute.title = 'Unmute'; } } // 8. Add custom controls to the video container root.appendChild( play ); root.appendChild( mute ); } } // 9. Initialise function on page load window.onload = init;</script>

Now we can control a video with nothing more than some simple JavaScript. The buttons work fine, but they don’t look much better than the native video controls. However, before we start doing some styling, let’s add all the necessary classes to our buttons via JavaScript:

play.className = 'video-button video-play';play.onclick = function() { if( video.paused ) { ... // Additional class names for container and button while playing root.className += ' video-on'; play.className += ' video-play-on'; } else { ... // Remove additional class names for container and button in paused state root.className = root.className.replace( ' video-on', '' ); play.className = play.className.replace( ' video-play-on', '' ); }}

Our buttons will be square but, with some tricky rounded corners (using the much-loved border-radius property, set to exactly half of the width/height of the square), we’ll style them to look like circles:

.video-play {border-radius:25px;}

Let’s add more dimensions with the box-shadow property, specifying all necessary values separated by commas, with the help of the inset keyword:

.video-play {box-shadow:0 0 50px #FFF,inset 5px 5px 20px #444,inset 0 -20px 40px #000;}

Unfortunately, the current version of Safari (4.0.5) doesn’t support the inset keyword, but Google Chrome (with its newer Webkit engine), Opera and Firefox all correctly display our shadows (albeit with minor visual differences).

Now, let’s place a small icon under every button to show its current state: Play/Pause and Mute/Unmute. We’ll add it via CSS-generated content placed after each button with the ::after pseudo-element (the double colon is newer syntax; we’ll use a single colon for backwards compatibility):

.video-button:after {position:absolute;background:url(i/buttons.png) no-repeat;content:'';}

To reduce HTTP requests across the network, we’re using CSS sprites: all icons are put inside a single image, which is placed using background-image and will be moved by the background-position property according to our needs. But there’s another niggle: Firefox has a problem with a positioning bug. For some reason, it thinks the position origin should be in the middle of the element, while the rest of the browsers are (rightly) basing the positioning coordinates on the top-left corner. For this reason, we just can’t position our icons correctly in Firefox – let’s hope this bug gets fixed soon.

So, at the moment there are two browsers that will show our experiment properly: Opera and Google Chrome. It’s acceptable in our case because we’re just trying things out. After all, we don’t need the other visual niceties.

Finishing touches

There’s only one thing left to add – more eye candy. For example, we can make the video semi-transparent in the beginning, less transparent on hover and completely opaque while playing. The background colour of the container is the colour our semi-transparent video will be dissolved in.

.video video {opacity:.4;}.video:hover video {opacity:.6;}.video-on video,.video-on:hover video {opacity:1;}

Our experiment wouldn’t be complete without some fancy animations. Let’s add a couple of properties from the draft CSS3 transforms and transitions specifications to make our interface and video pop: a smooth increase of the buttons’ size on hover, and animation of video transparency changes. We’ll start with the buttons: using the transform:scale(…) property we increase the button size on hover by an extra 10%. First, we define a button’s default scale:

.video-button { transform:scale(1.0);}

For full compatibility we should repeat this property four times with vendor prefixes: -o- for Opera, -webkit- for Safari and Google Chrome, -moz- for Firefox and all Gecko browsers, and one property without the prefix for future browsers that will support this property.

Next, we define the animation itself. We use the transition property to smoothly animate changes on any of the button properties for 0.2 seconds with linear acceleration.

.video-button {transition:all .2s linear;}

Lastly, we define the property that needs to change when the button is hovered. In our case, the size increase by 10%:

.video-button:hover {transform:scale(1.1);}

We use a similar approach to define all the different states of video transparency that we want to display.

Now we have luxuriously styled video that reacts nicely to the user’s actions and is controlled by smoothly animated buttons (see a demo here). And all of this using only pure HTML, CSS and JavaScript, without any plug-ins (except for the small concession we make in the fallback content). Now that’s magic.

Expert tip: iPad issues

Some people running the code in this tutorial on their iPads may find that the videos wouldn’t play. It’s been reported that the iPad has a bug, whereby it will only see the first <source> element inside a <video> element.

To get around this, you simply need to change the order of the <source> elements, so that the mp4 file that Apple loves so much is first, then pull in the open format version – either Ogg or the new WebM format (see for details).

There’s also a bug in some versions of the iPhone that means videos won’t play if there is a poster attribute. In short, experiment with the order of <source> elements and poster attributes.
This is a good opportunity to remind everyone to add links to the video files themselves, so those who can’t access Flash or the HTML5 video can still download the video for offline viewing on their operating system’s media player.