7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
  1. Code
  2. JavaScript

Build a Custom HTML5 Video Player

Scroll to top
Read Time: 22 mins
This post is part of a series called Strange and Unusual HTML.
Introducing the HTML5 “datalist” Element
Quick Tip: Consider Wrapping Your Code With a “figure” Element

Though the idea of a video-specific HTML element was suggested well over a decade ago, we're only just beginning to see this come to fruition! Forget all of that "HTML5 2012" mumbo-jumbo; the truth is that you can use the video element in your projects right now! You only need to be aware of a handful of requirements before mindlessly taking the plunge.

What We Must Know Up Front

  1. For good reason, the spec does not recommend offering full screen support. As you can imagine, now that video can be controlled with JavaScript alone, surely we can all imagine all of the various ways that this can be abused! With that said, the nightlies of some browsers do offer full-screen video, via a right-click.
  2. Unfortunately, if full screen support is a requirement for your needs at this time, you can't rely on it. Instead, you'd need to utilize or build a custom player that offers this feature.
  3. In order to force Internet Explorer to recognize the various new HTML5 elements, we have to use a simple bit of trickery, whereby we create the element with JavaScript, with document.createElement('video'). Strangely enough, this seems to trigger IE's awareness. Yes -- it's quite odd, but we've come to expect that from Internet Explorer, right? To expedite this process, Remy Sharp created HTML5 Shiv, which also, incidentally, remedies some print issues when working with HTML5 elements. Simply download the script, and reference it within the head section of your document(s).
  4. Browsers don't quite get along when it comes to the type of video they support. For example, Firefox prefers video coded in the ogg format, while Webkit browsers can render mp4 videos. In fact, this is an over simplification; however, this will do for the time being. However, suffice it to say that more video types are supported, including Google's recently open-sourced V8 codec.
  5. We can easily offer a Flash player as fallback support for older browsers; so never fear. Though, keep in mind that, at least right now, you'll need to convert your video into at least two formats.

Why Build a Custom Player?

  1. Full Screen Support: From personal experience, I can tell you that the full screen support issue is a deal breaker. For example, as the editor of Nettuts+, I can't offer coding screencasts that must be viewed at 600px! For this reason alone, building a custom player is essential.
  2. Branding: Each browser implements its own video player, which is slightly different from its counter-parts. As a result of building a custom player, we can ensure that the visuals are consistent from browser to browser...and we also get to brand/skin the player to our heart's content. As a proof of concept, this tutorial's player will use the Tuts+ logo.

Webkit's Video Player

Mozilla's Video Player

Our Tuts+ Branded Custom Video Player (in all browsers)

Step 1: The Video Element

As with any project, our first step is to create the necessary mark-up for our project.

First, find some sample videos to work with. The open-source "Big Bunny Buck" Pixar-like video is what I'll be using. I'm sure you've seen it in use around the web by now. Viewing this mark-up in the browser might leave you with a question mark over your head.

The Raw Video Element

Huh? That's it? It just looks like an image. What's the problem? Well, by default, this is all that we've requested. We have to then specify whether or not to display the controls, display a poster, etc. Let's do that now; revise your video element, like so:

Right off the bat, we notice that there are no quotations around the attributes. As it turns out, they aren't necessary. While years ago, it was considered a bad practice to exclude them, this is no longer the case. At this point, it only comes down to personal preference. So, if you feel more comfortable adding them in, then, by all means, do so.

  • controls : Adds the standard play/pause controls to the player
  • width: The width of the player
  • preload: Instructs the browser to begin preloading the video when the page loads. Think twice before you blindly add this attribute. You use up the viewer's bandwidth when you enable this feature. Secondly, at the time of this writing, no browser has implementing this functionality yet...but they will.
  • poster : Not fully implemented across all browsers, this allows you to set an image as the beginning of the video -- before it has begun playing.

The Video Element with Attributes

Step 2: Custom Controls

