Learn How to Build A Custom HTML5 Video Player

Reading Time: 12 minutes

Videos are great tools for catching attention and even better for storytelling. And people, business and brands are using them more and more. However, native HTML5 video player offers only limited options for customization. When client wants a video player that matches her brand these options are not enough. Today, we will take a look at how to create our own custom video player. Let’s take a complete control so we can provide our clients with the solution they want!

Codepen demo.

Briefing

In this tutorial our goal will be creating a HML5 video player with custom controls and progress bar. This video player will have seven control elements, or buttons. These buttons will add following functionalities: play or pause the video, move the video backward, move the video forward, stop the video, restart the video (move it to the beginning), mute or unmute the video and toggle fullscreen. We will implement these functionalities with JavaScript functions and event listeners.

One of the buttons will be initially hidden, the button for restarting the video. We will show this button when the video reaches the end. Other buttons, such as play and mute will change its icon according to the current state of the video. In order to make these controls unobtrusive, we will hide them in the default state of the video player. And, we will show them only when user hovers over the video player or when the video player will get focus. This will help us with mobile devices.

In order to make our work easier, we will not create the player icons from scratch. Instead, we will use icon font called Font Awesome. And, this will also be the only external resource we will need for this tutorial. Well, we will also need some video file, preferably in mp4 format and poster so we can show something while the video is loading or until the user hits the play button. Now, we can move to the HTML.

Note: Since this is a tutorial on how to build a video player, let me give you some ideas for free video resources. In case of videos, I have three favorite go-to websites. These websites are Cover, Videezy and Pexels Videos. And, here is a short list of another 20 sources on published on DesignHooks.

HTML

The structure of our video player will be very simple. We will create one div element with class “video-wrapper”. Inside this div will be video element with source tag nested inside it. Video element will have class “video-element”. It will also have two attributes. First attribute will be poster and its value will be the location to the image we will use as a poster. Second attribute will be preload. This attribute will be set to “metadata”.

When we set the preload attribute to “metadata”, when the page loads, the browser will load only metadata. It will not load the video itself. This could cause lower performance in case of a bigger video file. The source tag inside the video element will have two attributes as well. First attribute will be src and its value be the location of our video file. Second attribute will be type. The value of this attribute will be “video/mp4”.

We could also use other formats such as Ogg and WebM. However, if we consider browser support, MP4 video format is probably the best option since it is supported by all browsers. The last thing we should do is include a short message for people whose browsers don’t support HTML5 video tag. We will put this message as a pure text inside the video element, right under the source tag. This message is basically only for IE 8 and Opera Mini.

Next, after the video element will be div element with class “video-controls”. And, yes, this div will contain the buttons users will be able to use to control the video. As we discussed in briefing, we will create seven buttons. We will use button elements. Inside every button element will be one span element. This span will serve us as a placeholder for button icons provided by Font Awesome.

The last element, right after our container div with buttons, will be another div element with class “progress-bar”. And, inside it will be one more div element, now with class “progress-bar-fill”. We will use this div to show current progress or timeline of the video. We could use span for the fill. However, we would then have to set its display property to “block” via CSS. So, we can now use a block element right away and save a line of CSS.

Note: some people like to use “i” tag for icons. I think that “span” is a better choice. The purpose of “i” tag is defining a text that conveys an alternate voice or mood. Another use case for this tag can be a technical term, phrase from another language or a thought. I am not sure if icon label meets any of these conditions. If so, please correct me. For this reason, I like to use “span” tag since it is completely neutral and conveys no specific meaning.

Code:

