How to control the <audio> tag


A guide by Andy Moore Published August 2020

Some internet radio station stream players really suck!

Common problems include:

Hopefully this guide will help some stations build better players, and give their listeners a better experience on their website.

I'll provide you with all the ingredients you need, and recipes to bake stream players for your radio station: how you decorate it is up to you

This guide to controlling the <audio> tag is broken down into several sections, each with code samples and examples:

What the W3 and Mozilla say about the <audio> tag


Example 1: Basic Player


player-1.html

This is the most basic player, live example:

<audio controls>
  <source src="https://andymoore.info:8080/listen" type="audio/mpeg">
  Your browser does not support the audio tag.
</audio> 

The controls attribute instructs the browser to show play and pause buttons, plus a volume slider.

The src attribute of the <source> tag should be the address of your stream.

The type attribute of the <source> tag should be the content type of your stream.

"Your browser does not support the audio tag"

This would only ever show if the user's browser was unable to play audio.

The compatability chart below includes all the major browsers, though does not include screen readers which are used to assist blind and partially sighted users.

Would you go up to wheelchair user who was struggling to get up a kerb and tell them their legs don't work?

Of course not, you'd offer them help instead, and that's really the better way to treat browsers that don't support the audio tag.

If you Google search "Your browser does not support the audio tag." you'll find 1.9 million results, all of which I believe could be improved to give a better experience to users of adaptive technology. Here's my simple suggestion, include a link to your stream!

<audio controls>
  <source src="https://andymoore.info:8080/listen" type="audio/mpeg">
  <a href="https://andymoore.info:8080/listen">Click here to listen to our stream on a media player of your choice</a>
</audio> 

This small change is a huge benefit to our disadvanted visitors: we've gone from saying "your legs don't work", to actually being helpful.


Example 2: Basic Player with Autoplay


player-2.html

This code will create something which looks the same as the player above, but it'll play when the browser loads.

<audio controls autoplay>
  <source src="https://andymoore.info:8080/listen" type="audio/mpeg">
  <a href="https://andymoore.info:8080/listen">Click here to listen to our stream on a media player of your choice</a>
</audio>

The autoplay attribute instructs browsers which support this feature, to play the stream as soon as the page has loaded.

Let the user control when your stream starts, it should be their choice, not yours

Considerations:

Example 3: Custom Start and Stop Buttons


player-3.html

We're going to use Bootstrap to style the buttons, and jQuery to control the audio player.

Call the Bootstrap CSS in the head of your page, just before the </head> tag:

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">

Call the jQuery JavaScript at the bottom of your page, just before the </body> tag:

<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>

If you can host these files on the same domain name as your player your site will be quicker to load

Now we need to assign an ID to the player

<audio id="player">
  <source src="https://andymoore.info:8080/listen" type="audio/mpeg">
  <a href="https://andymoore.info:8080/listen">Click here to listen to our stream on a media player of your choice</a>
</audio>

Notice how we have removed the controls and autoplay attributes: this makes the player invisible to the user, without some custom controls they'll be unable to listen to your stream.

We remedy this by adding our own buttons, they both have an ID and an onclick command to control the player.

<button id="play" onclick="javascript:streamplayer( true );" class="btn btn-success">PLAY</button>
<button id="stop" onclick="javascript:streamplayer( false );" style="display:none;" class="btn btn-danger">STOP</button>

Next we need to add the JavaScript which will start and stop the stream, and show or hide the buttons:

<script>
  function streamplayer( command ){
    if( command === true ){
      $( "#play" ).hide();
      $( "#stop" ).show();
      $( "#player" ).trigger( "load" );
      $( "#player" ).trigger( "play" );
    }else{
      $( "#stop" ).hide();
      $( "#play" ).show();
      $( "#player" ).trigger( "load" );
    }
  }
</script>

Because some users have opted to disable JavaScript, and the player is unusable without it, we should offer a normal player as a fallback:

<noscript>
  <audio controls>
    <source src="https://andymoore.info:8080/listen" type="audio/mpeg">
    <a href="https://andymoore.info:8080/listen">Click here to listen to our stream on a media player of your choice</a>
  </audio>
</noscript>
Considerations:

Example 4: Custom Start and Stop Buttons, with stream loading indicator


player-4.html

It's time to make our buttons prettier with some nice icons from fontawesome.com:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

Now we ammend our buttons to use proper icons, and create a new loading button:

<button id="play" onclick="javascript:streamplayer( true );" class="btn btn-success">
  <i class="fa fa-play"></i>
</button>

<button id="loading" onclick="javascript:streamplayer( false );" style="display:none;" class="btn btn-info">
  <i class="fa fa-play-circle"></i>
