Creating a Custom HTML5 Audio Element UI

HTML5 has made many things simpler in web development, one of which is using embedded audio. Today, we’re going to work through the process of completely customising the HTML5 audio player element, which can adapt to older browsers with fallbacks. It’s a simple method that’s easy to build upon and tweak.

The open source project MediaElement.js is wildly popular, and has even been built into the core of WordPress since version 3.6. It allows you to embed any HTML5 audio or video using a native player interface which can dynamically adapt into a Flash/Silverlight player when needed. I was really impressed to see all the features and it seems to be one of the more advanced solutions for handling legacy browsers. We’ll be using that as a starting point!

2 Million+ Digital Assets, With Unlimited Downloads

Get unlimited downloads of 2 million+ design resources, themes, templates, photos, graphics and more. Envato Elements starts at $16 per month, and is the best creative subscription we've ever seen.

Explore Design Resources

Live DemoDownload Source Code

Getting Started

Right away, I noticed that MediaElement was built on top of jQuery. The latest library as of writing this code is 1.10.2 and support for various UI elements could change in future updates. Other than this script we also need a copy of mediaelement-and-player.min.js which you can grab from the official website or from the GitHub repository. The other two files you should copy into the same directory are flashmediaelement.swf and silverlightmediaelement.xap.

Now the script works by replacing your average tag with some internal HTML built using class names. Overwriting these classes should provide lots of room for detailed customization. I am using a fairly lengthy track called Evidence Song which is released under CC attribution + share alike.

  <div class="audio-player">
    <h2>The Good Lawdz - Evidence Song</h2>
    <audio id="audio-player" src="media/evidence-song.mp3" type="audio/mp3" controls="controls"></audio>
  </div><!-- @end .audio-player -->

The original design idea came from this tutorial on DesignModo which also uses album artwork and simplifies the volume/time tracks. My goal is to greatly adjust this into a simplified version where the focus is on clean usability. I’ve updated many graphics using stuff from PixelsDaily like these metallic sliders. Also the play/pause and mute/unmute buttons come from this audio player PSD.

Having some graphics or freebie PSDs organized ahead of time will save you loads of trouble when trying to build the player in CSS. But before we jump into the CSS code, I want to explain how we initialize the script.

Implementing MediaElement.js

The initialization method is called mediaelementplayer() which can take a number of different options. Check out the webpage documentation to learn more about how this works.

$(function(){
  $('#audio-player').mediaelementplayer({
    alwaysShowControls: true,
    features: ['playpause','progress','volume'],
    audioVolume: 'horizontal',
    audioWidth: 450,
    audioHeight: 70,
    iPadUseNativeControls: true,
    iPhoneUseNativeControls: true,
    AndroidUseNativeControls: true
  });
});

The features list is created much like an array of values. playpause is a single play and pause button, while progress and volume represent the scrubber tracks for each property. I can also define the audio element’s width/height as it should be rendered using jQuery.

The last three parameters will force a native display on mobile platforms using iOS or Android. Sometimes you can get a simple audio UI to work on mobile, but in this case it’s much easier to support the native player. The biggest problem is handling a mobile-responsive layout that also fits a usable audio interface.

Audio Player Styles

Now we can jump into CSS and see how the design is created. The basic code setup relies on relative or absolute positioning for each major item. We can set fixed width/height values to ensure everything looks the same in every browser. .audio-player will be the container class that we can always use as a base.

.audio-player, .audio-player div, .audio-player h2, .audio-player a, .audio-player span, .audio-player button {
  margin: 0;
  padding: 0;
  border: none;
  outline: none;
}

