Skip to main content

Architecture

This page describes how the pieces fit together end-to-end, from a fresh YouTube download to a listener's browser, including the live takeover path.

System diagram

LOCAL NODE (Windows, C:\Users\omar\radionew)
┌───────────────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌───────────────────────────┐ ┌──────────────────┐ │
│ │ yt-dlp │ │ content-pipeline.js │ │ smart-scheduler │ │
│ │ discovery │────▶│ sanitize / loudness / │─────▶│ .js │ │
│ │ (YouTube…) │ │ registry / purge │ │ Dirichlet + RL │ │
│ └──────────────┘ └───────────────────────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ downloads/ ──▶ media/music/… (E:/radionew) ──▶ playlists/*.m3u │
│ │
│ ┌────────── social-platform.js ──▶ scheduler overlay (ranked posts) │
│ │ live-manager.js ─────▶ restream winner ➜ harbor :8006 │
│ │ imaging-engine.js ───▶ sweepers / IDs / stingers (R128) │
│ │ fun-jingles.js ──────▶ karaoke jingles + sound FX │
│ └─────── station-control.js ─────▶ /api/station/* (MCP front door) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ radio-engine.js (sync) HTTP-pull ➜ curl │ │
│ │ /api/sync/media?token= (one ssh control) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ server.js (Socket.io + HTTP API) ──▶ web frontends (public/, radio-web/) │
└───────────────────────────────────────────────────────────────────────────────┘
│ HTTP pull (curl, media + playlists)

PRODUCTION NODE (Ubuntu, 10.10.8.230 /home/sms/radio)
┌────────────────────────────────────────────────────────┐
│ Liquidsoap (radio_worker.liq) │
│ │ ducking: live mic :9001 (music → 5%) │
│ │ switch : channel_live harbor :8006 (WHIP/restream)│
│ └──▶ Icecast :8005 (OGG) ──▶ ffmpeg bridge │
│ └──▶ MediaMTX :19351 HLS │
│ whip_server.py :8089 (go-live.html + /whip-info) │
│ FIFO whep_live.wav → ffmpeg → harbor :8006 │
│ chat-server.js :8094 (long-poll chat + presence) │
│ mmtx_listeners_proxy.py :19353 (listener count) │
│ radio-web :4477 (Next.js player) │
│ video_feed/ (LOKLOK TV: ffmpeg renderer + ticker) │
└────────────────────────────────────────────────────────┘

files.mediahubnetwork.net/main (OGG)
api.mediahubnetwork.net/loklok/index.m3u8 (HLS, same-origin)
files.mediahubnetwork.net:19351/loklokvideo/index.m3u8 (video)
files.mediahubnetwork.net:4477 (player)
api.mediahubnetwork.net/docs (docs)


LISTENERS / PLAYERS

Component responsibilities

1. Content discovery & download

yt-dlp fetches tracks into downloads/ using per-category manifests. A download archive (yt_download_archive.txt) prevents re-downloading the same video. The extractor is pinned to YouTube's android/web clients to dodge throttling/403s.

2. Sanitization & registry

The pipeline re-derives clean {artist, title} pairs from the embedded ID3 TIT2 tag (yt-dlp stores the raw YouTube video title), stripping suffixes like | ..., /, and [Official ...]. Categories are validated against curated singer catalogs and a junk gate (BAD_TITLE/REJECT_TITLE) that bans live/concert/show/news/paparazzi material, lyric/cover/karaoke uploads, animation/stock channels, TV promos — and the entire mahraganat category (genre terms + a mahraganat-artist blocklist, because most mahragan titles carry no genre word). Since 2026-08-11 a dedicated _BAD_TERMS blocklist bans AI-generated music at every gate (yt-dlp --reject-title pre-download, post-download BAD_TITLE, scrubCategory, deriveMeta re-check): ai-generated|ai song|suno|udio|melovox|ميوزك ليو|MUSIC L3O|حمو القماش|Qomash|vibeluxe|…. Results are cached in meta_registry.json.

3. Loudness normalization

Every asset passes through ffmpeg loudnorm targeting EBU R128: −14 LUFS, −1.5 dBTP, 11 LRA. Playlists therefore never have jarring level jumps between a whispered jingle and a loud song.

4. Scheduling

smart-scheduler.js consumes the registry and category manifests to build M3U playlists. It uses:

  • Dirichlet sampling for category mix (never a pure random shuffle).
  • Energy sequencing — tags each track with an energy 1–5 and avoids jarring transitions.
  • 90-minute freshness window per track for the lookahead queue, plus a stable 120-track on-air rotation rebuilt only at 85% consumption (see Anti-repeat & M3U rotation) — nothing repeats on the hour. Anti-repeat state (cooldowns, song-keys, artist spacing, recent on-air) is persisted to scheduler_antirepeat.json so a server restart never forgets what just played.
  • Adjacency constraints — different songs by the same artist stay apart.
  • Thompson-sampling RL arms — CHILL / BALANCED / PARTY moods that re-weight the category distribution based on accumulated reward.
  • Imaging elements (jingles/sweepers/ads/sfx/shorts) recycle every 10 minutes and are hard-cut by the transition logic. Since 2026-08-10 the sweepers, station IDs and stingers are synthesized by imaging-engine.js; since 2026-08-11 karaoke jingles + sound FX come from fun-jingles.js.
  • Rotation varietypopFrom picks the least-recently-aired eligible candidate (never-aired tracks win; tie-break = oldest in library), so rebuilds don't re-recycle the newest few tracks once their fresh window expires. The music fresh window is MUSIC_FRESH_MS = 3 h (matches COOLDOWN_PERIOD).

5. Social overlay

social-platform.js takes listener-submitted audio (song/mashup/ads/jingles/ sweepers/sfx), validates + R128-masters + dedupes it, and injects the top-ranked posts as a scheduler overlay ahead of the rolling library. sync pushes the updated rotation to the remote.

6. Sync to production

radio-engine.js pushes playlists and media to the Ubuntu broadcast node over HTTP pull — one ssh per file runs a remote curl against the token-gated /api/sync/* endpoints; sizes are verified and stale fingerprints re-upload (see Media sync).

7. Broadcast

On the Ubuntu node, Liquidsoap reads the generated M3Us, applies a 2-second music crossfade (hard cuts for imaging/shorts), and streams to Icecast. A WHIP server (whip_server.py:8089) accepts mobile browser mic streams, feeds them into a FIFO, and ffmpeg restreams the winner into the channel_live harbor (:8006) where Liquidsoap's switch puts it on air over the mix. MediaMTX transmuxes the master OGG into HLS for the player app. A second MediaMTX path, loklokvideo, carries the branded 1080p video program: an ffmpeg renderer composites the audio's spectrum + logo + lower-third + news ticker and pushes it to MediaMTX (see LOKLOK TV).

8. Delivery & frontends

  • Classic frontends (public/) connect over Socket.io for live metadata, chat, studio injection, voting, and now-playing updates.
  • The Next.js player plays the HLS stream (…/loklok/index.m3u8) with OGG fallback.
  • The PWA (html/index.html, served by the Go server at api.mediahubnetwork.net) is the installable radio player: HLS + OGG fallback, artist-backdrop hero, push notifications, offline shell, live chat + listener presence (via the long-poll chat server :8094).
  • The Flutter app (~/loklok_radio_app, built remotely with Flutter 3.44) plays the HLS stream through media_kit (mpv) and adds a premium client: live now-playing + listener counts, a full on-air queue, chat, and vibe voting — see Flutter app.

Control & agent API

  • station-control.js mounts /api/station/* — the agent-facing API over the scheduler (moods, genres, dayparts, singers, rotation, live).
  • mcp-radio.js is an MCP (Model Context Protocol) stdio server that wraps those endpoints for any AI agent.
  • live-manager.js ranks live streams and restreams the winner into the harbor.
  • station-config.json persists config overrides across restarts.

Paths & configuration

All filesystem roots are centralized in paths.js and are environment-overridable:

KeyDefaultPurpose
RADIO_ROOTC:/Users/omar/radionewProject root
MEDIA_ROOTE:/radionew/mediaLibrary (music, sweepers, ads…)
LOG_ROOTE:/radionew/logsRuntime logs
MODEL_ROOTE:/radionew/modelsML models
PLAYLIST_ROOTE:/radionew/playlistsGenerated M3Us
ARCHIVE_ROOTE:/radionew/pro_archiveBackup archives
DOWNLOAD_ROOTE:/radionew/downloadsFresh downloads

Environment & host inventory

HostRoleAddress / Credentials
Local dev (Windows)Source, pipeline, scheduler, API10.40.3.174, SSH omar@10.40.3.174 (Windows OpenSSH)
Prod broadcast (Ubuntu)Liquidsoap / Icecast / MediaMTX / WHIP / chat10.10.8.230, SSH sms@10.10.8.230
Public stream (master)Icecast mountfiles.mediahubnetwork.net/main (OGG)
Public stream (HLS)MediaMTX via ffmpeg bridgeapi.mediahubnetwork.net/loklok/index.m3u8 (same-origin cookie flow; :19351 is the raw MediaMTX port)
Listener countMediaMTX listener proxy (read-only)10.10.8.230:19353/v3/paths/list
Live ingestWHIP/WHEP server + go-live page10.10.8.230:8089 (/whip-info, /go-live)
Web / APISocket.io + frontendsapi.mediahubnetwork.net
Web player / PWAGo server staticapi.mediahubnetwork.net (PWA) / files.mediahubnetwork.net:4477 (Next.js)
Chatlong-poll chat + presencefiles.mediahubnetwork.net:8094