ctx->guides->icecast

Using icecast and mpd for web radio on OpenBSD

The goal of this guide is to show a simple yet powerful web radio setup. There is a radio stream server (typically with high bandwidth) and a number of music players that are the main source of music. At most one player can be live at any given moment. The server broadcasts the stream of the live music source while at the same time providing a fallback stream when no source is live. The fallback stream can be an always-on random playlist. This way the radio listeners are transferred to the live stream when it becomes available and back to the fallback when it stops.

Configuring the radio server

First things first, on the radio server you will need:

$ pkg_add icecast mpd mpc

Then, copy the sample configuration file that ships with icecast:

$ cp /usr/local/share/examples/icecast/icecast.xml.dist \
/var/icecast/icecast.xml

Edit the configuration file to your liking, but make sure you include the following, because this is the part that implements the fallback playlist logic. The no-mount option blocks client connections to the fallback stream; so this setup allows listeners only on the /live.ogg mount.

<mount>
    <mount-name>/play.ogg</mount-name>
    <no-mount>1</no-mount>
</mount>
<mount>
    <mount-name>/live.ogg</mount-name>
    <fallback-mount>/play.ogg</fallback-mount>
    <fallback-override>1</fallback-override>
</mount>

To run icecast do /etc/rc.d/icecast start.

Now lets configure the mpd music player to be the never-ending loop playlist on the server that feeds the fallback stream. The important thing in /etc/mpd.conf is the following part:

audio_output {
    type        "shout"
    encoding    "ogg"
    name        "myradio"
    host        "localhost"
    port        "8000"
    mount       "/play.ogg"
    password    "hackme"
    bitrate     "128"
    format      "44100:16:2"
}

To run the music player daemon do /etc/rc.d/mpd start. All is ready at the server side. To automate the starting of programs on boot add the following to your /etc/rc.conf.local configuration:

pkg_scripts="icecast mpd"

Now, simply create playlists using an mpd client like mpc and put the player in random and repeat mode. You can also use a simple 10-second crossfade effect:

$ mpc random on
$ mpc repeat on
$ mpc crossfade 10

To make all these settings persistent accross mpd restarts (and reboots) set the state_file option in mpd.conf:

state_file    "/var/spool/mpd/mpdstate"

By now you should be able to listen to the /play.ogg stream by using the /live.ogg mount point that is located at http://myradio.example.com:8000/live.ogg. You can test it from a client host with mplayer installed like this:

$ mplayer "http://myradio.example.com:8000/live.ogg"

If you experience connectivity problems from the outside world, the easiest way to resolve them is adding the wildcard IP address to the icecast.xml configuration:

<listen-socket>
    <port>8000</port>
    <bind-address>0.0.0.0</bind-address>
</listen-socket>

Otherwise, you can create an additional listen-socket node and specify the IP address of your public network interface.

Configuring the radio sources

A music source only needs a player with icecast support, so simply install mpd and ncmpc, a nice curses mpd client.

$ pkg_add mpd ncmpc

Again, the relevant portion of the configuration file is shown here:

audio_output {
    type        "shout"
    encoding    "ogg"
    name        "myuser@myradio"
    host        "localhost"
    port        "8000"
    mount       "/live.ogg"
    password    "hackme"
    bitrate     "128"
    format      "44100:16:2"
}

Notice that the only difference with the server is the mount point and the name. By convention the name includes the user that is currently live. It is crucial for the bitrate and format options to be the same with the /play.ogg source for the fallback trick to work. Furthermore, you see that the host option is still set to localhost, but no icecast is running on the source machines. This is on purpose, and the idea is to use an ssh tunnel between the radio server and the music source to avoid transmitting plaintext passwords. This is done like this, where myradio.example.com is the hostname of the server:

$ ssh -N -L8000:localhost:8000 myradio.example.com

The above command forwards port 8000 on localhost to port 8000 on the myradio.example.com machine. If you do not want an interactive session you can also use the -N switch, but that's optional. Once the tunnel is created and mpd is in playback state it becomes the live playing source that is streaming on the /live.ogg mount point. No other source can take the lock while the live stream is active.

Final considerations and metadata handling

If all worked-out well so far you have a working radio setup that supports both live broadcasting and a fallback loop playlist. You should only publish the location of the live mount point because listeners that use the fallback mount directly will continue to listen to this stream even if the live one comes up. That is:

http://myradio.example.com:8000/live.ogg

In order to use metadata in other services, icecast provides an XSLT interface to write scripts. For a list of all available options you can put the following code in /var/icecast/web/all.xsl:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="*">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>

This dynamically generated XML document is visible at http://myradio.example.com:8000/all.xsl. For example, if you need a text-only representation of the currently playing song you could use something like the following in a file of its own, and access it likewise:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="/icestats">
<!-- only show the first source -->
<xsl:apply-templates select="source[1]"/>
</xsl:template>

<xsl:template match="source">
<xsl:choose>
<xsl:when test="title or artist">
<xsl:if test="artist"><xsl:value-of select="artist" /> - </xsl:if>
<xsl:value-of select="title" />
</xsl:when>
<xsl:otherwise>Unknown</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

This is a rather complex example that tests the existence of artist and title metadata in order to construct a valid string at all times. The important thing here is the use of the <xsl:value-of select="title" /> construct. Furthermore, the source[1] hack ensures that only one (the first) mount point is selected, so it displays either the live or the fallback stream's information and not both.

Cheers!

lostd@