<div class="video-wrapper">
  <video class="video-element" poster="https://s3.amazonaws.com/coverr-public/poster/Porto_View.jpg" preload="metadata">
    <source src="https://coverr.co/s3/mp4/Porto_View.mp4" type="video/mp4">

    Your browser doesn't support the video tag.
  </video>

  <div class="video-controls">
    <button class="btn btn-backward"><span class="fa fa-backward"></span></button>

    <button class="btn btn-play"><span class="fa fa-play"></span></button>

    <button class="btn btn-forward"><span class="fa fa-forward"></span></button>

    <button class="btn btn-stop"><span class="fa fa-stop"></span></button>

    <button class="btn btn-reset" hidden><span class="fa fa-undo"></span></button>

    <button class="btn btn-mute"><span class="fa fa-volume-off"></span></button>

    <button class="btn btn-expand"><span class="fa fa-expand"></span></button>
  </div>

  <div class="progress-bar">
    <div class="progress-bar-fill"></div>
  </div>
</div>

CSS (SASS)

The second phase of building our video player is about creating some custom styles. This will be relatively short and fast since we are using only a small amount of elements. Let’s begin with the “video-wrapper” div. We will need to do three things. First, we will set its position to “relative” so we can position the controls absolutely later. Next, we will set its overflow to “hidden” so controls are visible only they should be. Finally, we will add some max-width.

Next on the line is the video element. We will set its display property to “block”. Next, we will set its width to “100%” so it will fill the whole “video-wrapper” div. And we will set its max-width to “100%” as well so it will not overflow the “video-wrapper” div. Finally, let’s also set its height to “auto”, just to make sure the video will keep its aspect ratio.

Code:

.video-wrapper {
  position: relative;
  overflow: hidden;
  max-width: 480px;
}

video {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
}

Styling the controls wrapper

Next on the list is the “video-controls” div and control elements, or buttons, inside it. For the div, we will set its position to “absolute”, and bottom to “-45px”. This is the final height of the whole div. In other words, this will put the div with control elements just below the visible area of the “video-wrapper”. Next, we will use combo of left and transform properties to center the “video-controls” div horizontally.

We can also add some padding to add a bit of space between the edge of this div and the buttons inside it. After this, we should also add at least some light background-color to make this wrapper more visible. I think that “rgba(255, 255, 255, .35)” will be enough. Finally, let’s add transition and apply it to its bottom property so the wrapper smoothly slide up into the view when user hovers over the wrapper.

This sliding effect will be quite easy to achieve. We will use hover and focus states of “video-wrapper” div to change the bottom property of “video-controls” to “12px” and its z-index to “2” (just to make sure “video-controls” div is always on top). We could set the bottom property to “0” since this would be enough to show it. However, I decided to add a few more pixels to offset the “video-controls” div from the bottom of the video.

Code:

// Video controls
.video-controls {
  position: absolute;
  bottom: -45px; // height of controls container
  left: 50%;
  padding: 7px;
  background: rgba(255, 255, 255, .35);
  transform: translateX(-50%);
  transition: bottom .25s ease-in;
}

.video-wrapper:focus > video + .video-controls,
.video-wrapper:hover > video + .video-controls {
  bottom: 12px;
  z-index: 2;
}

Styling the buttons and progress bar

And, we are in the third and last phase of building our video player. Now, we will add some styles to buttons and progress bar. Let’s tackle buttons first. We are going to create nice and subtle square buttons with transparent background, without any border. We will achieve the square shape by using a combo of padding, width and text-align properties. We can get rid of the border by setting it to “0”.

Next, we can increase the size icons by setting the font-size property to “18px”. After that, let’s set the color the buttons to “#fff” (or white) and the background-color to “transparent”. Let’s also make sure that when user hovers over any button, the mouse cursor will change to pointer. Next, let’s remove the outline on focus. Finally, when user hovers over the button, let’s change its opacity to “.25”. And, we can make this smoother with transition.

The last piece is the progress bar. For this element, we will need four lines of CSS. First, we will make sure it has no width. Second, we will give it some default height. Third, let’s give it some nice and visible background-color, like “#e74c3c” (red). Fourth, let’s again use transition, but now let’s apply it to width property. When the video is playing, we will use JavaScript to change the width on the fly. And, this is all CSS we need for our video player!

Code:

// Video controls
.btn {
  padding: 5px 0;
  width: 31px;
  font-size: 18px;
  text-align: center;
  color: #fff;
  background-color: transparent;
  border: 0;
  cursor: pointer;
  transition: opacity .175s ease-in;

  &:focus {
    outline: 0;
  }

  &:hover {
    opacity: .25;
  }
}

// Progress bar
.progress-bar-fill {
  width: 0;
  height: 4px;
  background-color: #e74c3c;
  transition: width .35s linear;
}

Putting It All Together

Before we move on to JavaScript, let’s take all the styles we previously created and put them together. So, this is what we have.

Code:

.video-wrapper {
  position: relative;
  overflow: hidden;
  max-width: 480px;
}

video {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
}

// Video controls
.video-controls {
  position: absolute;
  bottom: -45px; // height of controls container
  left: 50%;
  padding: 7px;
  background: rgba(255, 255, 255, .35);
  transform: translateX(-50%);
  transition: bottom .25s ease-in;
}

.video-wrapper:focus > video + .video-controls,
.video-wrapper:hover > video + .video-controls {
  bottom: 12px;
  z-index: 2;
}

// Video controls
.btn {
  padding: 5px 0;
  width: 31px;
  font-size: 18px;
  text-align: center;
  color: #fff;
  background-color: transparent;
  border: 0;
  cursor: pointer;
  transition: opacity .175s ease-in;

  &:focus {
    outline: 0;
  }

  &:hover {
    opacity: .25;
  }
}

// Progress bar
.progress-bar-fill {
  width: 0;
  height: 4px;
  background-color: #e74c3c;
  transition: width .35s linear;
}

JavaScript

This is the last part of this video player tutorial. And, it also the most important step in order to make our video player work. Let’s get right into it. First, we will store all elements we will use, such as buttons, icons, video element, progress bar and fill, inside consts. When we are done with this step, we can create our first function. This function will allow users play the video in a fullscreen mode. So, we can call this function “expandVideo”.

This function uses a requestFullscreen() method that is still more or less experimental and in order to make it work, we will need to use different variations for different browsers. For this reason, we will use an if statement. This if statement will test if the browsers supports either requestFullscreen(), mozRequestFullScreen() or webkitRequestFullscreen(). If so, it will use that method to toggle fullscreen.

Code:

const btnBackward = document.querySelector('.btn-backward');
const btnExpand = document.querySelector('.btn-expand');
const btnMute = document.querySelector('.btn-mute');
const btnMuteIcon = btnMute.querySelector('.fa');
const btnPlay = document.querySelector('.btn-play');
const btnPlayIcon = btnPlay.querySelector('.fa');
const btnForward = document.querySelector('.btn-forward');
const btnReset = document.querySelector('.btn-reset');
const btnStop = document.querySelector('.btn-stop');
const progressBar = document.querySelector('.progress-bar');
const progressBarFill = document.querySelector('.progress-bar-fill');
const videoElement = document.querySelector('.video-element');

// Toggle full-screen mode
const expandVideo = () => {
  if (videoElement.requestFullscreen) {
    videoElement.requestFullscreen();
  } else if (videoElement.mozRequestFullScreen) {
    // Version for Firefox
    videoElement.mozRequestFullScreen();
  } else if (videoElement.webkitRequestFullscreen) {
    // Version for Chrome and Safari
    videoElement.webkitRequestFullscreen();
  }
}

Move, mute and play, or pause

Now, let’s add functionality to our video player to allow users to move five seconds backward or forward, mute the sound and, most importantly, play or pause the video. We can call these functions “moveBackward”, “moveForward”, “muteVideo” and “playPauseVideo”. The “moveBackward”, “moveForward” *functions will be simple. We will get the current time of the video (videoElement) and either increase or decrease it for 5 seconds (or any number you want).

Next, let’s can create the fourth function that will allow the user to mute and unmute the sound of the video, the “muteVideo” function. We will again use if statement that will ask the videoElement whether it is currently muted or not. If the video is muted, we will set muted property of the video to “false” and change the icon of the “mute” button. Otherwise, we will set muted to “true”, and again change the icon of the button.

