Work

Unearth Radio

A global radio discovery app with live song recognition. A working prototype across Flutter, Supabase, and Python, paused before release.

Role

Product engineer

Status

Unreleased prototype, paused

Tech

Flutter, Dart, Supabase, PostgreSQL, PostGIS, PGMQ, Riverpod, Python, ffmpeg

Problem

Most radio apps optimise for returning to known stations. I wanted to explore a more discovery-oriented product: browse stations around the world, recognise what is playing, and make exploration itself part of the experience.

Approach

The Flutter app handles browsing, background playback, and free now-playing data from ICY metadata. A Python service keeps the station catalogue in sync with RadioBrowser. Song recognition runs as an async pipeline: the app queues a job in PGMQ, a Python worker captures ten seconds of the stream with ffmpeg and sends it to AudD, and the result reaches the app through Supabase Realtime.

Outcomes

  • A working prototype: browse stations by list or map, listen with background playback, and identify the song playing on a station.
  • The code and architecture documents are public on GitHub.
  • Paused before release. Recognition has a per-request API cost, and I chose to put my time into inkBlox instead of paying for an app with no users yet.

Unearth Radio is the project where I got to design around a cost before spending it. Song recognition is the feature that makes the app interesting, and it is also the one feature that costs money every time someone uses it.

So the app tries the free option first. Many radio streams already broadcast the current track as ICY metadata, and the player reads it directly. Paid recognition only runs when a listener asks for it, which matters on stations that send nothing useful.

The recognition pipeline is deliberately small. Capturing audio and calling an external API is slow and can fail, so it should not sit in a request the app waits on. PGMQ gives me a queue inside the Postgres database the project already has, without running a separate broker. After years of working with Kafka, I know what a larger event platform is good for, and this workload does not need one.

The station catalogue follows the same thinking. The first sync imports everything from RadioBrowser, and later runs fetch only what has changed since the last stored change ID.

Some of the plan is still design rather than code. Recognition results are meant to be cached per station, so one API call can serve everyone listening to the same broadcast, and gamification is meant to reward distance and obscure stations. The schema leaves room for both, but neither is built yet. Right now the worker awards points for a successful recognition.

I paused the project because inkBlox had real users and Unearth Radio did not. Paying for recognition before anyone needed it would have been the wrong trade. I plan to come back and polish it once the inkBlox roadmap is further along.

Screens

Links