cuddle.fish about disclaimers

All-In-One Web Radio Suite for Icecast/Liquidsoap

My five-years-running project that allows anyone to start a self-hosted web radio in minutes. [2022-11-08]

This is Cadence, my internet radio project that I’ve been building on-and-off since 2017.

cadenceradio.com

Cadence is a software suite for people interested in starting internet radio streams. You can use it to launch a broadcast complete with music search, song request, artwork, and real-time update APIs pre-bundled with stream software.

It’s source-available, free, and can be installed in minutes. You can find a live demo of it on https://cadenceradio.com/, or skip to starting your own broadcast: https://github.com/kenellorando/cadence.

In this post, I’ll teach you what the basic architecture of a hobby web radio looks like and what Cadence can do.

Ideation

In 2017, I was a college student obsessively fascinated with the J-Pop song only my railgun. I wanted to set up a web radio out of my dorm room so I could broadcast it to the world. Seriously. That’s why I did it.

Anyway, this was really more of an experiment to see if I was even capable. I downloaded some stream software to my Windows desktop and after a bit of fiddling around with firewalls, managed to get an actual public broadcast working as confirmed by my friends at another school.

At that time, it was just a direct link to audio data with no web page. Cadence as you see it today is the culmination of years of repeated installations of radio components, piecemeal development of things I thought would be useful, and running different deployments of cadenceradio.com.

Basic Web Radio

Let’s first examine a basic web radio stack.

Components

A basic self-hosted web radio can be composed of two software components:

  1. A source client is a program which prepares local audio data from files on disk or from a live microphone input. The source client transmits this data to a stream server.
    1. You can use a lot of programs as a source client. Historically, Cadence used mpd, foobar2000, even Winamp, before I finally discovered Liquidsoap, which the rest of this article will be covering.
  2. A stream server is a program which receives audio daata from a source client and broadcasts it over a network.
    1. Icecast2 and Shoutcast are two of the most popular choices. Cadence has always used the former.

The setup of these components looks like this:

Basic Operations

In this example, I have a server with audio files on it (e.g. mp3). Liquidsoap is installed as a source client configured to read to the directory containing audio files. It will then forward audio data to the stream server Icecast (installed on the same machine), which will broadcast it to the internet.

To “tune in” to the broadcast, listeners simply connect to the server’s IP address and port.

Both Icecast and Liquidsoap are available on the apt package manager for Linux.

$ sudo apt install icecast2 liquidsoap

Configuration Examples

Let’s look now at some configuration.

Liquidsoap

As a source client, Liquidsoap reads your audio files and generates an audio stream. This audio stream is only available locally at this point, which is to say that listeners on the web do not connect directly to Liquidsoap.

You can configure Liquidsoap with a configuration file (ending in a .liq extension). Here’s an example:

set("server.telnet", true)
default = mksafe(playlist(mode="randomize", "/music/"))
radio = fallback([request.queue(id="request"), default])
output.icecast(%vorbis.cbr(bitrate=192), host="localhost",port=8000,password="hackme", mount="cadence1",radio)
  1. Line 1 enables Liquidsoap’s telnet control interface.
  2. Line 2 configures Liquidsoap to randomly choose an audio file in the configured /music/ directory. When one file finishes playing, another file will be selected at random.
  3. Line 3 enables a request queue. This works like a priority queue. For example, if Liquidsoap is commanded to request a specific song, the requested song will enter a queue that will be “emptied” before it falls back to randomized selection.
  4. Line 4 finalizes the output stream and delivers it to local port 8000. Icecast expects to receive source data on this port. Liquidsoap authenticates with Icecast using the password hackme. It also names this output mount cadence1.

To start Liquidsoap, provide the path to the configuration file as an argument:

$ nohup liquidsoap -t myconfig.liq

Icecast

Icecast is the stream server. It receives a local audio source and broadcasts it on a network. The “local audio” is the stream generated by Liquidsoap.

When audiences want to listen to the stream, they directly connect to the Icecast application.

The Liquidsoap configuration from the above section delivers audio to local port 8000. Icecast is set to listen on this port in the below configuration:

<icecast>
...
    <authentication>
        <!-- Sources log in with username 'source' -->
        <source-password>hackme</source-password>
				...
    </authentication>
    <listen-socket>
        <port>8000</port>
    </listen-socket>
...
</icecast>
  1. The <source-password> block has the value hackme, the same value that Liquidsoap was configured to authenticate with Icecast.
  2. <listen-socket><port> contains value 8000, the port we expect Icecast to monitor for audio.

To start Icecast, provide the path to the configuration file as an argument:

$ nohup icecast -c myconfig.xml

Improvement Ideas

Having personally used a vanilla stack for many the years, I can point out some of the things I felt it lacked to be useful as a radio platform.

No Unified API

The vanilla radio stack components lack HTTP stream data APIs, making it difficult to look things up or control the components programmatically.

Each application provide alternative idiosyncratic methods:

Neither application maintains state on the user’s music library. Liquidsoap picks whatever audio files are available and Icecast broadcasts them. Without knowledge of the possible universe of songs that may be played, you can’t do things like search the library or make song requests.

No Fast Installation

One of the tedious parts of running a vanilla stack is setting it up in the first place.

The setup section above glossed over a lot of the implementation details and dependencies required for the sake of example, but here are a few other bumps you might encounter every time you install a vanilla stack:

No User Interface

