Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Radiogram

A live internet radio server written in Rust with nothing but the standard library — no tokio, no MP3 crates, just std::net, std::thread, and a frame parser written against the MPEG spec.

What this is

I built this to understand what actually happens inside a streaming service, instead of importing a library and calling it done.

Each folder of MP3s becomes a station, broadcast live: one thread per station reads the files frame by frame and paces itself to real time, so everyone tuned in hears the same moment of the same song. Tuning in is a one-line TCP handshake; the audio itself arrives as sequence-numbered UDP datagrams, and reordering them is the client's job.

The interesting parts:

  • src/mp3.rs — MPEG frame header parsing straight from the bytes: sync words, ID3 tags, resyncing after garbage.
  • src/server.rs — the fan-out with plain threads, a mutex, and a UDP socket. No async runtime.
  • src/bin/listen.rs — the reorder buffer, with tests for every way packets can misbehave.

No transcoding: whatever's in a station folder has to already be valid MP3.

Running it

Station layout

One folder per station under the root directory (default stations/):

stations/
  classical/
    debussy.mp3
    tchaikovsky.mp3
  chiptune/
    track1.mp3

Folder name = station name. Files play in order and loop. At least one station folder is required.

Locally

cargo run

Binds 0.0.0.0:7878 on both TCP and UDP. Configuration:

Variable Default What it does
RADIO_ADDR 0.0.0.0:7878 address to bind (tcp + udp)
STATIONS_DIR stations root folder of station subfolders

To check a station without a real client:

cargo run --bin listen classical | mpg123 -

Docker

docker build -t radiogram .
docker run -d -p 7878:7878/tcp -p 7878:7878/udp \
  -v /path/to/your/stations:/app/stations radiogram

Station folders are mounted at runtime, not baked into the image.

Browser playback

Browsers can't speak the protocol directly, so example/go-client/ is a small Go bridge that re-serves stations as plain HTTP audio, with a web player and a live stats page.

cd example/go-client
go run . -radio 127.0.0.1:7878

Then open http://localhost:8090.

The protocol

One line over TCP to tune in, audio over UDP. The Go bridge and the Rust listen client are both reference implementations.

List stations — send LIST\n, get one name per line, connection closes:

classical
chiptune
noise

Tune in — send the station name as a line:

classical\n

Reply is OK classical 7\n (the number is your token) or ERR unknown station 'classical'\n. Keep the connection open — it never carries audio, it just tells the server you're still listening. Closing it tunes you out.

Hello over UDP — send the token as ASCII in a single datagram to the same host and port, from the socket you want audio on. This is how the server learns your address and ties it to your registration.

Receive — audio then arrives as:

[4-byte big-endian sequence number][one MP3 frame]

One frame per packet, small enough to avoid fragmentation. The sequence number is shared by every listener of a station.

Reorder — packets that arrive early wait in a small buffer until the gap before them fills in. If it hasn't after ~8 out-of-order packets, the missing one is presumed lost and skipped; if it shows up later anyway, it's dropped. See ReorderBuffer in src/bin/listen.rs or the Go version in example/go-client/main.go.

About

A live mp3 streaming radio server written in Rust with nothing but the standard library.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages