An HTML5 <audio> Radio Player

Introduction

Previous to HTML5 coming on the scene, it was a fairly awkward task to add audio to web pages. For many years, Flash was the only way to provide audio in any kind of interactive way – but with the introduction of the <audio> element in HTML5, audio playback can now be done natively. It plays nicely with other open standards — you can create custom buttons using CSS and HTML, and give them appropriate functionality using the HTML5 audio API. It is nice not having to go back into Flash every time you want to make some changes to the audio content.

In this article we will look at the <audio> element, starting with the basics, looking in detail at how it works across different browsers, and then build a radio player application that uses live audio from a streaming server.

The basic syntax

The <audio> element is simple to use. To play an Ogg Vorbis file you can simply write:

<audio src="http://yourserver/rockandroll.ogg" controls preload> </audio>

The browser will then provide a simple player element in the web page.

Native audio element in Opera

Figure 1: a basic <audio> element rendered in Opera.

The <audio> element has five attributes:

  • src contains the path to the audio file you want to play. The src attribute can also be replaced with one or more nested <source> elements like this:

    <audio controls preload>
    	<source src="http://yourserver/rockandroll.ogg" />
    </audio>

    This is useful because you can use multiple <source> elements to point to different audio formats. As you'll see later, different browsers support different formats, so ideally you should provide something that any browser can play. For example:

    <audio controls preload>
    	<source src="http://yourserver/rockandroll.ogg" />
    	<source src="http://yourserver/rockandroll.mp3" />
    </audio>

    Note: to convert between Ogg and MP3 formats, there are a variety of free applications available, such as Free Rip for Windows, and Audio MP3 converter for Mac.

  • autoplay is a boolean attribute specifying whether the source file should start to play automatically at page load or not.

    Note: bear in mind that autoplay is considered bad by many users. Autoplay will force audio to play without the interaction of the user and can interfere with other audio sources the user might be listening to. If you need to use autoplay, make sure you always provide the user with the possibility to stop the playback.

  • preload tells the browser to make an informed decision about how much data to download. A mobile browser may decide to preload nothing, in order to conserve bandwidth, while a desktop browser on a fast connection might begin loading immediately. Available values are preload="none", preload="metadata" and preload="all". Find out more about the preload attribute.

  • loop is a boolean attribute specifying whether the source file should start to play all over again when the end of the source file has been reached.

  • controls is a boolean attribute specifying whether or not the browser should display its default media controls or not. If you don't specify this, no controls are shown, and you need to create your own controls using the handy audio JavaScript API and HTML, CSS, and whatever other web standards you want to draw the controls with.

The JavaScript API

The <audio> element exposes a powerful JavaScript API. In this article I will only scratch the surface of this API, since we will be using a third party library to help us build our player, but it is still useful to know about.

In JavaScript we can invoke an Audio object, which returns an <audio> element. Note that this element will not be part of the page's DOM, unless we explicitly add it to the document with further scripting. However, regardless of whether or not it's in the DOM itself, the <audio> element can be controlled via its API methods and properties. We can pass the URL of an audio file we want to play as an argument to the object like this:

var audio = new Audio("http://yourserver/rockandroll.ogg");

We can also change the source file by adding a new value to the src attribute:

audio.setAttribute("src", "http://yourserver/morerock.ogg");

By accessing the methods audio.play() and audio.pause() it is possible to start and pause the playback of the source file. audio.volume provides access to the volume, and the eventlistener timeupdate fires an event for every time update during playback. These simple methods are all we need to make a simple player, with the same functionality as the default player the browser provides.

Let's look at these features in action! The following script will construct an <audio> element and assign event handlers to some simple HTML buttons that we can then use to control the audio playback:

// Invoke new Audio object
var audio = new Audio('test.ogg');

// Get the play button and append an audio play method to onclick
var play = document.getElementById('play');
play.addEventListener('click', function(){
	audio.play();
}, false);

// Get the pause button and append an audio pause method to onclick
var pause = document.getElementById('pause');
pause.addEventListener('click', function(){
	audio.pause();
}, false);

// Get the HTML5 range input element and append audio volume adjustment to onchange
var volume = document.getElementById('volume');
volume.addEventListener('change', function(){
	audio.volume = parseFloat(this.value / 10);
}, false);