</button>

<button id="stop" onclick="javascript:streamplayer( false );" style="display:none;" class="btn btn-danger">
  <i class="fa fa-pause"></i>
</button>

You can change the icons to suit your site, or even do away with them and use images in their place.

We also need to revise the function to show the loading indicator when the play button is clicked, and to hide it when the stream is playing:

function streamplayer( command ){

  if( command === true ){

    $( "#play" ).hide();
    $( "#loading" ).show();
    $( "#player" ).trigger( "load" );
    $( "#player" ).trigger( "play" );

    var obj = document.getElementById( "player" );
    obj.addEventListener( "loadeddata", function() {

    if( obj.readyState >= 2 && $( "#loading" ).is( ":visible" ) ) {
        $( "#loading" ).hide();
        $( "#stop" ).show();
      }
    });

  }else{

    $( "#stop" ).hide();
    $( "#loading" ).hide();
    $( "#play" ).show();
    $( "#player" ).trigger( "load" );

  }
}
Considerations:

Example 5: Volume Controls


player-5.html

Give the user something to slide and drag around which will control the volume:

<input type="range" step="10" class="custom-range" id="volume" onchange="javascript:volume( this.value );">

Add the JavaScript function to change the volume:

function volume( level ){
  $( "#player")[0].volume = level / 100;
}

And ammend the main function so that it starts playing at the set volume:

volume( $( "#volume").val() );
Considerations:

Example 6: Now Playing


player-6.html

Everything so far has been done with JavaScript and HTML, to bring in live now playing data we need to employ a little PHP, and there are two different methods we'll cover that will enable you to get the data.

The following code samples all output the meta data for that stream; this allow us to show it to the visitor on the page, and keep it updated in near real-time by using a little more JavaScript.


Method 1: FFMPEG


now-playing-1.php

This method will use FFMPEG to connect to your stream, and pick out the data needed.

It's worth noting that this isn't the most optimal way to get your now playing data, but it is one of the coolest in that this same snippet of code is like a digital set of ears: being able to listen digitally opens up possibilities like stream monitoring services, volume detection, silence detection and more.

<?php

$ffmpeg = 'C:/ffmpeg/ffmpeg.exe'; // change to $ffmpeg = 'ffmpeg'; on Ubuntu etc
$stream_url = 'https://andymoore.info:8080/listen';

$stream = @shell_exec( "$ffmpeg -t 1 -i $stream_url -af 'volumedetect' -f null /dev/null 2>&1" );

$info = @explode( 'StreamTitle     :', $stream );
$info = @explode( "\n", $info['1'] );

header( 'Cache-Control: no-cache, must-revalidate ');
header( 'Pragma: no-cache' );
header( 'Expires: '.gmdate( 'D, d M Y H:i:s \G\M\T', time() - 60 ) ); 

@die( $info['0'] );

Method 2: ICECAST JSON


now-playing-2.php

This method connects to an Icecast stream's JSON data and reads through each mountpoint till it finds a match on the details you provided, it'll then take the 'title' value Iceast provides and echo it to the browser.

With this using JSON it could actually be fully managed on the client side through JavaScript, rather than making back end requests to a PHP script, however we're doing server side with PHP as that sets us up nicely for the next step when we integrate album artwork.

<?php

$stream_json = 'https://andymoore.info:8080/status-json.xsl';
$mount = 'http://andymoore.info:8000/listen'; // mount must be http b/c icecast sucks with ssl

$info = json_decode( file_get_contents( $stream_json ), 1 );

foreach ( $info['icestats']['source'] as $source ){
  if( $source['listenurl'] == $mount ){
    $info = $source['title'];
  }
}

header( 'Cache-Control: no-cache, must-revalidate ');
header( 'Pragma: no-cache' );
header( 'Expires: '.gmdate( 'D, d M Y H:i:s \G\M\T', time() - 60 ) ); 

@die( $info );

Method 3: GET MP3 STREAM TITLE FUNCTION


now-playing-3.php

There are multiple variations of this function floating around the web; it's pretty old and I don't know who the original author is so am unable to credit them: if you are the original author, let me know, I hate it when people take my work, post it elsewhere and take credit for it...

As much as I don't like using code that was written by another, this works well enough that there's no need to reinvent any wheels.

<?php

echo getMp3StreamTitle( 'https://andymoore.info:8080/listen' );