We obviously don't what to use the browser's default controls. As such, we must implement our own controls-mark-up. Before we progress, you might have just asked yourself, "Then what's the point of adding the controls attribute at all? The answer is because we must consider and compensate for the fact that JavaScript might be disabled on the viewer's computer. In those cases, had we not added the controls attribute, they'd only see what appears to be an image.

Always consider the possibility that JavaScript might be disabled.

The div with an id of "videoControls" is where we'll add the necessary buttons and logo. One nifty little trick is the use of "& #x25BA;" to create the play button. Don't worry too much about the "progress" div; we'll review this section in more detail later in this tutorial. The short answer is that this is the container for our progress bar. Lastly, we add a button that will toggle the full-screen functionality, as well as the Tuts+ logo.

Two Control Sets at Once?

Don't worry; though it appears that we've now created a second set of controls (and unstyled at that), we'll eventually remove the default controls with JavaScript. Nonetheless, the screenshot above should match your own preview, if you're following along.

Step 3: The JavaScript

With the mark-up complete, we can now move on to the fun part! Create a new folder, called "js," and add a new file: videoPlayer.js. Next, let's be good boys and girls and wrap our code within a self-invoking anonymous function, to prevent the creation of needless global variables.

You can strip out the arguments if you wish, it's a small improvement with two advantages:

  1. By passing in this (the global Window object) and document, we prevent the JavaScript engine from having to filter up and track those objects down. This can be a great convenience for larger projects, but it offers a very small performance benefit.
  2. When minifying your JavaScript, window and document can now be represented via something like a and b.

Next, let's continue to keep our code as clean as possible by creating a videoPlayer object that will contain all of our methods. We'll also create an init method that will be called immediately when the page loads.

As we'll certainly be manipulating elements on the page, let's go ahead and reference the location of them within variables. Just before we declare the "videoPlayer" variable, prepend:

If you're working along (and I hope you are), take some time to learn exactly what these variables are referencing. Refer back to your mark-up, if necessary. We grab the video element, the video controls div, the play button, the full screen toggle button, and the three progress divs. We "cache" the location of these elements because there's no reason to needlessly traverse the DOM every single time we need to access one of these elements!

The Init Method

Let's begin writing some actual code. Within the init method, paste in the following snippet:

A neat way that we style elements based upon whether JavaScript is enabled is by apply a class of "js" to the documentElement, or the html element.

This allows us to then write CSS like:

Remember the issue with the two sets of video controls? Let's remedy that now. This is an easy one; we'll simply use the removeAttribute method, and get rid of controls.

Let's now add our first HTML5 event: loadeddata. This event fires when the browser has finishe loading the meta data for the video. This is an excellent spot to perform any initial procedures, like displaying custom controls.

If you know a bit of JavaScript, you might be worried that we're not compensating for Internet Explorer. For those unaware, IE8 and below do not recognize addEventListener. Instead, in the fashion it's grown accustomed to, IE uses its own attachEvent. However, that's all changing in version nine of the browser. As IE8 and below don't understand the video element, we can ignore the attachEvent event entirely.

When the loadeddata event fires, we call the yet to be created "initializeControls" method. Within an object method, we can't refer to "initializeControls" by itself. We must first reference the object itself: hence, this.initializeControls.

initializeControls : Our Custom Controls

Let's go ahead and create that method now.

As mentioned previously, if writing your own custom player, use this method to perform any additional initial procedures. For now, this method will calls another method, showHideControls. This new method will display or hide the controls when the user hovers over the video.

The showHideControls Method

This method attaches four event handlers to the video player and controls elements, represented by the video and videoControls variables, respectively. This code is simple: when you mouseover the video, change the opacity of the video controls to 1. Inversely, when moused off, hide the controls from the view. This code also assumes that, by default, within our stylesheet, the video control's opacity is set to none.

Remember: when possible, always delegate styling to our CSS file, rather than adding it directly with your JavaScript. In addition to performance benefits and the reduction of reflows/repaints, this practice adheres correctly to the separation of presentation from logic. However, from time to time, you'll have no option but to add the styling within your JavaScript file, and, in those cases, that's okay.