// Get where one are in playback and push the time to an element
audio.addEventListener("timeupdate", function() {
	var duration = document.getElementById('duration');
	var s = parseInt(audio.currentTime % 60);
	var m = parseInt((audio.currentTime / 60) % 60);
	duration.innerHTML = m + '.' + s + 'sec';
}, false);

The above script is applied to the following HTML:

<div>
	<input id="play" type="button" value="Play" />
	<input id="pause" type="button" value="Pause" />
	<span id="duration"> </span>
</div>
<div>
	Volume:
	<input id="volume" type="range" min="0" max="10" value="5" />
</div>

See the simple audio player in action.

If you want to read about the Audio API in more detail I suggest you dive into Simon Pieters' "everything you need to know about HTML5 video and audio" article.

Codec support across browsers

The <audio> element goes hand in hand with the <video> element in HTML5. There have been a lot of debates and disagreements as to which video format to use (read our Introduction to HTML5 video for more details), and audio has undergone the same kinds of discussions. Currently the support for audio codecs across the major browsers is as follows:

Browser(Ogg) VorbisMp3Wave
Opera 10.50xx
Firefox 3.5xx
Safari 4xx
Chrome 3xx
IE 8

To support several browsers we need to provide the same audio content in different formats. As we mentioned before, you can reference the different formats using multiple <source> elements placed inside the <audio> element:

<audio controls preload>
	<source src="http://yourserver/rockandroll.ogg" />
	<source src="http://yourserver/rockandroll.mp3" />
	<!-- A fallback solution - Flash would do -->
</audio>

A fallback solution for browsers that don't support the <audio> element can (and should) be included within the opening and closing audio tags. For instance, this could be a Flash player, referencing the same MP3 file we point to in the second <source> element.

Unknown territory — playing streaming audio through the <audio> element

We now have audio playback of single audio files using <audio> nicely sorted out across browsers. However, next we come upon fairly unknown territory: playing a live audio stream through <audio>. The aim throughout the rest of this article is to build a radio player based on the <audio> element.

Simple tests show that currently browsers handle streams very differently. We're still in the early stages of HTML5, so hopefully support for audio streams will improve in the near future. On the bright side, it is clearly possible to provide streams as inputs for the <audio> elements, and by using the multiple sources and the fallback features it is possible to make it work across all modern browsers.

This article does not touch the topic of setting up a streaming server but there are several good tutorials available on setting up Icecast to stream live content as both Ogg and MP3.

Building the player

We will build our player on top of some audio streams provided by The Norwegian Broadcasting Corporation (NRK). NRK streams each radio channel in both Ogg and MP3 format, so there are two sources to work with.

To help us deal with possible cross-browser quirks, and the issue of support for different audio formats in different browsers, we will be using the jPlayer plug-in for jQuery. jPlayer provides a common interface using the native <audio> element in modern browsers, and a Flash fallback for older browsers. By having a common interface for both the native <audio> element and the Flash fallback, we are able to create a common design, made with JavaScript, CSS and HTML, on our player without worrying about if the native part or the fallback are used for playback.

Separating the data from the player

First, we'll make a small JSON feed containing information about our streams. We're including some general information about the radio station and each radio channel — the URLs of the streams, the channels' names, and a URL pointing to a logo associated with each channel:

{
	"station" : {
		"name" : "NRK",
		"fullname" : "Norsk Rikskringkasting AS",
		"website" : "http://www.nrk.no/",
		"defaultChannel" : "P1",
		"channels" : [

			{
				"name" : "P1",
				"channel" : "NRK P1",
				"website" : "http://www.nrk.no/p1/",
				"schedule" : "",
				"logo" : "http://yoursite/gfx/nrk_p1.png",
				"streams" : {
						"type" : "middle",
						"ogg" : "http://radio.hiof.no/nrk-p1-128.ogg",
						"mp3" : "http://radio.hiof.no/nrk-p1-128"
				}
			}

		]
	}
}

By putting the information about the radio station and streams in this JSON feed we separate the stream information from the player itself. This is very handy if in the future we want to add, remove or change some data related to the streams (e.g. adding or removing a radio station or pointing to a new URL for the streams.) We can make these changes just by altering the JSON file, and we won't need to touch the actual player.

Adding structure and design

Our next step is to set up an HTML structure to contain the player and its controls:

