DEV Community

AngryBeat
AngryBeat

Posted on

How Internet Radio Streaming Actually Works (and How to Build a Simple Radio Player)

If you have ever clicked play on a live radio station in your browser, you have used one of the oldest and most durable streaming setups on the web. It is less glamorous than video streaming, but it is a great way to learn how audio moves across the internet. This post walks through the moving parts and ends with a tiny player you can build in plain HTML and JavaScript.

What happens between the studio and your speakers?

A typical internet radio station has four pieces:

  1. The source. Playout software or a live mixer produces audio in the studio.
  2. The encoder. The audio is compressed into a format such as MP3, AAC, or Opus at a chosen bitrate, often 64 to 192 kbps.
  3. The streaming server. The encoder pushes the compressed audio to a server, commonly Icecast or SHOUTcast, which fans it out to every connected listener.
  4. The client. Your browser, app, or smart speaker opens an HTTP connection and plays the bytes as they arrive.

The interesting part is that classic Icecast streams are just a long running HTTP response. The server never "finishes" sending the file. Your client reads a continuous flow of audio frames and decodes them on the fly.

Why does Icecast use plain HTTP instead of something fancier?

Simplicity. Any HTTP client can request the stream, proxies and CDNs understand it, and the browser <audio> element can play it directly. The server sends headers such as Content-Type: audio/mpeg, and optional icy- headers carry the station name and genre.

If the client sends the request header Icy-MetaData: 1, the server interleaves small metadata blocks (like the current song title) into the audio at a fixed interval announced in the icy-metaint header. Players strip those blocks out and show "Now playing" text. Browsers do not expose that metadata to the <audio> element, which is why many web players fetch the current track from a separate JSON status endpoint instead.

What about HLS and segmented streaming?

Many large broadcasters also offer HLS (HTTP Live Streaming). Instead of one endless response, the audio is cut into short segments, and the player repeatedly downloads a playlist (.m3u8) that lists the newest ones. HLS scales nicely through CDNs and supports adaptive bitrates, but it adds latency because the player buffers a few segments before starting. Icecast style streams usually start faster and stay closer to live.

Which codec should a station choose?

  • MP3 plays everywhere and is still the safe default.
  • AAC and HE-AAC sound better at low bitrates, which helps mobile listeners.
  • Opus is excellent quality per bit and is well supported in modern browsers, though some older devices and car systems lag behind.

Lots of stations publish more than one stream so clients can pick what they support.

How do you build a minimal radio player?

Here is a small player that loads a stream URL, handles play and pause, and reports errors. Replace the URL with a stream you have permission to use.

<audio id="radio" preload="none"></audio>
<button id="toggle">Play</button>
<p id="status">Stopped</p>

<script>
  const STREAM_URL = "https://example.com/live.mp3";
  const audio = document.getElementById("radio");
  const btn = document.getElementById("toggle");
  const status = document.getElementById("status");

  btn.addEventListener("click", async () => {
    if (audio.paused) {
      audio.src = STREAM_URL;
      try {
        await audio.play();
        btn.textContent = "Pause";
        status.textContent = "Live";
      } catch (err) {
        status.textContent = "Could not start: " + err.message;
      }
    } else {
      audio.pause();
      audio.removeAttribute("src");
      audio.load();
      btn.textContent = "Play";
      status.textContent = "Stopped";
    }
  });

  audio.addEventListener("waiting", () => status.textContent = "Buffering...");
  audio.addEventListener("playing", () => status.textContent = "Live");
  audio.addEventListener("error", () => status.textContent = "Stream error");
</script>
Enter fullscreen mode Exit fullscreen mode

Two details matter here. First, preload="none" stops the browser from opening the stream before the listener asks for it, which saves bandwidth for you and the station. Second, on pause we clear the src and call load(). With live radio you do not want to resume from a stale buffer; you want to reconnect to the live edge.

What are the common gotchas?

  • Mixed content. An HTTPS page cannot load an http:// stream in most browsers. Use HTTPS stream URLs.
  • Autoplay rules. Browsers block audio until the user interacts with the page, so always start playback from a click.
  • CORS. Plain playback through <audio> does not need CORS, but if you route audio through the Web Audio API for visualizers, the server must send the right headers.
  • Reconnects. Mobile networks drop. Listen for error and stalled events and retry with a short backoff.
  • Respect the source. Only embed streams you are allowed to use, and credit the station.

Where can you find streams to explore?

If you want to hear how real stations handle bitrates, codecs, and buffering, a directory is the easiest place to start. AngryBeat lets you listen to free live internet radio from stations around the world in the browser, which is a handy way to compare how different streams behave before building your own player.

FAQ

Is internet radio the same as a podcast?
No. A podcast is a finished file you download. Internet radio is a live stream that never ends.

What bitrate is good enough?
For speech, 64 kbps AAC is fine. For music, 128 kbps MP3 or 96 kbps AAC is a common sweet spot.

Can I show the current song in a web player?
Not directly from the <audio> element. Most players poll a JSON status endpoint provided by the stream server.

Icecast or HLS?
Icecast for simplicity and low latency, HLS for large scale CDN delivery and adaptive bitrate.

Top comments (0)