7 November 2011
Familiarity with HTML and JavaScript.
Intermediate
This is Part 3 in a three-part series of tutorials on HTML5 multimedia. In Part 1 and Part 2, I covered the video and audio elements, respectively, and briefly showed how adding the controls attribute to these elements informs the browser to add a set of default controls to the media element in question. If you tried out the sample applications for those tutorials, you may have noticed that the controls look different depending on the browser you are using.
If you want to achieve a uniform look across browsers for your media controls, you can use the handy HTML5 media element API. You can create and style your own media control set using standard HTML and CSS and then use the media element API to hook it up to the audio and video elements you want to control.
This tutorial describes the steps needed to gradually build up a custom media player, adding various features and functionality in the process, and using different API attributes, events, and methods. You can see the completed media player by downloading and exploring the sample files for this article.
Note: This tutorial uses a video example, but the steps can be easily adapted to handle audio.
To begin with, you'll need to define a video element to use with the yet to be created media player:
<video id="video" controls>
<source src="grass-in-the-wind-sma.mp4" type="video/mp4">
<source src="grass-in-the-wind-sma.webm" type="video/webm">
</video>
You'll notice that the controls attribute has been defined for the video, even though you're going to create your own. Since your custom controls will be built in JavaScript, you're going to turn the default controls off via JavaScript. That way, if a user has JavaScript turned off, they'll still be served with the browser's default control set.
To turn the default controls off, you simply set the video element's controls attribute to false:
<script>
// Grab a handle to the video
var video = document.getElementById("video");
// Turn off the default controls
video.controls = false;
</script>
And with that, you're ready to move on!
The first and most basic requirement for any media player is the ability to play and pause the media in question. For this example, you'll use a single button, which will serve as a play button when the video is paused (or stopped) and a pause button when it's playing.
<div id="controls">
<button id="playpause" title="play">Play</button>
</div>
Next you need to create a JavaScript function that will do the work of changing the button title and starting or pausing the media.
In this example, the function is named togglePlayPause(). Take a look at the full implementation below; a line-by-line explanation follows.
function togglePlayPause() {
var playpause = document.getElementById("playpause");
if (video.paused || video.ended) {
playpause.title = "pause";
playpause.innerHTML = "pause";
video.play();
}
else {
playpause.title = "play";
playpause.innerHTML = "play";
video.pause();
}
}
To have this function invoked every time the play/pause button is clicked, you add it to the onclick event of the button:
<button id="playpause" title="play" onclick="togglePlayPause()">Play</button>
The first line of the togglePlayPause() function obtains a handle to the play/pause button itself, and assigns it to the variable playpause:
var playpause = document.getElementById("playpause");
Next, it checks the status of the video to see if it's paused or ended, via the two attributes paused and ended. If the video is in either of these states, it then sets the button's title and innerHTML attributes to "pause" and calls video.play() to start playing the video.
If the video is not currently paused or ended, then you can assume it is already playing. In this case, the function sets the button's title and innerHTML to "play" and calls video.pause() to pause the video.
The button's default text is "play." When the button is clicked for the first time, the video will start playing and the button's text will be changed to "pause." Subsequently, when the pause button is clicked, the video will pause and the button's text will be changed back to "play."
As you'll see, the remaining functionality that you'll add in this tutorial follows the same basic format: listen for an event from the video element, check the element's status, and then act on it via API methods.
Another vital piece of functionality for a media player is the ability to control the volume, including the ability to mute it altogether.
To add a volume control, you'll use one of the new HTML5 input types: range. This input type is usually rendered by the browser as a slider, which the user can move from left to right and vice versa, so it's ideal for a volume control. You specify the minimum and maximum values for the range input via the min and max attributes. You use the step attribute to set the amount you want the slider's value to change when the slider's position changes. To create a volume control slider with a range between 0 and 1, and a step size of 0.1, you can use the following code:
<input id="volume" min="0" max="1" step="0.1" type="range" />
When the slider is moved, you want to invoke a JavaScript function that will adjust the volume, so add an onchange event handler:
<input id="volume" min="0" max="1" step="0.1" type="range" onchange="setVolume()" />
Next, create a JavaScript function named setVolume():
function setVolume() {
var volume = document.getElementById("volume");
video.volume = volume.value;
}
This simple function obtains a handle to the volume slider and assigns its value to the video element's volume attribute.
Note: Firefox 7 doesn't support the range input type and displays a text field instead. Typing a new value in this text field (between 0 and 1) and moving the focus away from the text field will alter the volume in this browser.
Adding a mute button is just as easy. Again you start by defining a new button, this time with an onclick handler:
<button id="mute" onclick="toggleMute()">Mute</button>
Next, create a function named toggleMute():
function toggleMute() {
video.muted = !video.muted;
}
This function simply sets the video element's (Boolean) muted attribute to be the opposite of its current value. This toggles the mute status of the button. Easy!
When a video is playing, users are accustomed to checking the progress bar to see how much has played and how much is left to play.
To add a simple progress bar to your media player, you can use a div element and a span element. Specifically, you increase the width of the span element as the video progresses, using it to represent the amount played.
<div id="progressBar"><span id="progress"></span></div>
Of course, you'll want to style these elements with simple CSS so that the progress can be seen easily:
#progressBar {
border:1px solid #aaa;
color:#fff;
width:295px;
height:20px;
}
#progress {
background-color:#ff0000; // red
height:20px;
display:inline-block;
}
Next, define a function that will update the progress bar by changing the width of the span element:
function updateProgress() {
var progress = document.getElementById("progress");
var value = 0;
if (video.currentTime > 0) {
value = Math.floor((100 / video.duration) * video.currentTime);
}
progress.style.width = value + "%";
}
The first line of this function obtains a handle to the progress span element itself. It checks the value of the video element's currentTime attribute, which defines the current playback position, in seconds. If currentTime is greater than 0, and therefore the video has advanced, it calculates the current progress as a percentage using the video element's duration attribute, which contains the video's total length in seconds. Finally, it sets the CSS width of the progress span to this calculated value.
With the play, pause, mute, and volume controls you used events such as onclick and onchange to invoke the appropriate functions. You can't use this approach with a progress bar, because it updates in response to video progress, not user interaction.
The HTML5 media element API, however, raises a number of events that you can listen for and act upon instead. One of these is the timeupdate event, which fires every time the media's currentTime attribute is changed. (This attribute changes as the media is played.)
In the JavaScript initialization code of your web page, add an event listener that invokes the updateProgress function when the timeupdate event fires:
video.addEventListener("timeupdate", updateProgress, false);
Now your progress bar will be updated as the video plays.
The media element API defines a number of events that you can use in implementing a media player. For a complete list, see the W3C's summary of media element API events. Table 1 shows several of the more commonly used events.
Table 1. Media element API events.
Event name |
Description |
playing |
Raised when playback of media is ready to start after having been previously paused |
ended |
Raised when the media has stopped playing as it has finished |
timeupdate |
Raised when the media's current playback position has changed |
play |
Raised when the media that was previously paused is no longer paused and playback has resumed |
pause |
Raised when the |
volumechange |
Raised when the media's |
When you're adding custom controls, it's good practice to listen for some of the available events to make sure your controls are always synchronized with the state of the video.
How might the controls lose synchronization? Recall that you removed the default control set via JavaScript. It is possible, however, for a user to reenable these controls and use them to interact with the video. For example, in Firefox, a user can right-click the video, select Show Controls, and click Play or Pause. If a user did this and started a video playing, then the text on the play/pause button that you created would no longer accurately reflect the media's current state.
Regardless of what mechanism is used to control the video, the appropriate events will still be raised. So you can listen for the pause and play events and act on them accordingly to keep your buttons in synch; for example:
video.addEventListener('play', function() {
var playpause = document.getElementById("playpause");
playpause.title = "pause";
playpause.innerHTML = "pause";
}, false);
video.addEventListener('pause', function() {
var playpause = document.getElementById("playpause");
playpause.title = "play";
playpause.innerHTML = "play";
}, false);
You should also listen for the ended event, so that when the video ends, the play/pause button is also kept up to date. You can do this by calling the pause() method on the video when the ended event is raised:
video.addEventListener('ended', function() { this.pause(); }, false);
Note: The reason you call the pause() method here is that it automatically causes the pause event to be raised which will in turn cause the code we've written above for the pause event handler to be called. You could indeed duplicate the code in the ended event handler, or, if you wanted to do something different or extra, you would define it here.
The final feature to add is a media playlist, which the user can use to change the video played in the media player. This is actually quite simple. First of all you define your playlist; for example:
<ul id="playlist">
<li><a href="#" onclick="playlistClick('grass-in-the-wind-sma');">Grass blowing in the wind</a></li>
<li><a href="#" onclick="playlistClick('tree-in-the-wind-sma');">Trees blowing in the wind</a></li>
</ul>
There are two items in this playlist, and each calls a function named playlistClick() when clicked. This function takes a single argument: the stem of the video file it is to play (that is, the file name without the file extension). This function is defined as follows:
function playlistClick(file) {
var v = document.createElement("video");
if (v.canPlayType("video/mp4") != "") {
changeSource(file + ".mp4");
}
else if (v.canPlayType("video/webm") != "") {
changeSource(file + ".webm");
}
return false;
}
This function first creates a temporary video element and then calls the canPlayType() method for each of the supported video types, which in this case are MP4 and WebM. After determining which file type the browser is capable of playing, it calls changeSource()with one argument, the file stem that was passed into the function concatenated with the appropriate file extension. This function also returns false to prevent the element from following the link to the value of its href attribute.
The changeSource() function is defined as follows:
function changeSource(src) {
resetPlayer();
video.src = src;
video.load();
}
This function calls resetPlayer(), which you'll look at next, and then sets the video element's src attribute to the new video file that has been passed as an attribute. Finally, it calls load()to load the new video source into the video element.
Note: Not all browsers require the load() method to be called, but Safari does. Therefore it's a good idea to call it.
The resetPlayer() function resets a few of the player's components in preparation for loading a new video:
function resetPlayer() {
var playpause = document.getElementById("playpause");
playpause.title = "play";
playpause.innerHTML = "play";
if (video.currentTime > 0) video.currentTime = 0;
updateProgress();
}
First, it sets the play/pause button text to "play." Next it resets the video element's currentTime variable to 0 if it's not already at 0. Finally it calls the updateProgress() function, which will reset the progress bar back to the start. (The progress bar uses the video element's currentTime attribute, which was just set to 0.)
That's it! You've seen all the steps necessary to create a simple HTML5 media player. It is admittedly not the most attractive player available, but you can use CSS to style it and improve its appearance.
To see more of the media element API, its events, and its properties in action, check out the W3C's HTML5 Video Events and API demonstration page, which plays videos with basic controls and displays API properties and events.
You can read more about creating custom controls for HTML5 video and audio in my upcoming book, HTML5 Multimedia: Develop and Design.