Multimedia: Scripting Media Players - JUMP START HTML5 (2014)

JUMP START HTML5 (2014)

Chapter 11 Multimedia: Scripting Media Players

Now that you know how to add video and audio elements using markup, let's add some DOM scripting. In this chapter, we'll look at customizing the look and feel of your audio or video element with HTML elements, CSS, and scripting.

If you're not familiar with DOM scripting—better known as JavaScript—don't worry. We'll go easy in this chapter. But if you'd like to learn more (and you should) try SitePoint's Jump Start JavaScript. The World Wide Web Consortium's Web Platform Docs also has some good tutorials to get you started.

Every HTML element has a DOM scripting interface. An interface is a group of attributes, constants, events, and methods that are available to a scripting language. Some elements, like p have simple interfaces. Not many properties apply to the p element. Others like audio and video have a robust interface that allow us to interact with and control every state of the element.

As we saw in earlier chapters, audio and video have a lot of attributes in common. This is also true for their scripting interfaces. In this chapter, we'll focus on creating a video player. However, most of what we'll discuss also holds true for audio. Differences and exceptions are noted.

Event-Driven DOM Scripting: An Introduction

To create our player, we'll need use a technique known as event-driven programming. The audio and video elements fire events during media loading and playback. We can use DOM scripting to listen for these events, and take an action when one occurs. It's a bit like an actor listening off-stage for his cue to walk on stage, but in code form.

Both elements fire events for all sorts of things. For this player, however, we'll only talk about three:

· durationchange: fired when the browser has downloaded enough of the file to determine the duration of our media file

· timeupdate: fired whenever the media position is updated

· volumechange: fired whenever the volume changes

There are two ways that we can listen for an event: We can use the element's event handler attributes or we can use the addEventListener() DOM method. Event handler attributes are basically the event names, prefixed with on ― ondurationchange, ontimeupdate, and onclick, for example. Most HTML5 events have these attributes, and it's perfectly acceptable to use them, e.g.

object.onclick = function_to_invoke;

We're going to use addEventListener() in this chapter, however. Using addEventListener() gives us more flexibility. With event handler attributes, we can only invoke one function per event. With addEventListener(), we can invoke several functions at once. The basic syntax is as follows:

object.addEventListener('event_name', function_to_invoke);

When an event fires, an event object will be passed to the listener or callback function as its argument. An event object is a container for passing data. If we'd like to use that data within our callback function, we need to make sure that our callback function has a defined parameter. To build on the generic example above, our function_to_invoke would resemble the sample below.

function function_to_invoke(event){

// function body that acts on our event

}

Each event object contains several properties, and we can retrieve each property using dot syntax. The basic form is eventobject.property where eventobject is the name of our function parameter. We'll be most concerned with the target of the event object in this chapter, so we'll useevent.target in almost all of our functions. The target property is a reference to the object on which the event was fired.

target is also an object in its own right. It contains properties such as id that are common to all HTML elements. But it also contains properties that are specific to the object type: duration for video objects, or value for form controls. It is a tough concept to wrap your brain around at first, so it may take you a few read-throughs to grasp fully.

Step 1: Creating Our Markup

There are two ways to create a media player using DOM scripting: completely with JavaScript (and the document.createElement() method) or by scripting markup. We're going to choose the latter. Let's build our markup:

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>

Scripting an audio and video player

</title>

<link rel="stylesheet" type="text/css" href="video.css">

</head>

<body>

<div id="video_wrapper">

<video preload="metadata">

<source src="./video_file.mp4" type="video/mp4">

<source src="./video_file.webm" type="video/webm">

</video>

<div id="video_time">

<progress value="0" id="playback"></progress>

<span id="elapsed"></span> <span id="duration"></span>

</div>

<div id="video_seek">

<label for="seek">Seek</label>

<input type="range" id="seek" title="seek"

↵min="0" value="0" max="0">

</div>

<div id="video_controls">

<button type="button" id="play">Play</button>

<button type="button" id="pause" class="hidden">

Pause

</button>

<label for="volume">Volume</label>

<input type="range" id="volume" title="volume"

↵min="0" max="1" step="0.1" value="1">

</div>

</div>

<script type="text/javascript" src="video.js"></script>

</body>

</html>

