Creating Your Own Accessible HTML5 Media Player

This was my first in a series of blog posts on creating an accessible HTML5 audio player. If you’re more interested in outcome than process, see the article Putting it all together: Accessible HTML5 Audio Player with Yahoo! Media Fallback.

HTML5 introduces the <audio> and <video> elements, which enable web browsers to natively support media without requiring users to download and install a plug-in. It also greatly simplifies the code required to add media to a web page. For example, here’s all you need to add Man With Small F (The Inaccessible PDF Song) to your HTML5 website (feel free – I don’t mind):

<audio controls tabindex="0">
  <source type="audio/ogg" src=""/>
  <source type="audio/mpeg" src=""/>
   Sorry, your browser does not support HTML5 audio.

In the above example, the <audio> element includes two attributes:

  • controls – Tells the browser to display the default media player.
  • tabindex – places the player in the page’s tab order. This is necessary in order for keyboard users to access the controls (more on that in a moment).

This example also includes two <source> elements, which is necessary since browsers don’t currently support a common file type, either for video or audio.

Finally, this example includes fallback content, which is delivered to users whose browsers don’t support <audio>. But this is only an example: Don’t do what I did. Telling the user "Sorry, your browser does not support HTML5 audio" is not good fallback content. Instead, we could provide a link to download the media file, or could add an <object> element that delivers an alternative accessible Flash player (more on that later too).

HTML5 <audio> and <video> have been written about extensively elsewhere. My favorite all-encompassing resource is the article by Simon Pieters on the Opera Developer Blog titled Everything you need to know about HTML5 video and audio. In fact, that article was the launching point for everything you’re about to read here. My focus in this article is on audio, since my goal is to provide an accessible audio player for my music blog.

The Problem

Current versions of most major browsers support <video> and <audio> at some level. Internet Explorer will support these elements in the next major release (IE9). Unfortunately, none of the current browser implementations are fully accessible by keyboard nor screen-reader.

Keyboard Accessibility