<div id="radio-player" class="radio-default">

	<!-- Audio placeholder used by jPlayer -->
	<div id="player"> </div>

	<!-- Container for channel picker -->
	<div id="channelPicker">
		<a tabindex="8" accesskey="l" id="paginationLeft" class="inactive"><span>Left</span></a>
		<div id="channels"> </div>
		<a tabindex="9" accesskey="r" id="paginationRight" class="active"><span>Right</span></a>
	</div>

	<!-- Container for display -->
	<div id="display">
		<a id="currentChannel"><img src="gfx/default/default-station.png" /></a>
		<span id="duration"> </span>
		<span id="quality"> </span>
	</div>

	<!-- Containers for admin functions -->
	<a tabindex="7" accesskey="c" id="displayChannelPicker" title="Channels"><span>Channels</span></a>
	<a id="config">Config</a>

	<!-- Containers for jPlayer actions -->
	<a tabindex="3" accesskey="d" id="volumeMin" title="Mute"><span>Mute Volume</span></a>
	<a id="volume"><span>Adjust Volume</span></a>
	<a tabindex="4" accesskey="u" id="volumeMax" title="Max"><span>Max Volume</span></a>
	<a tabindex="1" accesskey="p" id="play" title="Play"><span>Play</span></a>
	<a id="pause" title="Pause"><span>Pause</span></a>
	<a tabindex="2" accesskey="s" id="stop" title="Stop"><span>Stop</span></a>

	<!-- Container for error messages -->
	<div id="error">
		<h2>Error</h2>
		<p> </p>
	</div>

</div>

Each <a> and <div> element is given an id so we can easily style the elements and assign JavaScript functions to them for interacting with the <audio> element via the audio API. jPlayer will be using some of the elements, for example the ones with the play and pause id. We will see that later.

Each button is styled by adding a background image to it and placing the element where we want it in the player using absolute positioning. For example, the CSS for the play button looks like this:

/**
  * Play button
  */
.radio-default #play{
	position: absolute;
	top: 75px;
	left: 255px;
	width: 40px;
	height: 40px;
	background-image: url(../gfx/default/button-play.png);
	background-position: top left;
	background-repeat: no-repeat;
	cursor: pointer;
}

The other controls, channel picker, time display, etc. are created in much the same way.

One small detail worth noticing is the class attribute on the <div> element that wraps the whole player. We can use the value of this class attribute as a prefix on all our styles like this:

.radio-default #play{
	/* some style */
}

By doing so we have added the possibility to skin the player. If we then create a new set of styles with a new prefix we can use a small JavaScript function to switch skins by just changing the value of the class attribute on the surrounding <div> element:

.radio-different #play{
	/* some different style */
}

Putting jPlayer into action

Our final step is to use jPlayer to create all the player functionality. We first add the jQuery library and the jPlayer plug-in to the HTML document, as well as including our own JavaScript file, which will hold additional code:

<script src="script/jquery-1.4/jquery.min.js"> </script>
<script src="script/jplayer-1.1.7/jquery.jplayer.js"> </script>
<script src="script/player.js"> </script>

jPlayer needs the jQuery library to work, but we will use jQuery to also help us with other tasks in our player.

To start with, we need to select the element in our document that will contain jPlayer's generated <audio> element:

// Player element - Holds the jPlayer
var playerElement = jQuery("#player");

Next, we take advantage of jQuery's AJAX functionality to read our JSON feed with the streaming details:

jQuery.ajax({
	url: "http://yourserver/channels.json",
	dataType: 'json',
	ifModified: true,
	success: function(data, status){
		for (var i = 0, len = data.station.channels.length; i < len; i++) {

			// Put each channel into a channel picker

		}
	}
});

When the AJAX request has fetched the data from the server it loops through each channel in the feed and adds them to a channel picker. The channel picker is just a list of image tags containing the logo for each channel and an onclick event for changing the audio streams in the player when the user selects a channel.

Channel picker

Figure 2: The radio station channel picker.

This is a simplification compared to the final player functionality, but the function attached as an onclick event on each image in the channel picker has a small method for clearing out the old stream set on the audio element and adding the stream the user selected as a new stream to the <audio> element:

changeChannel:function(){

	// Remove old stream
	playerElement.jPlayer("clearFile");

	// Set new stream
	playerElement.jPlayer("setFile", "urlToNewMP3Stream", "urlToNewOGGStream");
}

