HTML5 Video & Skinning Tutorial Part 4: Interactive Progress Bars
June 24, 2010 by Nicholas Davison
Today we will be covering progress bars – updating them as the video plays and letting users jump to any position along them.
Throughout these tutorials, we will be continuing the concepts/implementation division: Explaining the ideas first, letting those who wish to run ahead do so – then going over a step by step implantation with jQuery.
Concepts
currentTime
Yesterday we covered reading and writing the currentTime attribute:
var now=document.getElementById('myVideo').currentTime;
document.getElementById('myVideo').currentTime=now+10;
duration
To display a progress bar, we need to know whether 10 seconds is 50% of a 20 second clip or 10% of a 100 second clip. That extra property is the video’s duration:
var maxTime=document.getElementById('myVideo').duration;
Percentage Played
To calculate a percentage played from those two, we do the following:
var percentage=100 * document.getElementById('myVideo').currentTime / document.getElementById('myVideo').duration;
We need to be careful however: If the video isn’t properly loaded, the duration may be unset or zero, risking divide by zero errors. A more robust version is therefore:
var percentage=0;
if ((typeof document.getElementById('myVideo').duration != "undefined") && (document.getElementById('myVideo').duration>0)) {
percentage=100 * document.getElementById('myVideo').currentTime / document.getElementById('myVideo').duration;
}
timeupdate
Calculating the percentage played is all well and good but how often do we update?
We could run a setInterval every 100 milliseconds but that doesn’t come close to syncing with video frame rates. It also fires regardless of whether video is currently playing or not – loading the processor even when nothing is happening.
Fortunately, the <video> element has an event it fires periodically whenever the video changes its currentTime. We can listen for this event and then update our progress bar whenever it fires:
document.getElementById('video').ontimeupdate=function() {
/* Update Progress Bar */
};
Implementation
HTML
Inside <div class="video_controls" rel="myvideo">, after <ul class="video_buttons" />, we need to add a pair of divs: one for the track the bar sits inside, spanning the whole width and one for the bar itself:
<div class="video_progress">
<div class="video_progress_bar"></div>
</div>
CSS
The CSS is simple too: The track is set to black and 20px high, the bar is set to the same height, a dark red, and absolute positioned, relative to the track, so other elements (say a text description) could be added over the top, later.
.video_progress {
background-color: black;
height: 20px;
position: relative;
}
.video_progress_bar {
background-color: #840909;
height: 20px;
left: 0;
position: absolute;
top: 0;
}
JavaScript
Another Helper Function – Finding Event Positions Within Elements
For users to be able to click anywhere along the .video_progress track and have the .video_progress_bar jump to that position, we are going to need to know where, inside an element, an event happened.
Because the volume control is going to work the same way tomorrow (just vertically instead of horizontally), it makes sense to make a single helper function that will work for both.
- We take an event (e).
- We get its target and wrap it as a jQuery element.
- We then use jQuery’s offset method to find out where, on the page, that element was.
Next, we subtract that element from the event’s X and Y positions on the page to get the relative X and Y positions within the element that the event happened at.
Finally, we return it in a hashmap, matching the offset method’s format.
function findEventPositionWithinElement(e) {
var $_target=$(e.currentTarget);
var _offset=$_target.offset();
var _relativeX=e.pageX-_offset.left;
var _relativeY=e.pageY-_offset.top;
return {left: _relativeX, top: _relativeY};
}
The Initialization Function
We start with our usual initialization function.
In it, we have two main blocks:
- The first goes through all of the videos, pre-fetching their matching controls and progress_bars, ready to bind the timeupdate events as they fire and thus update the progress bar.
- The second goes through all of the progress bars, pre-fetching their matching videos, ready to bind the user’s clicks, jumping the video to that position.
function initVideoProgress() {
$('video').each(function(index, _video) {
var $_controls=findControlsForVideo(_video);
var $_progress_bars=$_controls.find('.video_progress_bar');
/* Bind The timeupdate Event For The Video */
});
$('.video_progress').each(function() {
var $_video=findVideoForElement(this);
/* Bind The click Events On The Progress Bar Track */
});
}
Bind The timeupdate Event For The Video
Tracking the timeupdate event, calculating the percentage played, then setting the .video_progress_bar with to the same percentage is now very quick, using the code from the Concepts section above:
$(_video).bind('timeupdate', function() {
var percentage=0;
if ( (typeof this.duration!="undefined") && (this.duration>0)) {
percentage=100*this.currentTime/this.duration;
}
$_progress_bars.css("width", percentage+"%");
});
Bind The click Events On The Progress Bar Track
We bind the click event for the progress bar, capturing the event (e).
Passing e in to findEventPositionWithinElement(e) from earlier, we can get how far along the track the user clicked.
To calculate the percentage, all we need to do is take this position, divide it by the total width of the track and then multiply by 100.
$(this).click(function(e) {
var _position=findEventPositionWithinElement(e);
var _percentage=100*_position.left/$(this).width();
/* Move The Video(s) To The New Position */
});
Now we know how far along we want to move all of the videos…
We move through them all, checking to make sure they have a non-zero duration (to avoid divide by zero errors again).
For all of those with non-zero durations, we calculate their new currentTime by multiplying their duration by the new percentage and then dividing by 100.
We don’t need to update the progress bar as this update will fire a timeupdate event and trigger the earlier code.
$_video.each(function(index, _video) {
if (typeof _video.duration != "undefined") {
if (_video.duration>0) {
_video.currentTime=_video.duration*_percentage/100;
}
}
});
Finishing Off
Once again, we add the initialization function to the $(document).ready() and we’re done.
Note: We haven’t included sample video files in order to keep download sizes small. You can test with your own .ogg and .mp4 files or download ones you wish to use from the web.
Tomorrow:
TBD
Resources
The W3C spec for HTML5 video is available at:
http://www.w3.org/TR/html5/video.html
However, we would recommend the whatwg version as its linked table of contents makes it slightly more usable (even if the text is just as dense):
http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html
Nicholas Davison Director of Web Development
Read more from the Engineering category. If you would like to leave a comment, click here: Comment or stay up to date with this post via RSS, or you can Trackback from your site.
Comments
appliances repair Dec 11, 2011 at 2:41am
Nice! Just wanted to respond. I thoroughly loved your post. Keep up the great work on www.digitaria.com .
Mark L Jun 06, 2011 at 6:51pm
Just wondering how you might play a video segment, say from 10.00 to 25.50s. I have already tried using ontimeupdate, checking the time and pausing if the time has passed, but I have noticed that sometimes the by the time the script executes, the video may have played a few more frames. Any ideas on how to work around this?
Post new comment