function getMp3StreamTitle($streamingUrl, $interval = 1023, $offset = 0, $headers = true)
{
    $needle = 'StreamTitle=';
    $ua = 'Mozilla';
    $opts = [ 'http' => [ 'header' => 'Icy-MetaData: 1', 'user_agent' => $ua ] ];

    $context = stream_context_create($opts);
    if ($stream = fopen($streamingUrl, 'r', false, $context)) {
        $buffer = stream_get_contents($stream, 8196, $offset);
        fclose($stream);
        if (strpos($buffer, $needle) !== false) {
            $title = explode($needle, $buffer)[1];
            return substr($title, 1, strpos($title, ';') - 2);
        } else {
            return getMp3StreamTitle($streamingUrl, 8196, $offset + 8196, false);
        }
    } else {
        throw new Exception("Unable to open stream [{$streamingUrl}]");
    }
}

Putting it all together

Now we need to give an ID to where we want the now playing data to show on the page:

<h1 id="now_playing" class="text-center">.... loading ....</h1>

Next we need some more JavaScript, this will read the now playing script we just created, and update the page with the details:

function now_playing(){
  $.get( "now-playing-2.php", function( data ){
    $( "#now_playing" ).html( data );
  });
  setTimeout(function(){ 
    now_playing();
  }, 10000);
}

The value of '10000' in the JavaScript above equates 10 seconds between each refresh; if this is too infrequent lower the number by 1000 for every second you want to reduce the time by, if you call it too often you're placing more load on the client than needed, too infrequently and it doesn't update quick enough.

It's impossible to get the now playing data to update the very same second the song changes: a stream could lag by any number of seconds being the main reason; there's also the lag of the stream to take into account, plus how long it takes the script to get the data from your stream.

A refresh time of five (5000) or ten (10000) seconds in most cases should suffice.

Finally, we update the <body> tag of the document to call this function as soon as the page has loaded:

<body onload="javascript:now_playing();">
Considerations:

Example 7: Artist Album Artwork


player-7.html

The Last FM is the best album artwork API I've integrated, what makes it great is that other providers don't have the right artwork, they've all too often got a compilation album, whereas Last FM is more likely to have the art for either the single or album.

You now need to go to Last.FM and get yourself an account if you haven't got one already, then Create a Last FM API Account.

now-playing-4.php

Now you need to edit your now playing script so that it also connects to Last FM to get the image URL for the artist / album artwork:

<?php

$lastfmkey = 'LAST_FM_API_KEY';
$stream_json = 'https://andymoore.info:8080/status-json.xsl';
$mount = 'http://andymoore.info:8000/listen'; // mount must be http b/c icecast sucks with ssl

// icecast json method for getting the now playing data

$info = json_decode( file_get_contents( $stream_json ), 1 );

foreach ( $info['icestats']['source'] as $source ){
  if( $source['listenurl'] == $mount ){
    $info = $source['title'];
  }
}

// last.fm api connection

@list( $artist, $title ) = explode( ' - ' , $info);

$lastfm = 'https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key='.$lastfmkey.'&artist='.urlencode( $artist ).'&track='.urlencode( $title ).'&format=json';
$track = json_decode( file_get_contents( $lastfm ), 1 );

if( isset( $track['track']['album']['image']['3']['#text'] ) ){
  $info .= ' - '.$track['track']['album']['image']['3']['#text'];
}

header( 'Cache-Control: no-cache, must-revalidate ');
header( 'Pragma: no-cache' );
header( 'Expires: '.gmdate( 'D, d M Y H:i:s \G\M\T', time() - 60 ) ); 

@die( $info );

Now update your page so there's somewhere for the image to go, fill in the blank src value with the address to a placeholder image:

<div id="artwork" class="text-center mb-3" style="display:none;">
  <img id="image" src="" />
</div>

Next you need to edit the JavaScript as it's now getting more information from the now playing script:

function now_playing(){
  $.get( "now-playing-3.php", function( data ){
    var data = data.split( " - ")
    $( "#now_playing" ).html( data["0"] +" "+ data["1"] );
    $( "#image" ).attr( "src", data["2"] );
    $( "#artwork" ).show();
  });
  setTimeout(function(){ 
    now_playing();
  }, 10000);
}
Considerations:

Example 8: The Popup Player


Players which stop playing when your visitor changes pages are useless!

Loading your stream player in a popup window will keep it playing in the background, even when your listener has left your site.

<button class="btn btn-info mb-3" onclick="javascript:window.open('player-7.html','_blank','width=350,height=600');">Launch Popup</button>

Has this helped? Why not treat me to an Amazon Gift Card?


Example 9: The Persistent Player


You're already viewing the demo for this player.

Free Radio Station Website Builder

© 2020 Andy Moore Privacy Policy Terms and Conditions