Here, we're using range input types for our volume and seek controls. It's a new form control type in HTML5. In older browsers, range inputs default to text fields. We've also added a preload attribute and set its value to metadata. As you may remember from earlier in this book, the preloadattribute, when set to metadata prompts the browser to download just enough to determine the duration and dimensions (in the case of video). Your page will resemble Figure 11.1.

What your video player should look like (with minimal styling) as shown in Opera 17

Figure 11.1. What your video player should look like (with minimal styling) as shown in Opera 17

Note: IE9 and Older Versions of Firefox

Internet Explorer 9 and Firefox 3.5 - 22 do not support input type="range". You'll need to use a JavaScript alternative such as jQuery UI Slider or fd-slider for those browsers.

Step 2: Retrieving Our Video Object

Before we can do any more, we'll need to create a video object for our script to act on. We'll use the video element from our markup, and retrieve it using the document.querySelector() method. This method, part of the Selectors API, will return the first object in the document that matches the selector. It accepts a CSS selector as an argument—in this case, the video element selector.

var video = document.querySelector('video');

If your video element has an id attribute, you could also use the document.getElementById() method, as shown below.

var video = document.getElementById('my_video');

Either of these will return an object of the type HTMLVideoElement. If this was an audio element, our object would be an HTMLAudioElement object. Both HTMLVideoElement and HTMLAudioElement interfaces with defined properties that we can read and write using DOM scripting. Some properties, such as controls have corresponding HTML attributes. Others like volume are only available through the scripting interface.

Through the rest of our chapter, we'll add event listeners to this object (and other objects), then read and modify its properties using DOM scripting.

Step 2: Playing and Pausing Video

Most of what you'll ever want to do with a player is, well, play a video file. You may also want to pause it to take a phone call, or hide your goofing off from your boss. The HTMLVideoElement object has two, self-explanatory methods for these tasks: play() and pause(). Any time the play()method is called, playback will begin or resume. When the pause() method is invoked, playback will pause.

We'll make sure these methods are invoked at the user's request. Play and pause buttons are already part of our markup, so now we just need to add an event listener to each. First, let's reference our buttons as variables:

var play = document.getElementById('play');

var pause = document.getElementById('pause');

Since these are buttons, they'll most likely be clicked on. So let's add click event listeners to both.

play.addEventListener('click', clickhandler);

pause.addEventListener('click', clickhandler);

Next, we'll need to define our clickhandler function. Because these buttons will perform similar actions, we can use the same function for both. We'll invoke one function or the other based on the value of the event.target.id property.

function clickhandler(event){

var id = event.target.id;

if( id == 'play' ){

video.play();

}

if( id == 'pause' ){

video.pause();

}

}

Step 3: Determining the File's Duration

Media objects have a duration property that's only available through the scripting interface. This property becomes defined once the browser has downloaded the file's metadata.

When the browser can determine the running time of the media file, it fires a durationchange event on the audio or video object. We can listen for this event and use it to update our duration display. Our markup also has a progress bar and a range control for seeking. In order for those controls to work correctly, we'll need to set the max attribute for those controls to the value of our duration property.

First let's look at our updateduration function:

function updateduration(event){

var durationdisplay = document.getElementById('duration'),

elapseddisplay = document.getElementById('elapsed');

durationdisplay.innerHTML = event.target.duration;

elapseddisplay.innerHTML = event.target.currentTime;

}

In this function, we have retrieved the span#duration and span#elapsed elements. Then we're updating the text between the <span> tags with the values of duration and currentTime of our video object.

We'll also define two more functions, one that updates the value of the max attribute for the progress bar, and another that updates the value of max for the seek control. If we didn't set the max values, our progress bar wouldn't accurately indicate how much of the video has elapsed, nor would our range control allow us to scrub forward or backward.

var seek = document.getElementById('seek'),

playback = document.getElementById('playback');

function updateseekmax(event){

if( event.target.duration ){

seek.max = event.target.duration;

}

}

function updateplaybackmax(event){

if( event.target.duration ){

playback.max = event.target.duration;

}

}

Now let's add these functions to our video object as durationchange event listeners.

video.addEventListener('durationchange', updateduration);

video.addEventListener('durationchange', updateseekmax);

video.addEventListener('durationchange', updateplaybackmax);

As our document loads and the durationchage event is fired, these three functions will be called.

Step 4: Indicating Time Elapsed

