Skip to main content

Station Control (station-control.js)

station-control.js exposes the full scheduler/content agent API under /api/station/*. It is what MCP (mcp-radio.js) and the studio UI talk to, and is the single place where a human or an AI agent can steer the radio: moods, genres, rotation windows, dayparts, singer catalogs, content fetches, and live takeovers.

Mounting

mountStationControl(app, {
radio, // the radio engine bridge
contentPipeline, // the fetch/R128/rundown pipeline
SCHEDULER_CONFIG, // the smart-scheduler defaults
station, // station-config read/write helpers
live, // the LiveManager instance
runContentRefresh, // the full refresh function from server.js
})

Auth

If the STATION_KEY env var is set, every /api/station/* route requires the x-station-key header (or Authorization). Empty/unset = open.

Config persistence

All scheduler knobs live in station-config.json (written via station-config.js) and are re-applied on every boot (and after every PUT /api/station/config). This means an agent can change the vibe and it stays changed across restarts.

Key persisted blocks:

  • schedulermood, rotationBuffer, freshWindow, cooldown, genre split, mashup/sfx chances, weights
  • genres — category → desired percentage (the GENRE_SPLIT)
  • dayparts — window → {weights, hardGenre?} (applied at getDaypart())
  • singers — category → singer catalog (used to prune/reweight the pool)
  • queries — category → trend queries (for the content pipeline)
  • content — caps/purge counts per category
  • live.streams — the LiveManager stream registry (see below)

Key behaviours

  • State snapshotGET /api/station/state returns rotation array, queue depth, per-category deck counts, current config, and live status in one call. This is the "am I healthy?" endpoint and the agent's first call.
  • Mood presetsPUT /api/station/mood with {preset:'chill'|'balanced'| 'party'|'night'|'morning'|'underground'} rewrites the scheduler weights in one shot (or accepts a custom full config object).
  • DaypartsPUT /api/station/dayparts with a daypart object ({daypart:{start,end},window:{weights,hardGenre?}}); POST /api/station/dayparts/clear removes all.
  • Content fetchPOST /api/station/content/fetch with {category,target,kind} runs a targeted fetch via contentPipeline.fetchForCategory; the result goes through R128, into the deck, and the deck is re-shuffled + rotation rebuilt.
  • Rotation rebuildPOST /api/station/rotation/rebuild fully regenerates the on-air rotation from the current config; skip pops the head (keeps cooldown state); play force-injects a chosen track.
  • Config save/resetsave persists the live config; reset deletes station-config.json and returns to built-in defaults.

What it does NOT do

  • It never edits smart-scheduler.js code — it mutates runtime state + the persisted config file only. Live code reload is not supported (restart the node process to pick up code changes).
  • It does not directly stream audio; that is Icecast/Liquidsoap on the remote.

See also