jPlayer takes care of clearing out old streams and setting new ones on the <audio> element. jPlayer knows what type of streams the browser supports and if the Flash fallback should be used. After we have read our channel data and set up a selection of channels the user can choose from, we must set up jPlayer to work against our HTML structure.

playerElement.jPlayer({
	ready: function(){
		this.element.jPlayer("setFile", urlToDefaultMp3Stream, urlToDefaultOggStream);
	},
	swfPath: "script/jplayer-1.1.1/",
	nativeSupport: true,
	volume: 60,
	oggSupport: true,
	customCssIds: true
})
.jPlayer("cssId", "play", "play")
.jPlayer("cssId", "pause", "pause")
.jPlayer("cssId", "stop", "stop")
.jPlayer("cssId", "volumeMin", "volumeMin")
.jPlayer("cssId", "volumeMax", "volumeMax")
.jPlayer("cssId", "volumeBar", "volume")
.jPlayer("onProgressChange", function updateDuration(lp,ppr,ppa,pt,tt) {
	 jQuery("#duration").text(jQuery.jPlayer.convertTime(pt));
});

The above code adds the jPlayer to the #player element we selected earlier. We tell jPlayer to use the native <audio> element if the browser supports it by setting nativeSupport: true; we also set a default volume (volume: 60), the location of the Flash fallback (swfPath: "script/jplayer-1.1.1/"), whether Ogg should be used if supported by the browser (oggSupport: true), and what to do when the player is ready (ready: function( ... );).

In the ready function we set the default radio channel we want to present to the user. If the user selects another channel in the channel picker the selection will send a new stream to the player and override this default.

Finally, we point jPlayer to the IDs of our various control buttons, and set up an event listener when the audio is playing, so that we can change the time display in our player.

We now have a working player that runs in multiple browsers.

Radio player in website

Figure 3: The final radio player running in Opera.

In the final player there are few extra bits of code outside the core functionality that we haven't discussed – to handle navigation in the channel picker and update different elements based on the user interaction. The code for this player is checked into Github so feel free to fork the code and play around with it.

Turning the player into a Widget application

It is cool to be able to add a radio player like this to an ordinary web page, but it comes with some drawbacks. The stream will be broken if the user navigates away from the page holding the player, if the page reloads, or if the browser crashes for some reason. Such events are annoying for the listener and as an audio content provider you want the listener to stay tuned.

By having the player repackaged as an Opera Widget, users will be able to install it as a standalone application in Opera 10.5x. The player will then run in a separate window, independent from the browser, and the stream will continues to play even if the user does not have Opera running.

Packaging the player as a Widget is easy. We add a simple config.xml file to the root directory to define it as a Widget. This configuration sets the name of the Widget, the dimensions of the player window, and which icons should be used to represent the application on the start menu, desktop etc.

Widgets can handle AJAX requests to any server available on the Internet and since we have put all our stream data in a JSON feed on the server-side, we are able to update the data in the stream without having to update the player in any way. This is good since we might be forced to do changes related to our streams faster than the users can update their players. To enable the Widget to make AJAX requests across the network and to our server, we must add network="public" on the <widget> element. Our final configuration file looks like this:

<widget defaultmode="application" network="public private">
	<widgetname>Radio Player</widgetname>
	<description>Radio Player</description>
	<width>300</width>
	<height>120</height>
	<icon>gfx/icon/icon_128.png</icon>
	<icon>gfx/icon/icon_64.png</icon>
	<icon>gfx/icon/icon_32.png</icon>
	<icon>gfx/icon/icon_16.png</icon>
	<author>
		<name>John Doe</name>
		<email>john.doe@yoursite.com</email>
		<link>http://yoursite/</link>
	</author>
	<id>
		<host>radio.yoursite.com</host>
		<name>radio-player</name>
		<revised>2010-05</revised>
	</id>
</widget>

To package the application, we simply zip up the HTML, CSS, JavaScript and any other assets, along with the config.xml file, and change the file extension to .wgt. We now have a Widget ready to be installed.

Radio player as widget

Figure 4: Our radio player running as an Opera Widget.

The player can be found on the Opera Widgets repository.

Summary

In this article we have looked in detail at the HTML5 <audio> element, used jQuery to make our life easier and help us work through cross-browser differences, and built a really useful radio player that works as part of a web page and as a standalone widget.

Read more...