It is time to implement the fifth and most important functionality of our video player, the “playPauseVideo” function. This function will basically copy the structure of the “muteVideo” function. Now, we will not test if the video is muted, or its mute property. Instead, we will test its paused property. If it is paused, we will use play() method to play the video and change button icon from “Play” to “Pause”. Otherwise, we will use pause() and change the icon from “Pause” to “Play”.

Code:

// Move the video backward for 5 seconds
const moveBackward = () => {
  videoElement.currentTime -= 5;
}

// Move the video forward for 5 seconds
const moveForward = () => {
  videoElement.currentTime += 5;
}

// Mute the video
const muteVideo = () => {
  if (videoElement.muted) {
    videoElement.muted = false;

    btnMuteIcon.classList.remove('fa-volume-up');
    btnMuteIcon.classList.add('fa-volume-off');
  } else {
    videoElement.muted = true;

    btnMuteIcon.classList.remove('fa-volume-off');
    btnMuteIcon.classList.add('fa-volume-up');
  }
}

// Play / Pause the video
const playPauseVideo = () => {
  if (videoElement.paused) {
    videoElement.play();

    btnPlayIcon.classList.remove('fa-play');
    btnPlayIcon.classList.add('fa-pause');
  } else {
    videoElement.pause();

    btnPlayIcon.classList.remove('fa-pause');
    btnPlayIcon.classList.add('fa-play');
  }
}

Reset, stop, update and… listen

This is the last part in this tutorial and the last three functions, “restartVideo”, “stopVideo” and “updateProgress”. “restartVideo” function will set the currentTime property of the video to “0”. Then, it will show the “play” button (we will hide it with event listener when the video reaches its end) and hide the “Restart” button. “stopVideo” function will use pause() method to pause the video, set the currentTime property of the video to “0” and change the icon “Play” button from “Pause” to “Play”.

The very last function our video player needs is “updateProgress”. This will dynamically update the width CSS property of the progress bar according to the current time of the video. First, we will divide the whole duration of the video by 100 and multiplying it by currentTime. This will give us the correct value for progress bar width in percentages so we can then apply it.

The final step is attaching some event listeners to video player controls. We will use addEventListener() method to attach an event listener to all buttons, btnBackward, btnExpand, btnMute, btnPlay, btnForward, btnReset, btnStop. All these event listeners will listen to is “click” and use one of the functions we previously created. Lastly, we will add two more event listeners for the videoElement.

One event listener be for “ended” event and one for “timeupdate”. Listener for “ended” event will hide the “Play” button and show the “Reset” button. Listener for “timeupdate” will apply our updateProgress() function. And, that’s it!

Code:

// Restart the video
const restartVideo = () => {
  videoElement.currentTime = 0;

  btnPlay.removeAttribute('hidden');
  btnReset.setAttribute('hidden', 'true');
}

// Stop the video
const stopVideo = () => {
  videoElement.pause();
  videoElement.currentTime = 0;
  btnPlayIcon.classList.remove('fa-pause');
  btnPlayIcon.classList.add('fa-play');
}

// Update progress bar as the video plays
const updateProgress = () => {
  // Calculate current progress
  let value = (100 / videoElement.duration) * videoElement.currentTime;

  // Update the slider value
  progressBarFill.style.width = value + '%';
}

// Event listeners
btnBackward.addEventListener('click', moveBackward, false);
btnExpand.addEventListener('click', expandVideo, false);
btnMute.addEventListener('click', muteVideo, false);
btnPlay.addEventListener('click', playPauseVideo, false);
btnForward.addEventListener('click', moveForward, false);
btnReset.addEventListener('click', restartVideo, false);
btnStop.addEventListener('click', stopVideo, false);
videoElement.addEventListener('ended', () => {
  btnPlay.setAttribute('hidden', 'true');
  btnReset.removeAttribute('hidden');
}, false);
videoElement.addEventListener('timeupdate', updateProgress, false);

Putting It All Together

Now, let’s take all the JavaScript we created for our video player and put them into one piece.

