Last year I wrote a post (Building HTML5 video controls with JavaScript) introducing the HTML5 Media Elements API and demonstrating a simple set of controls for playing video.
In this (somewhat belated) follow-up I’m going to explore building a more interactive set of controls using a JavaScript UI library; I’m going to use Glow, but it could easily be adapted to jQuery UI or similar.
You can see the player I’ll be using as an example here — although it must be stressed that it’s not a final version, for reasons I’ll cover at the end:
The video player itself is pretty straightforward:
<video autobuffer controls height="180" poster="BBB_poster.jpg" width="320"> <source src="bbb.mp4" type="video/mp4"> <source src="bbb.webm" type="video/ogg"> <source src="bbb.theora.ogg" type="video/ogg"> <img alt="Film Poster" height="180" src="BBB_poster.jpg" width="320"> </video>
The video
element has the controls
attribute set, which we’ll remove with JavaScript later. Note also the poster
attribute, which displays a still image until the video is ready to be played, at which point it displays the first frame instead.
Next there are two source
elements, which serve the video to Firefox, Opera and Chrome (ogg) and Safari (mp4). Finally there’s an img
element, which displays if the browser doesn’t have video support. Update: Added WebM support.
As for the controls, rather than list them here I’ll ask you to view the source to see what I’ve done. Basically I’ve added a bunch of form elements; two input
image
types for the play and volume icons, and two input
text
types for the duration and volume sliders. The latter two aren’t necessary, but I wanted them there for accessibility reasons.
How I’ve styled the player doesn’t matter too much; I’ve been influenced by the Quicktime player for the layout of the controls, but really the CSS isn’t too important here. The only thing to note is that I’ve added some rules here for screen overlays, which I’ll explain in due course:
.overlay { background-repeat: no-repeat; height: 180px; position: absolute; width: 320px; } .paused { background-color: rgba(0,0,0,0.2); background-image: url('pause.png'); background-position: left bottom; } .play { background-image: url('play.png'); background-position: center center; }
You can see all the script I’ve used in the file video.js. I’ll go through some of the more important functions in turn.
The first thing I’ve done is removed the native controls from the player for people who have JS enabled, so as not to provide two conflicting sets of controls:
video[0].controls = false;
Next I’ve defined some of the key variables which I’ll be using throughout the script. One of those variables, volumeSlide, is assigned to one of Glow’s native widgets, aSlider; this will be used to control the volume:
volumeSlide = new glow.widgets.Slider('#volume',{bindTo:'#vol_count', buttons:false, step:0.1, min:0, max:1, size:70, val:1});
You can see what all the options do in the Glow documentation, but the key ones I’ve set are for it to appear in <div id=“volume”>, to have a minimum value of 0 and a max of 1, and to increment in steps of 0.1. This matches the volume setting for the video
element.
For the next step I’m going to create another slider, but this time for the duration/seek bar. In order to do this, however, I need to query the video’s metadata to know what the duration of the video is, and in Safari (which uses mp4 video) that doesn’t load before the rest of my JS has run.
To get around this I’ll poll the readyState
attribute every half a second — with thesetInterval
function — until it’s value is at least 1, which means the metadata has loaded; once that’s done, I’ll load the slider:
t = window.setInterval(function() { if (video[0].readyState >= 1) { window.clearInterval(t); durationSlide = new glow.widgets.Slider('#vid_duration',{bindTo:'#duration_count', buttons:false, step:1, min:1, max:Math.round(video[0].duration-1), size:260, val:0}); playVid(); } },500);
So you can see there that I’ve created the slider with a minimum value of 1 and a maximum of the duration of the video (in seconds), to increment in steps of 1. After that the setup is complete so I can begin the actual playback functions.
There are too many functions to go into in detail, so I’ll quickly go through what happens. First an overlay is placed over the top of the video, which begins playback when clicked. Next, event listeners are added to the play button, the volume icon, and the volume and duration sliders.
The listener on the play button runs the function playControl, which determines the state of playback (ended, paused or playing) and either plays or pauses the video accordingly. It also updates the icon to reflect its action (if it is paused, the icon changes to a play icon, and vice versa), and adds the pause overlay onto the video screen when relevant:
function playControl() { if (video[0].paused === true) { video[0].play(); /* Further functions here */ }; } else if (video[0].ended === true) { video[0].play(); /* Further functions here */ } else { video[0].pause(); /* Further functions here */ }
There’s a function called startCount which uses setInterval
to move the duration slider along by one second while the video is playing, and a function called pauseCount which uses clearInterval
to pause.
The muteToggle function does what you expect, and mutes the video; it also changes the volume icon to show that state, and disables the volume slider while it is active.
A further function, volumeIcons, sets the state of the volume icon; there are four possible icon states, which are used depending on the value of the volume.
And the last function, secondsToTime, converts second values into hour/minute/second values, allowing for the timer to be updated. This is done every second by the startCountfunction, and also used for the function which is called from the event listener on the duration slider.
That event is probably worth looking at in detail:
glow.events.addListener(durationSlide,'slideStop',function(event){ video[0].currentTime = event.currentVal; var currentSecs = secondsToTime(event.currentVal); vidTimer.text(currentSecs.h + ':' + currentSecs.m + ':' + currentSecs.s); });
Using the slideStop
event I can check when the slider has been moved, and first set the video to begin playback from that point, then update the timer with the same values. The volume slider has a similar event set on it.
So as a reminder, here’s what I have so far:
Please bear in mind that this is very much a work in progress; I started writing the controls without Glow and introduced it at a later stage, so some of the JavaScript could do with being optimised.
The markup for the controls could also do with some extra work to make them fully accessible, which they probably aren’t right now. Also, all of the dimensions are built around this video size, and won’t scale if different sized videos are used.
I hope to return to this topic when I have more time, and create a robust set of video player controls which can be used in any site without extra work.
Please feel free to let me know if you encounter any bugs or oddities as you use this; it will help with the next stage of development.