znuff's notepad

Update jPlayer track/listeners on a webpage with jsonp

With version 2.4, IceCast is shipping their own xml2json template. IceCast 2.4 doesn’t yet deal with the CORS issue so if you want to parse the metadata/listener count etc. client-side, you will still need to output JSONP. IceCast 2.4.1 has fixed this.

I recently found myself working on a small web-radio project for some friends, and I stumbled upon some interesting technical “challenges” (to say so).

I wanted a HTML5 Web-Radio Player (I used the excellent jPlayer project, which supports html5’s <audio>in a wide range of browsers, but also has a fall-back to Adobe Flash for older browsers), but I also wanted the current playing track and a listener count on it. I modified a default jPlayer skin to show up the elements I needed, but I was still left with the issue of actually getting the data from the icecast server to my webpage (player).

From my searches I found some PHP scripts that connect to the stream and retrieve the data using the ICY protocol, but those were really slow based on my tests.

Keeping on searching, I found some good ideas of using Icecast’s templating system that is used to generate its admin & stats pages, which are all output as XML, to generate JSON instead.

Now, this would be all fine and I would have considered this matter fixed, except… there’s the “dreaded” Access-Control-Allow-Origin: * issue, which prevents you from loading JSON data from another domain other than the one the page is hosted, unless you send the Access-Control header, which you can’t really do using Icecast.

The other solution involved using JSONP (which is basically JSON wrapped in a function, that is loaded by the browser as a script and interpreted), and this looked perfect!

Without further explanations (because I suck at writing these things), here’s the rundown.

Tools needed:

First step is to create the XSL file that is used to generate the JSONP data we’re going to use.

Create a file called json.xsl with the following contents. I placed mine (on ubuntu-server) in /usr/share/icecast2/web/

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:output omit-xml-declaration="yes" method="text" indent="no" media-type="text/javascript" encoding="UTF-8" />
   <xsl:strip-space elements="*" />
   <xsl:template match="/icestats">
      <!-- <xsl:param name="callback" /> <xsl:value-of select="$callback" /> -->
      parseMusic({
      <xsl:for-each select="source">
         "
         <xsl:value-of select="@mount" />
         ":{"server_name":"
         <xsl:value-of select="server_name" />
         ","listeners":"
         <xsl:value-of select="listeners" />
         ","description":"
         <xsl:value-of select="server_description" />
         ","title":"
         <xsl:if test="artist">
            <xsl:value-of select="artist" />
            -
         </xsl:if>
         <xsl:value-of select="title" />
         ","genre":"
         <xsl:value-of select="genre" />
         ","bitrate":"
         <xsl:value-of select="bitrate" />
         ","url":"
         <xsl:value-of select="server_url" />
         "}
         <xsl:if test="position() != last()">
            <xsl:text>,</xsl:text>
         </xsl:if>
      </xsl:for-each>
      });
   </xsl:template>
</xsl:stylesheet>

Updated: Added SL’s suggestion to the xml:output tag.

If everything’s good, you should be able to go to http://yourserver:port/json.xsl and receive a JSONP string like this:

parseMusic({"/radio":{"server_name":"Amea Radio GR","listeners":"75","description":"Unspecified description","title":"Unknown","genre":"Misc","bitrate":"128","url":"http://amea-radio.gr"}});

In this JSONP you can see the mount point listed as /radio and then the stats. These can be extended to support more data (everything that icecast is able to show about a specific mount point).

Now, to actually make an use of this data, we will use some simple HTML elements and some simple AJAX with jQuery.

The HTML code that interests us:

Listeners: <span id="listeners">00</span>
Current track: <span id="track-title">LIVE</span>

And the javascript code that does all the magic:

function radioTitle() {

    // this is the URL of the json.xml file located on your server.
    var url = 'http://stream.amea-radio.gr:9000/json.xsl';
    // this is your mountpoint's name, mine is called /radio
    var mountpoint = '/radio';

    $.ajax({  type: 'GET',
          url: url,
          async: true,
          jsonpCallback: 'parseMusic',
          contentType: "application/json",
          dataType: 'jsonp',
          success: function (json) {
            // this is the element we're updating that will hold the track title
            $('#track-title').text(json[mountpoint].title);
            // this is the element we're updating that will hold the listeners count
            $('#listeners').text(json[mountpoint].listeners);  
        },
          error: function (e) {    console.log(e.message);  
        }
    });

}

This defines a function called radioTitle() that does all the magic needed.

As for updating the data while the page is opened, we will need to add 2 timers to be run on the webpage every 10 seconds. We will also do this using jQuery, because it’s more convenient

$(document).ready(function () {

    setTimeout(function () {radioTitle();}, 2000);
    // we're going to update our html elements / player every 15 seconds
    setInterval(function () {radioTitle();}, 15000); 

});

And that’s pretty much all the work.

You can see it in action (and play around) on this JSFiddle: http://jsfiddle.net/Znuff/vL5TM/

I will try to extend the json.xsl file whenever I will have the time with all the possible stats, but for now this will work.

Credits for the ideas:

You can see it in action on this page: http://stream.amea-radio.gr/

Exit mobile version