div.audio-player {
  position: relative;
  width: 450px;
  height: 70px;
  margin: 0 auto;
  background: #4c4e5a;
  background: -webkit-linear-gradient(top, #4c4e5a 0%, #2c2d33 100%);
  background: -moz-linear-gradient(top, #4c4e5a 0%, #2c2d33 100%);
  background: -o-linear-gradient(top, #4c4e5a 0%, #2c2d33 100%);
  background: -ms-linear-gradient(top, #4c4e5a 0%, #2c2d33 100%);
  background: linear-gradient(top, #4c4e5a 0%, #2c2d33 100%);
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}
.audio-player h2 {
  position: absolute;
  top: 7px;
  left: 10px;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: bold;
  font-size: 16px;
  color: #ececec;
  text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}

The large audio player container might be designed in any fashion using repeating textures, background gradients, even CSS3 box shadows. I also created a sample h2 element which holds the band name and song title. Using jQuery we could even update this text value based on some pre-generated playlist of music.

/* play/pause control */
.mejs-controls .mejs-button button {
  cursor: pointer;
  display: block;
  position: absolute;
  text-indent: -9999px;
}

.mejs-controls .mejs-play button, .mejs-controls .mejs-pause button {
  width: 34px;
  height: 34px;
  top: 32px;
  left: 7px;
  background: transparent url('playpause.png') 0 0 no-repeat;
}
.mejs-controls .mejs-pause button { background-position: 0 -35px; }
 
 
/* mute/unmute control */
.mejs-controls .mejs-mute button, .mejs-controls .mejs-unmute button {
  width: 18px;
  height: 19px;
  top: 9px;
  right: 142px;
  background: transparent url('audio.png') 0 0;
}
.mejs-controls .mejs-unmute button { background-position: 0 -19px; }

You’ll notice pretty quickly that each input has a defining class which is very simple to manipulate. The play/pause button is created using a single PNG file and the background-image updates based on which button should be displayed. These can be targeted using .mejs-play button and .mejs-pause button, respectively.

The mute/unmute audio icon is also very similar which uses another PNG sprite. The largest time sink will come when you need to study each of these individual classes and learn how to write specific CSS selectors for them. MediaElement is great about updating the interface to include plenty of related classes for almost every user interaction.

Scrubber Bars

There are two unique scrubbers relating to the audio volume and the time seek on the track. I’ve kept to gradients or flat background colors but you might instead try using a repeating background image to see how it looks.

/* volume scrubber bar */
.mejs-controls div.mejs-horizontal-volume-slider {
  position: absolute;
  top: 13px;
  right: 15px;
  cursor: pointer;
}

.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-total {
  width: 120px;
  height: 11px;
  background: #212227;
  -webkit-box-shadow: inset 0px 1px 0px rgba(0,0,0,0.3), 0px 1px 0px rgba(255,255,255,0.25);
  -moz-box-shadow: inset 0px 1px 0px rgba(0,0,0,0.3), 0px 1px 0px rgba(255,255,255,0.25);
  box-shadow: inset 0px 1px 0px rgba(0,0,0,0.3), 0px 1px 0px rgba(255,255,255,0.25);
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
}

.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-current {
  position: absolute;
  width: 0;
  height: 9px;
  top: 1px;
  left: 1px;
  background: #90d26a;
  background: -webkit-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -moz-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -o-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -ms-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: linear-gradient(top, #90d26a 0%, #83bb63 100%);
  -webkit-border-radius: 8px;
  -moz-border-radius: 8px;
  border-radius: 8px;
}

For each scrubber bar, you will find there are different portions which need to be styled separately. Each bar will have a full-view class which represents the value at 100%. For the volume this is labeled .mejs-horizontal-volume-total and it is meant to blend nicely into the layout. Similarly .mejs-horizontal-volume-current represents the bar which displays the current volume level. So if it’s set to 50% only half of the total bar will be filled using these CSS properties. I like how the rounded border-radius looks on each scrubber track and it’s so much easier with the help of CSS3 properties.

/* time scrubber bar */
.mejs-controls div.mejs-time-rail { width: 380px; }
 
.mejs-controls .mejs-time-rail span {
  position: absolute;
  display: block;
  width: 380px;
  height: 12px;
  top: 40px;
  left: 55px;
  cursor: pointer;
  -webkit-border-radius: 0px 0px 2px 2px;
  -moz-border-radius: 0px 0px 2px 2px;
  border-radius: 0px 0px 2px 2px;
}
 
.mejs-controls .mejs-time-rail .mejs-time-total { 
  background: #565860; 
  width: 380px !important; /* fixes display bug using jQuery 1.8+ */
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}
.mejs-controls .mejs-time-rail .mejs-time-loaded {
  top: 0;
  left: 0;
  width: 0;
  background: #7b7d82;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}
.mejs-controls .mejs-time-rail .mejs-time-current {
  top: 0;
  left: 0;
  width: 0;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
  background: #90d26a;
  background: -webkit-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -moz-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -o-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: -ms-linear-gradient(top, #90d26a 0%, #83bb63 100%);
  background: linear-gradient(top, #90d26a 0%, #83bb63 100%);
}

The loading rail has an extra feature with the class .mejs-time-loaded. This represents how much of the audio file has been pre-loaded via streaming downloads. This effect is more commonly seen in video players like with YouTube. But audio tracks can still have the same effect – these files are just smaller and don’t take as long to finish downloading.

I should note that using any version of jQuery 1.8+ doesn’t always render the total scrubber bar width. For some reason it never increases unless we include more specific parent classes or use the !important keyword. Similar bugs could appear in future releases so be sure to double-check everything.

Extra UI Features

The last two CSS sections focus on the metal sliders and the time-based tooltip. I found this effect very inspiring on the DesignModo player and I wanted to keep it as a show of what is possible within the user interface.

/* metallic sliders */
.mejs-controls .mejs-time-rail .mejs-time-handle {
  position: absolute;
  display: block;
  width: 20px;
  height: 22px;
  top: -6px;
  background: url('handle-lg.png') no-repeat;
}
.mejs-controls .mejs-horizontal-volume-slider .mejs-horizontal-volume-handle {
  position: absolute;
  display: block;
  width: 12px;
  height: 14px;
  top: -1px;
  background: url('handle-sm.png') no-repeat;
}

Not too much worth explaining here, other than the perfectly-fitted background images with absolute positioning. It’s a good idea to keep everything within alignment unless your goal is to build a responsive fluid-width audio player. Things definitely get a bit more tricky but it should be obvious that anything is possible with enough work.

/* time progress tooltip */
.mejs-controls .mejs-time-rail .mejs-time-float {
  position: absolute;
  display: none;
  width: 33px;
  height: 23px;
  top: -26px;
  margin-left: -17px;
  z-index: 9999;
  background: url('time-box.png');
}
 
.mejs-controls .mejs-time-rail .mejs-time-float-current {
  width: 33px;
  display: block;
  left: 0;
  top: 4px;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 10px;
  font-weight: bold;
  color: #666;
  text-align: center;
  z-index: 9999;
}

Following the older example I kept this tooltip hover effect using a single background image. CSS3 can generate unique rounded tooltips and arrows, but this often requires more HTML than we can manually edit into the audio player. This text is held inside a class .mejs-time-float which uses display: none by default. We only wish to show the time value when hovering on top of the scrubber bar, so it can help users decide how far along they need to skip.

Overall this plugin does require a lot of code to get everything looking good. It may very well take you more than a day to achieve a finished product. However I would also argue that MediaElement.js is the most precise solution for modern HTML5 audio/video players. If you have a beautiful mockup design it can almost certainly be created with enough time & patience.

Live DemoDownload Source Code

Closing

I do hope this tutorial can shed a bit of light onto the process of customizing an HTML5 audio player. Using common CSS3 styles without any JS is a possibility, but it has very poor support among older versions of IE and similar out-of-date browsers. Although JavaScript is not always supported by every browser, MediaElement.js provides a simple fallback to the classic HTML5 player interface.

Feel free to download a copy of my project source code and see what type of UI designs you can build!