Your own AI-powered radio station β live, personalised, and always on.
Demo Β· Features Β· Setup Β· Usage Β· Configuration
AI Radio Station turns your Spotify playlists into a real radio experience. An AI host introduces songs, delivers weather and news updates, and keeps the energy going between tracks β all spoken aloud by a natural-sounding voice. Fully configurable via a single YAML file.
Define a host persona with a name, tone, and style. The AI generates contextual on-air commentary β song intros, time checks, energy bursts β that sounds like a real radio DJ, not a robot.
Plays music from your own Spotify playlists and shuffles between them. No separate audio setup required β it works with any active Spotify device or can register directly as a Spotify Connect device on your machine.
All AI-generated commentary is synthesised with ElevenLabs, giving your host a consistent, lifelike voice. Choose any voice from the ElevenLabs library and swap models for different quality/latency tradeoffs.
The built-in weather tool fetches real-time conditions for any location using the Open-Meteo API (no key required). The host weaves current temperature, wind, and sky conditions into natural-sounding updates on a schedule you control.
Configure any RSS feed (BBC, Tagesschau, or your own) and the host will summarise the latest headlines in a conversational way β no verbatim reading, just the vibe of a real news break.
Group your playlists by genre or mood and have the station rotate through them one block at a time. Each group plays for a configured duration or number of songs, then the host delivers a narrated transition into the next genre. Groups play in random order and loop indefinitely.
When the host speaks over music, the volume fades down automatically and fades back up when the segment ends β exactly like a real radio broadcast. Requires the librespot Spotify Connect device; falls back to pause/resume without it.
Control when and how often the host speaks with a simple trigger system:
| Trigger | Description |
|---|---|
show_start |
Fires once when the station launches |
show_end |
Fires once when the station stops |
before_song |
Fires before a new track plays (with song name & artist) |
after_song |
Fires after a track ends |
interval_minutes |
Fires on a repeating timer (e.g. every 30 minutes) |
group_transition |
Fires between genre/group blocks (with {{group.name}} and {{group.next_name}}) |
Set a language code and pair it with a multilingual ElevenLabs model to run your entire show in any language.
Supports Anthropic, OpenAI, and Google (Gemini) out of the box. Switch providers with a single line in your config.
Connect to any Model Context Protocol server over HTTP/SSE to pull live data into your segments. Define servers once in your config, then reference individual tools per segment β the result is injected into your prompt as {{mcp.<server>.<tool>}}.
On macOS, the station integrates natively with the operating system:
- Now Playing widget β current song, artist, and album art appear in the macOS media controls (Control Centre, lock screen, and Touch Bar). Pause, resume, and skip work from any standard media key or the widget itself.
- Menu bar icon β a βͺ icon in the menu bar gives quick access to all controls without switching to the terminal: pause/resume, skip song, switch genre group or playlist, trigger any segment, and quit.
Requires the native helper binary β see Setup step 2 for build instructions.
Control playback from the terminal while the show runs:
| Key | Action |
|---|---|
Space |
Pause / Resume |
N |
Skip to next song |
S |
Transition to next song or segment |
H |
Show help |
Ctrl+C |
Stop the show |
See the project in action with this quick demo of a show running with a mix of segments, including song intros, weather updates, and genre transitions (sound on π):
ai-radio-station.mp4
In the demo the segments were triggered manually via the interactive controls for demonstration purposes, but in a real show you'd rely on the automatic triggers (e.g. before_song, interval_minutes, etc.) to schedule everything.
- Bun runtime
- A Spotify account with a registered app (free)
- An ElevenLabs API key
- An API key for your chosen AI provider (Anthropic / OpenAI / Google)
- (Optional) librespot for audio ducking:
brew install librespot - (Optional, macOS only) Xcode Command Line Tools for the Now Playing widget and menu bar:
xcode-select --install
git clone https://github.com/BetaHuhn/ai-radio-station
cd ai-radio-station
bun installThis compiles the Swift helper that powers the Now Playing widget and menu bar:
bun run build:nativeThe binary is written to bin/NowPlayingHelper. If you skip this step the station runs fine β you just won't get the macOS integrations.
Create a .env file in the project root:
# Spotify β from https://developer.spotify.com/dashboard
SPOTIFY_CLIENT_ID=your_spotify_client_id
# ElevenLabs β from https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=your_elevenlabs_api_key
# AI provider β set the key for whichever provider you choose
ANTHROPIC_API_KEY=your_anthropic_api_key
# OPENAI_API_KEY=your_openai_api_key
# GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_keyWhen registering your Spotify app, add
http://localhost:8888/callbackas a Redirect URI.
bun run authThis opens a browser for OAuth and saves tokens to ~/.ai-radio/spotify-tokens.json.
Copy the example config and customise it:
cp show.example.yaml show.yamlEdit show.yaml to set your host name, tone, playlists, AI provider, ElevenLabs voice, and segments. See Configuration below.
# Start the show
bun start
# Start with a custom config file
bun start --config my-show.yaml
# List available Spotify Connect devices
bun start --list-devices
# Target a specific device
bun start --device "Living Room Speaker"All show settings live in show.yaml. Here's a quick overview of the main sections:
show:
name: "Morning Drive" # Show name used in prompts
host: "Alex" # Host name used in prompts
tone: "upbeat, energetic" # Tone used in prompts
style: "morning drive radio" # Style used in prompts
language: "en" # BCP 47 language code
location: "New York, US" # Location for weather tool
news_feed: "https://..." # RSS feed URL for news tool
connect_device_name: "AI Radio Station" # librespot device name
segment_audio: "duck" # "duck" or "pause"
duck_volume: 15 # Volume % while host is speaking
ai:
provider: "anthropic" # anthropic | openai | google
model: "claude-sonnet-4-6"
elevenlabs:
voice_id: "..." # From elevenlabs.io/app/voice-library
model_id: "eleven_turbo_v2_5"
spotify:
# Flat playlist mode
playlists:
- id: "..."
name: "My Playlist"
# Or group playlists by genre/mood for block rotation
groups:
- id: country
name: Country Roads
transition_after_minutes: 20 # switch after 20 minutes β¦
transition_after_songs: 8 # β¦ or after 8 songs (whichever fires first)
playlists:
- id: "..."
name: "Dirt Roads"
- id: pop
name: Pop Hits
transition_after_minutes: 15
playlists:
- id: "..."
name: "Top Pop"
segments:
- id: "intro"
trigger: "show_start"
prompt: >
Kick off {{show.name}} with a short energetic intro...| Trigger | Description |
|---|---|
show_start |
Fires once at the start of the show |
show_end |
Fires once at the end of the show |
before_song |
Fires before a new song starts (has access to {{track.name}} and {{track.artist}}) |
after_song |
Fires after a song ends (has access to {{track.name}} and {{track.artist}}) |
interval_minutes |
Fires at regular time intervals (e.g. every 30 minutes, has access to {{time}}) |
group_transition |
Fires between genre/group blocks when using spotify.groups (has access to {{group.name}} and {{group.next_name}}) |
For before_song and after_song triggers, you can specify an interval to control how often the segment fires. For example, interval: 4 means the segment will fire every 4 songs.
For interval_minutes, specify the number of minutes between firings with interval_minutes: 30 to fire every 30 minutes.
Segments can optionally use tools to fetch live data. Just add a tools list to the segment config:
- id: "update"
trigger: "interval_minutes"
interval_minutes: 30
tools:
- name: weather
location: "Los Angeles, US"
- name: news
url: "https://feeds.bbci.co.uk/news/world/rss.xml"
count: 3
prompt: >
Give a quick weather update for {{weather.location}} β right now it's {{weather.temperature}}Β°C and {{weather.description}}.
Also, summarise the latest news: {{news.headlines}}.Built-in tools include:
weather: Fetches current weather conditions for the show's configured location using the Open-Meteo API.news: Fetches the latest headlines from the show's configured RSS feed.
You can connect to any MCP server that exposes an SSE endpoint and call its tools from segments. Declare your servers in the top-level mcp block, then reference them in a segment's tools list:
mcp:
servers:
- name: my-server # Unique name used to reference this server
url: http://localhost:3000/sse
segments:
- id: "tech_update"
trigger: "interval_minutes"
interval_minutes: 45
tools:
- mcp: my-server
tool: get_headlines
arguments:
category: technology
count: 3
prompt: >
Here are the latest tech headlines: {{mcp.my-server.get_headlines}}
Summarise them in a casual, radio-friendly way.The tool result is available as {{mcp.<server-name>.<tool-name>}} in the segment prompt.
You can also built and integrate your own custom tools by specifying a TS file to run which exports a default function that returns data to be used in prompts. See tools/weather.ts for an example.
To use a custom tool, add it to the tools list for a segment and specify the path to the tool file:
- id: "custom_update"
trigger: "interval_minutes"
interval_minutes: 30
tools:
- path: ./my-tool.ts
prompt: >
Here's a custom update: {{custom_data.value}}Prompts support template variables that are resolved at runtime:
| Variable | Available in |
|---|---|
{{show.name}}, {{host}}, {{tone}} |
All triggers |
{{track.name}}, {{track.artist}} |
before_song, after_song, group_transition |
{{time}} |
interval_minutes |
{{group.name}}, {{group.next_name}} |
group_transition |
{{weather.*}} |
Segments with tools: [weather] |
{{news.source}}, {{news.headlines}} |
Segments with tools: [news] |
{{mcp.<server>.<tool>}} |
Segments with an MCP tool entry |
bun start [options]
Options:
--config <path> Path to show YAML config (default: show.yaml)
--auth Authenticate with Spotify
--list-devices List available Spotify Connect devices
--device <name> Target a specific Spotify Connect device
--help, -h Show help