Here’s the status as of August 22, 2010:

  • Opera 10.6 is the only browser that supports keyboard access to individual player components (the play/pause button, seekbar, mute button, and volume control. This is true in both the Windows and Mac versions. However, there is also one quirk in both versions: users can tab to the seekbar slider, but they can’t operate it, at least not using any obvious keys (I tried all four arrows, plus Page Up and Page Down). Otherwise all controls are fully accessible by keyboard, including (interestingly) the volume slider, which can be controlled using up and down arrows.
  • Firefox 3.6 (Windows and Mac) has partial keyboard support. The player can receive focus in the tab order, and once it has focus users can toggle play/pause with the space bar, and can seek forward and backwards using the left and right arrows. There is no apparent way to access the Mute button or volume control with keyboard alone. However, if the user mouses over the mute button, the volume control is displayed and at that point it can be adjusted up or down using the arrow keys. Interestingly, the volume can only be adjusted with keyboard – it’s not accessible to mouse users!
  • Safari 5.0 (Mac) and 5.0.1 (Windows) both have the same problem: Users can tab to the player, but once it has focus they can’t access any of the individual controls, nor can they trigger the play event. It’s also worth noting that Safari’s player (in either browser) doesn’t provide a volume control, so if users want to adjust multiple audio sources independently of the main system audio, they’re out of luck in Safari. An obvious beneficiary of independent volume controls is screen reader users, particularly important on a Mac since Safari is the likely browser of choice for VoiceOver users.
  • Chrome 5.0, like Safari, is 100% inaccessible by keyboard.

Screen Reader Accessibility

JAWS 11 does not support <audio> yet. It just skips the audio player entirely. According to TPG’s new AViewer (as used in Firefox), MSAA is exposing the <audio> element as a nameless, valueless, descriptionless node with role="grouping" and state="focusable". That could surely be more explicit. However, the individual player components are being exposed with much more useful information. The Play button has role="push button" and name="Play" (or "Pause", depending on its state); the slider has role="slider", and a name that includes the elapsed time and total duration; and the Mute button has role="push button" and name="Mute" (or "Unmute", depending on its state). I would think this would give screen readers enough information to work with.

In fact, NVDA in Firefox does recognize <audio> and renders it meaningfully. It reads the buttons with their current name as exposed by MSAA (Play, Pause, Mute, or UnMute), and these buttons work perfectly. As for the slider, NVDA reads the current position as a percentage value. It also sounds a tone that gradually ascends in frequency as the audio loads, and stops when the file is fully loaded. However, once the slider has focus I couldn’t figure out a way to control it (anyone else?) There is also another quirk: Before playback begins, the position of the slider is announced as “0.178071510753741 percent”. I think most users would be content if that value was rounded to the nearest second (i.e., 0).

On a Mac, Voiceover in Safari announces the <audio> element as "Audio Element Controller Toolbar". The user can access the individual components with VoiceOver command Control + Option + Shift + Down arrow. VoiceOver announces that there are four items on the toolbar, and each is clearly named (there’s a Play Button, Mute button, "Back 30 Seconds Button", and the "X seconds slider" where X is the current position in seconds. Each of these controls is operable.

So, things are currently looking pretty good for VoiceOver users, and not too bad for NVDA users in Firefox. However, since there are quite a few users who fall outside of these groups, I’m convinced that we need an interim solution:

The Solution: A Custom HTML5 Media Player

See the sample custom player on my HTML5 Audio Test Page.

Until browsers and assistive technologies provide better, more accessible support for HTML5 <audio>, we need to build our own player controls. Fortunately HTML5 media elements include a powerful API that allows us to access and control their inner workings using external controls. My HTML5 Audio Test Page compares the default controller with a custom-built accessible one. I commented the code on that page extensively, so I won’t go into all the technical details here, but here are a few highlights on how I built the accessible controller:

First, I removed the controls attribute from <audio>. Without this attribute, the default player is not displayed. Then, I added an empty <div>, to which I would add the custom controls using Javascript. By building it with Javascript, I can test whether the browser supports <audio>. If it does, the custom controls are added to the DOM. Otherwise, users get the fallback content contained within the <audio> element. Here’s how I tested for <audio> support:

audio = document.getElementById(‘player2’);
if (audio.canPlayType) {
   //this browser suports HTML5 audio
   //add the controller elements to the empty div, plus other code that handles their behavior

My first attempt at building a controller was (somewhat) successful, and the HTML that was created by Javascript looked something like this:

<div id="controller">
<input type="button" id="playpause" value="" title="Play"/>
<input type="range" min="0" max="[set dynamically]" step="any" id="seekBar"/>
<span id="timer">
 <span id="currentTime"></span>
 <span id="duration"></span>
<input type="button" id="muteButton" value="" title="Mute"/>
<input type="range" id="volumeControl" min="0" max="1" width="30" height="100" step="any" />

In other words, the controller included:

  • A Play/Pause button
  • A seekbar/slider. This uses an <input> with type="range", another new feature in HTML5
  • A timer showing current elapsed time (in minutes:seconds), followed by the total duration
  • A mute button. When this is clicked, the volume is muted. If it just receives focus but is not clicked (i.e., via onmouseover or onfocus events), a volume controller appears.
  • A volume controller, hidden by default. Like the seekbar/slider, this control uses an <input> with type="range".

I ran into a problem with the two buttons (Play/Pause and Mute/Unmute). My intent was to render these buttons as standard visual player controls, with well-recognized icons representing their functionality. They originally had value=”Play” and value=”Mute” respectively. In CSS I set color:transparent on these input fields, plus assigned a height, width, and background-image. This worked great in all browsers except Opera, which displayed the value text on top of the buttons:

screen shot of custom HTML5 player, with text displayed clumsily over top of the player buttons

Opera normally supports color:transparent, but apparently that only applies to text nodes, not input values. If there’s a way to tackle that with CSS3 selectors, I don’t know what it is, so the workaround I used instead was to use title instead of value to communicate the function of the buttons. I set value=”” on both buttons, then set title="Play" and title="Mute" respectively. Each of these titles (and the corresponding background image) is changed to "Pause" and "UnMute" respectively with Javascript when the button is clicked. Both JAWS and NVDA read these titles correctly.

Another problem is that Firefox doesn’t support <input type="range">. Instead, it just shows a text box. I considered leaving the text box in place, and wrote Javascript code that allows users to seek forward and backward in the audio by entering a value into the text field. However, it resulted in a pretty clunky user interface:

screen shot of Firefox custom HTML5 player, with a text input field where other browsers display a slider

So, I decided to replace that particular input field with rewind and fast forward buttons. If users’ browsers can handle a slider, users will get a slider. Otherwise (Firefox), they’ll get these buttons. I currently have the rewind and fast forward interval set to 15 seconds, but that’s customizable with a single variable.

screen shot of Firefox custom HTML5 media player with rewind and fast forward buttons rather than a slider

The lack of support for <input type="range"> also affects the volume control, but introduces yet another problem because I wanted it to be displayed as a vertical slider, rather than horizontal (which is customary for volume controls). I set it up to be hidden by default, then to display whenever a user hovered over or tabbed to the mute button. Being a slider, Firefox displayed it as a text field which was odd looking. However, the new problem is that no browser other than Opera (in Windows only) correctly recognizes it as a vertical slider. The spec says: "When the control is taller than it is wide, it is expected to be a vertical slider, with the lowest value on the bottom." In this case, my #volumeControl div was stylized with width:30px and height:100px, and included width and height attributes with these same values, yet Chrome, Safari, and even the Mac OS version of Opera still displayed it as a horizontal slider within a vertical container:

screen shot of custom HTML5 media player in Google Chrome, with a tiny horizontal slider displayed inside a tall vertical container, which just looks silly

Here’s Opera’s (correct) rendering in Windows:

screen shot of custom HTML5 media player in Opera for Windows, the only browser that displays a vertical slider correctly

Yet another problem related to sliders: Just because a browser supports sliders doesn’t mean they’re keyboard-accessible. Again, my hat is off to Opera: Their perfectly-rendered horizontal slider can be controlled with left-and-right arrow keys, and their perfectly-rendered vertical slider can be controlled with up-and-down arrow keys. In contrast, both Safari’s and Chrome’s slider can receive tab focus, but once it has focus the user can’t do anything with it.

Given the widespread problems with the vertical slider, I commented out the volume control for now and replaced it with a pair of research-based volume up and down buttons, which are easier for most folks to use anyway, and each can be assigned an accesskey for easy triggering (see below).

I decided to keep the horizontal slider in all browsers that support it, even though the rewind and fast forward buttons may be easier to use, especially since they have accesskeys assigned.
I figured some sighted mouse users might prefer the slider though.

However, if users need to control it without a mouse they’ll need to use either Opera or Firefox.

On a Mac, Voiceover in Safari announces the Play and Pause buttons, and it does a great job of identifying the sliders, including information about the current slider position (i.e., "40% slider"). VoiceOver users can interact with the slider by pressing Control + Option + Shift + Down Arrow when the slider has focus. Then, they can use the arrow keys to advance it forward or back.

Accesskey Controls

I assigned the following accesskeys to my custom controls, so they can be triggered anywhere from the web page:

  • P = Play/Pause
  • R = Rewind
  • F = Fast Forward
  • S = Slider
  • M = Mute/Unmute
  • U = Volume Up
  • D = Volume Down

All browsers implement accesskeys differently (details are on the Wikipedia Accesskey page), and in this case they aren’t recognized at all in Opera due to a bug in which Opera doesn’t recognize accesskeys on dynamically created elements. That’s not a huge problem because the way Opera implements accesskeys isn’t much of a time saver anyway (Shift + Esc brings up a list of accesskeys, then the user navigates the list).

For browsers that display a slider, I’m hiding the rewind and fast forward buttons, but they can still be implemented using their accesskeys.

What about a Fallback Player?

For users whose browsers don’t support HTML5, we could simply link to the audio files and let users download them and play them using their preferred audio software. Or we could provide a Flash MP3 player like we did in the olden days. For an accessible Flash video player, I’ve had some success with Longtail Video’s JW Player, which would also work for audio. However, the JW Player is not free for commercial use, and I’m really hoping for a free solution so others can easily (and cheaply) replicate it. With that in mind, I spent the last couple of hours testing each of the 10 Easy To Implement Flash Based Mp3 Players with JAWS 11. Unfortunately, eight of the ten players do not have labels on their controls (this includes those players that just have one stinking button). With no labels, JAWS identifies the various controls as "Graphic 1", "Graphic 2", etc., or in some cases simply says "Flash movie start. Flash movie end."

Of course, all you optimists know the glass is not 80 percent empty – it’s 20 percent full! There were two Flash MP3 players that showed much promise in my testing. I’ll save the details for another blog post, but for now you can check these players out on my Nifty Player and Yahoo! Media Player test pages.


I welcome any insights or suggestions. Follow the "Comments" link at the bottom of this page. Thanks!

17 replies on “Creating Your Own Accessible HTML5 Media Player”

These blog is really great to share with us. In these there are so many things which is really great. It will simply link to the audio files and let users download them and play them using their preferred audio software.

How would you create a custom "Stop" button. My problem is that on the iPad, it seems that if there are multiple 's when you pause one, none of the others will play unless the first one has ended. When a user clicks on the second control after starting to play and then pausing the first one, the second one will not play. If the first one has actually ended, the second one will play.

So, we need to check to see if the first one is playing or is paused and make it end before the second one will play.


Hi Linda,

Is the behavior you're describing unique to the iPad, or does it behave that way on other devices/other browsers too? I don't have an iPad to test with, but I do have a playlist that works well in most browsers on both Mac OS and Windows (note that it doesn't work in Firefox 3.x, but does work in FF 4.0 beta):

If you click on a title in the playlist, it loads that song, and if another song is already playing, it automatically starts playing the new song. I'd be curious to know how this does on an iPad.

I hate to muddy the water but I'm using your wonderful examples as a starting point for an HTML5/CSS3 page destined for cell phone users only. How many of the HTML5-doesn't-handle-this-on-all-browser workarounds can I drop? Can I rely on the default HTML5 audio tag to handle Android+iPhone and leave it at that? Thanks again for posting this.

@JetCityOrange, Thanks for muddying the water! Unfortunately since HTML5 is a work in progress, I think exceptions are the norm even for mobile devices. Here's what I know:

Safari on iPhone supports the HTML5 element, but if your element contains multiple elements, the one supported by Safari (e.g., MP3) needs to be first. If the first source file that Safari encounters is one it can't play (e.g., Ogg), it throws up a "Cannot play audio" error rather than trying to load and play the second source file, which is what it's supposed to do. Unfortunately, VoiceOver (iPhone's screen reader app) does not currently support the element at all – it just ignores it. So building a custom player is necessary for VoiceOver users, and the one I've created works beautifully.

As for Android, I'm not sure whether any of the Android browsers support yet. And last I heard Android's screen reader (TalkBack) was not working yet in any browser. If anyone has a status update on either of these issues, please share.

Note that the most current version of my custom player is here:


The volume *can* be adjusted in Firefox with the mouse if you set the height of the audio element to ensure that the volume slider is still contained by the bounding box so that when the mouse user is selected their preferred audio level, the audio element doesn't lose focus. Unfortunately, the default bounding box is the audio element's unexpanded height so that when you are attempting to use the volume slider, it loses focus immediately.

@J I've written a more recent post that puts it all together: An HTML5 player with Yahoo! Media Player as an accessible fallback. That's described here, and links to a test page where you can access all the code: (It's also available as a WordPress plug-in)

Note, however, that if you only have mp3's there will be some browsers that won't play your files, most notably Firefox and Opera. For HTML5 audio to work in all browsers you'll need to provide your audio in two formats – mp3 and ogg is currently a good combination. Alternatively, you can not use HTML5 audio at all, and just use the Yahoo! Media Player, which is a Flash-based player that supports MP3 in all browsers. More on that here:

Here is another BIG question I have, when I put the code in for the html5 player it works perfectly, however it immediately loads the mp3 file from the server. Since I am placing many of these html5 players into the site, they will all "load". IS there a way to start the loading process when the person clicks the "play" button? This is vitally important, as it takes a lot from my server and the mp3 flash player I use does not load the file until they push play.

So building a custom player is necessary for VoiceOver users, and the one I've created works beautifully!!

Good stuff Terrill,
I am learning lots thanks to your examples on HTML5 audio players. I hope to add a variable speed playback below the main slider, something like ten ticks for forward, ten for back and to show the time integral of this in the main slider. Have you seen any variable rate code?

Comments are closed.