Icecast provides an optional browser UI, but it’s rather dated looking and only provides basic playback and song information.

An improved UI could at least provide us with access to new API radio functions such as music library search, song requests, and album cover viewing.

Cadence Re-introduction

Only now with the context of how Liquidsoap/Icecast radios work and an understanding of what they cannot do, we can introduce Cadence again with a deeper appreciation for what it’s actually doing.

Cadence is a web radio API software suite that provides:

  1. a unified HTTP API to control Liquidsoap and Icecast. Cadence monitors the contents of a music directory and keeps state of song metadata into an integrated database. This allows users to library search, song request, and view album artwork.
  2. Liquidsoap and Icecast bundled and pre-configured to work with each other and the Cadence API out of the box. The entire radio stack can be set up in mere minutes in a single command.
  3. a browser UI so radio administrators may share their stations on the web. It’s integrated with the Cadence API and lets audiences search for music and make song requests.

Going forward I’ll be calling any radio stack with a Cadence API a “Cadence stack”. The diagram below illustrates a Cadence stack as the outlined square of components on the right. The Cadence API is the green component labeled “cadence”.

Cadence Radio Stack

Cadence API Features

Song metadata Extraction

One of the few inputs you are required to provide to Cadence at startup is a target directory containing music to play.

When the Cadence API is first launched, the directory you provide will be searched for audio files (.mp3, .flac, .m3a).

Each file will have metadata like title, artist, album, year, plus the file’s absolute path on the server, copied into a database. An additional non-metadata property called ID is set by Cadence so that the first song scanned has {ID: 1} and so on.

Search the Library

A user connected to Cadence may use the /search API to find songs whose title or artist match or contain a given string. Cadence’s database returns a list of results ordered by relevance. Results look like this:

[ { 
	"ID": "1", 
	"Artist": "ZUTOMAYO", 
	"Title": "秒針を噛む", 
	"Album": "潜潜話", 
	"Genre": "J-Rock", 
	"Year": 2019
} ...]

Make a Request

When a user access the song request API, they only need to provide the non-metadata property I mentioned earlier, ID. The entire request POST body could look like this: { "ID": "1" }

Cadence checks the metadata database for the song numbered 1.

If there’s one found, Cadence will read its stored absolute path and initiate a telnet session to the Liquidsoap service. Cadence completes the request using Liquidsoap telnet syntax providing the path of the song, e.g. request.push(/music/秒針を噛む.mp3). When Liquidsoap accepts, the song enters the priority play queue and the request loop has completed.

The Cadence API itself can be optionally configured to rate limit user from requesting songs too often. Administrators could use this to prevent anyone or anything from hogging the entire queue to themselves. After a song request is accepted, Cadence notes the requester’s IP in memory. If the same entity makes another request within the configured timeout duration, the Cadence API will deny the request.

Real-Time Stream Information

Icecast information like the song it is currently playing, the audio stream URL, and the number of connected listeners, is shared through an Icecast file called status-json.xsl.

Cadence will constantly keep watch on that file’s data and make it available through Cadence’s API.

If any changes are detected, it will send relevant data down a special server-sent event API so any connected clients will be notified of the change in real-time. This allows clients to update without needing to continuously poll for new data. This is a convenient feature for any Cadence stack frontend client.

Suppose the radio’s song and artist change. The Cadence API will send out events named title and artist. Here’s an example consuming the EventSource with JavaScript:

let eventSource = new EventSource("/api/radiodata/sse");
eventSource.addEventListener("title", function(event) {
    console.log(event.data);    // "秒針を噛む"
});
eventSource.addEventListener("artist", function(event) {
    console.log(event.data);    // "ZUTOMAYO"
});

Minimal-Touch Containerized Launch

The vanilla stack’s major pain point was the time and effort required to manually set it all up.

A Cadence stack comes with all components are containerized. It’s built to be portable and generally supported on most host systems without additional dependencies.

The containers have multi-architecture support and run as-is on most machines. To my knowledge, I’m not even sure if there are containerized versions of Icecast and Liquidsoap out there, which is why Cadence comes shipped with them custom-built to work with the Cadence API.

When you want to launch the stack, you just need to set a few passwords and a few configuration values, run a single command, and you’re done. Spinning up an entire radio stack from scratch, which used to take me about an hour, is doable with Cadence in minutes.

Browser UI Stations

The Cadence API server also serves a frontend built with raw HTML, CSS, and JavaScript. The UI provides all API functionality asynchonously, so searching/requesting music will not interrupt radio play.

When Cadence is launched, it automatically sets Icecast’s stream URL into the page’s HTML, so the webpage will be configured at launch with its radio. There’s no need for administrators to manually configure the stream source.

Album Artwork Fetching

Cadence handles artwork separately from other metadata on an artwork-specific endpoint. When the UI is notified by a server-sent event of a song change, Cadence’s UI reaches out to this API in a follow-up call.

When the artwork API endpoint receives a request, the Cadence API searches the database for the currently playing song’s file path (e.g. /music/秒針を噛む.mp3). This path is used to extract the album artwork from the file directly. The artwork is returned to the client encoded in base64.

Start a Cadence Radio

All code is available for you to use at https://github.com/kenellorando/cadence.

Installation instructions are provided in the README. I think if you’re somewhat familiar with Docker, you can be up and running in about five minutes.

If you have any problems setting up your Cadence stack, I am more than happy to help!

API Documentation

Container Repositories