Button Pressing

We've now successfully hidden the default controls, but we haven't yet attached any logic to these various custom buttons. That's what we must do next.

Return to the init method, and append a call to a new method.

You're, of course, free to name these methods how you wish, but at the very least try to name them in such a way that it's easy to understand exactly what the purpose of the function is.


Within this method, we again attach a handful of new events that are available to the video element: play, pause, and ended.

  • play: Fired when the video begins playing.
  • pause: Fired when the video is paused.
  • ended: Fired when the video has concluded. This event listener can be used to reset the video back to the beginning.

Keep in mind that, for instance, the play event doesn't fire when you click on the play button that we created. No, instead, it fires when the video itself begins playing. That's an important distinction to remember.

In order to trigger the events listed above, we add another event listener to the play button, and listen for when the user clicks on it.

The playPause Method

Let's talk through the code above in general-speak. When this method was called -- in our case, when the play button is first clicked -- was it paused or at the end of the video? If the latter, we need to reset the time of the video back to 0, so that it can play from the beginning again. Next, we have to play the video: video.play().

Now if the video was not paused or at the end -- meaning that the video was in the process of playing when the play button was clicked, we should do the opposite, and pause the video instead: video.pause().

Switching out the Play/Pause Buttons

The playPause method, detailed above, handles the process of either playing or pausing the video. But, we certainly need to provide the user with some feedback as to what the button push does, right? Definitely; to do so, we'll switch between a "play" and "pause" HTML character code each time the button is clicked. So, essentially, this one button handles both the playing and pausing.

If you'll refer to the event listeners from a moment ago, we've already added this functionality.

Pretty simple, right? If the play button is pressed, the click event of the play button fires, which calls video.play(). This method then plays the video and triggers the play event. The play event then changes the value of the button to a pause symbol, and updates the title attribute as well. Finally, when this process occurs again, we do the opposite.

Full Screen Support

I mentioned it in the opening paragraph of this tutorial: full screen support is a must for me. Let's add that functionality now. Return to your init() method, and, again, add a snippet to the bottom. We need to listen for when that full screen toggle button that we created....

...is clicked. When it is, we should either switch the video to full screen, or the opposite.

Don't forget: fullScreenToggleButton refers to the "fullScreen" button.

Wait a minute: what is that? Shouldn't it be this? The answer is no. Within the context of the click event, this now refers to the "fullScreen" button, not the videoPlayer object.

To remedy this, we'll "cache" the original value of this within a variable, called that. This can be added to the top of our init() method.

Ta da! Now, we can again refer to the videoPlayer object. As such, that.fullScreenOff means, "access the method called "fullScreenOff" that is a child of the videoPlayer object.

There's one problem. How do we determine the current state of the video? Is it full-size or regular-size? An easy way to handle this situation is to create a variable, called "isVideoFullScreen." You can add this variable to the very top of your page, along with the others. By default, of course, this value should be set to false.

This boolean now allows us to correctly call the appropriate function when the "fullScreen" button is clicked.

Using the ternary operator, we check: "is the video currently in full screen mode? If it is, we call fullScreenOff(). Otherwise, we call fullScreenOn().

Within these two functions, we first change the value of isVideoFullScreen, accordingly. Next, we adjust the size of the video, itself. cssText allows us to pass a string of styling to an element. In this case, we can't accomplish this task from directly within our stylesheet. This is because the desired values are dynamic, and will be dependent upon the visitor's browser window size.

Use window.innerWidth and window.innerHeight to retrieve the width and height of the browser window.

It's also a good practice to apply full-screen-specific classnames to our elements. That way, should you need to add any appropriate styling, you now have a class name to hook onto.

The Escape Key

In addition to the full-screen toggle button, it's also quite common to leave full screen mode when the user presses the escape key on their keyboard. It's a nice convenience, and is one that we should implement into our player. Luckily, it's easy!

To listen for key clicks, we use the keydown event. While I could pass an anonymous function as the second parameter, this bit of code could be used in multiple places. And when this is true, the code should be relocated to its own function, for reuse. We'll call it, checkKeyCode.