Once our media file begins playback, we'll want to indicate how much time has elapsed. To do this, we'll need to listen for the timechange event, and in our callback function, read the currentTime property of our video object.

timechange is an event fired periodically—every 15 to 250 milliseconds—during media playback. When fired, we'll check the value of the currentTime property and update the text in our elapsed display. This currentTime property returns the current playback position of the media file in seconds. Let's set up our callback function.

function timeupdatehandler(event){

var elapsed = document.getElementById('elapsed');

elapsed.innerHTML = event.target.currentTime;

}

Then we can add an event listener to the video object.

video.addEventListener('timechange', timeupdatehandler);

currentTime is what's known as a getter/setter property. We can use it to get the current point in the media timeline. But we can also use it to move between points in the media timeline. Using video.currentTime = 0, for example, resets the current playback point to the beginning. This is the secret to creating a custom seek bar.

Step 5: Seeking Using a range Input Type

Range input types work by having a minimum value and a maximum value (set using min and max attributes). Positions along the control correspond to values between the minimum and maximum values of the range—in this case 0, and the media file's total duration in seconds.

All input elements, including range, fire change events when a user changes the value of the control. When the event object is passed to our handler, we can read the range control's current value attribute, and update the value of video.currentTime. To keep our progress bar in sync, we'll update its value as well.

Let's look at our callback function and event listener:

function seekhandler(event){

video.currentTime = event.target.value;

playback.value = event.target.value

}

seek.addEventListener('change', seekhandler);

When the user adjusts the seek control, our video will advance or reverse accordingly.

Advancing the video using our seek control. Video image from "Sita Sings the Blues" by Nina Paley (sitasingstheblues.com)

Figure 11.2. Advancing the video using our seek control. Video image from "Sita Sings the Blues" by Nina Paley (sitasingstheblues.com)

Step 6: Adjusting Volume

Our volume control works similarly to the seek control.

HTML media objects have a volume attribute that's available through the scripting interface. Its lower boundary is 0.0, and its upper boundary is 1.0. Acceptable values fall somewhere in between. All values are relative to the intrinsic volume of the media file in question.

Our volume range control has a min value of 0 and a max values of 1 to correspond to the upper and lower boundaries of the volume property. Since these values are less than or equal to one, we need to add step="0.1" to our tag so that values increment appropriately.

As with our seek control, when the user changes the volume control, the browser will fire a change event. We can then read its event.target.value property and update the video objects's volume property with that value. In the code below, we've defined our callback function, and adding an event listener to our volume control.

var volume = document.getElementById('volume');

function volumehandler(event){

video.volume = event.target.value;

}

volume.addEventListener('change', volumehandler);

From here, you can style your player as you'd like with CSS. I'll leave that to you.

Hinting at Bandwidth Consumption by Changing the Value of preload

In Chapter 7, we mentioned that the preload attribute provides hints to the browser about how much of our media should be downloaded at a time. Using preload="none" keeps the browser from downloading any portion of the file, but once the user initiates playback, the browser could aggressively download the media. Using preload="metadata" is a hint to the browser that it should behave as though bandwidth were at a premium.

If bandwidth conservation is your goal, the combination of preload="none" and preload="metadata" is the way to go. To do that, however, we'll need to use scripting to change the value once the user initiates playback. Let's update our clickhandler function from above.

function clickhandler(event){

var id = event.target.id;

if( id === 'play' ){

video.play();

video.preload = 'metadata';

}

if( id === 'pause' ){

video.pause();

}

}

When the user clicks Play, this function will also change the value of preload alerting the browser that it should throttle its download. Remember, however, that these are hints; browser behavior isn't guaranteed.

Wrapping Up

In this book we've covered media encoding and HTML5 markup. We've learned how to make our media accessible. And we've learned the basics of building a player using the multimedia scripting APIs. There's more than what we've covered here, however.

Though dense, I recommend reading the documentation for the video element from HTML: The Living Standard. It's maintained by the WHATWG and geared towards web developers rather than browser vendors.

Advertising company LongTail maintains a The State Of HTML5 Video page that tracks current browser support for particular multimedia features.

Mozilla.org also provides a guide to using HTML5 audio and video that talks a bit more about things we haven't covered, like error handling, and specifying a playback range. Also see Dev.Opera's Introduction to HTML5 video for more tips and tricks on scripting a video player.

You're now equipped with what you need to know about HTML5 audio and video. Happy coding!