Code:

const btnBackward = document.querySelector('.btn-backward');
const btnExpand = document.querySelector('.btn-expand');
const btnMute = document.querySelector('.btn-mute');
const btnMuteIcon = btnMute.querySelector('.fa');
const btnPlay = document.querySelector('.btn-play');
const btnPlayIcon = btnPlay.querySelector('.fa');
const btnForward = document.querySelector('.btn-forward');
const btnReset = document.querySelector('.btn-reset');
const btnStop = document.querySelector('.btn-stop');
const progressBar = document.querySelector('.progress-bar');
const progressBarFill = document.querySelector('.progress-bar-fill');
const videoElement = document.querySelector('.video-element');

// Toggle full-screen mode
const expandVideo = () => {
  if (videoElement.requestFullscreen) {
    videoElement.requestFullscreen();
  } else if (videoElement.mozRequestFullScreen) {
    // Version for Firefox
    videoElement.mozRequestFullScreen();
  } else if (videoElement.webkitRequestFullscreen) {
    // Version for Chrome and Safari
    videoElement.webkitRequestFullscreen();
  }
}

// Move the video backward for 5 seconds
const moveBackward = () => {
  videoElement.currentTime -= 5;
}

// Move the video forward for 5 seconds
const moveForward = () => {
  videoElement.currentTime += 5;
}

// Mute the video
const muteVideo = () => {
  if (videoElement.muted) {
    videoElement.muted = false;

    btnMuteIcon.classList.remove('fa-volume-up');
    btnMuteIcon.classList.add('fa-volume-off');
  } else {
    videoElement.muted = true;

    btnMuteIcon.classList.remove('fa-volume-off');
    btnMuteIcon.classList.add('fa-volume-up');
  }
}

// Play / Pause the video
const playPauseVideo = () => {
  if (videoElement.paused) {
    videoElement.play();

    btnPlayIcon.classList.remove('fa-play');
    btnPlayIcon.classList.add('fa-pause');
  } else {
    videoElement.pause();

    btnPlayIcon.classList.remove('fa-pause');
    btnPlayIcon.classList.add('fa-play');
  }
}

// Restart the video
const restartVideo = () => {
  videoElement.currentTime = 0;

  btnPlay.removeAttribute('hidden');
  btnReset.setAttribute('hidden', 'true');
}

// Stop the video
const stopVideo = () => {
  videoElement.pause();
  videoElement.currentTime = 0;
  btnPlayIcon.classList.remove('fa-pause');
  btnPlayIcon.classList.add('fa-play');
}

// Update progress bar as the video plays
const updateProgress = () => {
  // Calculate current progress
  let value = (100 / videoElement.duration) * videoElement.currentTime;

  // Update the slider value
  progressBarFill.style.width = value + '%';
}

// Event listeners
btnBackward.addEventListener('click', moveBackward, false);
btnExpand.addEventListener('click', expandVideo, false);
btnMute.addEventListener('click', muteVideo, false);
btnPlay.addEventListener('click', playPauseVideo, false);
btnForward.addEventListener('click', moveForward, false);
btnReset.addEventListener('click', restartVideo, false);
btnStop.addEventListener('click', stopVideo, false);
videoElement.addEventListener('ended', () => {
  btnPlay.setAttribute('hidden', 'true');
  btnReset.removeAttribute('hidden');
}, false);
videoElement.addEventListener('timeupdate', updateProgress, false);

Closing thoughts on building a custom video player

Congratulations! You’ve built your own HTML5 video player with custom controls and progress bar. I hope that this tutorial was easy to follow and that you enjoyed it. I also hope that you have a chance to either learned something new or at train your skills. From now, whenever your client, employer or anyone else will ask you to build a custom video player that matches his website and brand, you will know exactly how to do it. Good job!

Thank you very much for your time. And, until next time, have a great day!

Did you like this article? Please subscribe.

Are you on social media? Let's connect! You can find me on Twitter and Dribbble.

Leave a Reply