This function is unfortunately more confusing than it should be, due to Internet Explorer issues. I'm admittedly not certain how the upcoming IE9 handles the event object, so, for the time being, we'll still compensate for the discrepencies between the way IE and the other modern browsers handle the event object.

Boring stuff aside, this code checks to see if the key that was clicked has a code of "27." If it does, then the "Escape" key was pressed, in which case we can exit fullscreen, via videoPlayer.fullScreenOff().

Tracking the Progress

Last, but not least, we need to track the progress of the video. So, as the video progresses, we need to provide a progress "scrubber" that will help to track the video. It additionally allows the user to quickly scan to their desired portion of the video.

When Should We Begin Tracking?

That's easy: when the video is playing! So, with that in mind, let's modify our play event code, and call a function that will track the video's progress.


Don't let this code scare you. It's merely a recursive function that allows us to call a different method, updatePlayProgress(), every fifty milliseconds. Why not use setInterval instead? This is because setInterval can be nasty at times. Without going into too much detail that exceeds the scope of this tutorial, setInterval will run (in this case) every fifty milliseconds, regardless of how long the code within the function takes.

setInterval is like a driver in rush hour traffic who refuses to step on the breaks.

So, when possible, combining setTimeout with a recursive function is the smarter solution. Note that we assign setTimeout to a variable, playProgressInterval, because we need to later clear this timeout. Therefore, we need some sort of id to hook into.


Now that we've specified that, every fifty milliseconds, the updatePlayProgress method should be called, let's create it now.

Though the code above might appear to be super confusing at first, it's not too bad. We grab the progress bar and update its width (every 50ms). We can determine the correct width by dividing the current location of the video, by the length of the video -- which we can calculate with video.duration. This value should then be multiplied by the total width of our progress holder, and voila!

But, what happens the video is paused by the user? We surely don't want to continue this setTimeout. Of course not. As such, we should create another method, called stopPlayProgress, and refer to it when the pause event is triggered.

Video Scrubbing

The final step that we'll cover in this tutorial details how to "scrub" the video -- meaning, when the user clicks on the progress bar, and scrolls up and down the video. I'm sure you've performed this task yourself on multiple occasions. No, no, no; these things are done automatically. We have to code that functionality.

Quick...hold your breath.

...and breath out. Let's take this huge clump of code line by line.

  • Listen for when the user clicks on the progress bar.
  • When they do, call videoPlayer.stopTrackingPlayProgress to clear the timeout that we created above.
  • Next, call the playPause method that will handle, in this case, pausing the video.
  • While the mouse is still being pressed, listen for when it moves. When it does, call the setPlayProgress method.
  • This complicated method is difficult to explain with words; you'll need to study it for a bit. Essentially, the code determines where you clicked, and then divides that by the entire width of the progress bar. Next, it updates the current time of the video, and the width of the dynamic progress bar.
  • After this function returns, we next listen for when the user mouses up from their original click. When they do, we clear any existing mouse events, and proceed with playing the video, as well as tracking its progress again.


There's no two ways about it; this stuff is confusing, until you suddenly hear the "click," so to speak. This usually is proceeded by an extended, "Oooohhhhhh." If you haven't gotten there yet, of if your eyes couldn't help but glaze over this long tutorial, that's okay. Read it again, and, most importantly, work along. That's the only way we grow. Secondly, large lumps of code our admittedly difficult to take in all at once. To compensate, read through this tutorial again in bits and pieces, as you refer to the final source code through out. This will help your mind to grasp exactly where each block of code belongs.

On the visual side, the wonderful thing about a custom player like this is that, once the logic has been written, it's 100% skinnable. Just track down your desired ids and classes, and you're all set. The demo provided with this tutorial offers one option, however, if you don't like the look if it, no problem; design your own!

What's Next?

We have a neat and functional player right now, however, it can be extended further. In the next lesson (free of charge to you), we'll learn how to add a volume control, as well as a timer to our video controls. So stay tuned.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.