-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Grimnir Radio uses a modern, scalable architecture designed for broadcast automation with multi-instance support.
┌─────────────────────────────────────────────────────────────┐
│ Control Plane Cluster │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │
│ │ (Leader) │ │ (Follower) │ │ (Follower) │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
└─────────────────────────┼─────────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌────────────┐
│PostgreSQL │ │ Redis │ │Media Engine│
│ Primary │ │Event Bus │ │ (gRPC) │
└───────────┘ └───────────┘ └─────┬──────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ GStreamer │
│ │ Pipelines │
│ └──────┬───────┘
│ │
└──────────────────────────────┼─────────┐
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│Icecast2 │ │ S3/ │
│Streams │ │Storage │
└─────────┘ └─────────┘
The main application binary handling all business logic and API requests.
Responsibilities:
- HTTP REST API - Station, media, schedule management
- Scheduler - Generates schedule entries from clocks/smart blocks
- Executor - Per-station goroutines managing playback state
- Priority System - 5-tier priority ladder for content sources
- Event Bus - Pub/sub for distributed coordination
- Authentication - JWT-based auth with RBAC
- WebSocket - Real-time event streaming to clients
Key Services:
internal/
├── api/ # HTTP handlers
├── scheduler/ # Schedule generation
├── executor/ # Playback execution
├── priority/ # Priority management
├── smartblock/ # Rule-based playlists
├── auth/ # Authentication
└── eventbus/ # Event distributionSeparate process handling all audio processing via GStreamer.
Responsibilities:
- gRPC Server - Control interface (port 9091)
- Pipeline Management - GStreamer pipeline lifecycle
- DSP Processing - Loudness, AGC, compression, limiting
- Crossfades - Cue-aware seamless transitions
- Live Input - Harbor-style DJ input with handover
- Telemetry - Real-time audio levels, LUFS, underruns
Why Separate Process?
- Isolation: GStreamer crashes don't affect control plane
- Resource Limits: Can constrain CPU/memory per engine
- Scaling: Run multiple engines for different stations
- Development: Restart media engine without losing API state
Supported Databases:
- PostgreSQL (recommended) - Full features, best performance
- MySQL 8+ - Alternative for existing MySQL infrastructure
- SQLite - Development and single-instance deployments
Data Models:
stations # Station configuration
media_items # Media library metadata
smart_blocks # Rule-based playlist definitions
clocks # Hour templates
schedule_entries # Materialized schedule
mounts # Output stream endpoints
users # User accounts (JWT auth)
priority_sources # Active priority sources
executor_states # Per-station playback state
Purpose: Distributed coordination for multi-instance deployments
Implementations:
- In-Memory - Single instance, development
- Redis Pub/Sub - Multi-instance, simple setup
- NATS JetStream - Multi-instance, enterprise-grade
Event Types:
-
schedule_update- New schedule entries generated -
priority.emergency- Emergency content inserted -
priority.override- Manual override activated -
live.handover- DJ handover initiated -
webstream.failover- Webstream source failed over -
dj_connect/dj_disconnect- DJ connection events -
now_playing- Track metadata updates
Media File Storage:
Option A: Filesystem
/media/
├── station-1/
│ ├── media-id-1/
│ └── media-id-2/
└── station-2/
└── media-id-3/
Option B: S3-Compatible
- AWS S3
- MinIO (self-hosted)
- DigitalOcean Spaces
- Backblaze B2
- Wasabi
Configuration:
# Filesystem
GRIMNIR_MEDIA_ROOT=/var/lib/grimnir-radio/media
# S3
GRIMNIR_S3_BUCKET=my-media
GRIMNIR_S3_REGION=us-east-1
GRIMNIR_S3_ACCESS_KEY_ID=...
GRIMNIR_S3_SECRET_ACCESS_KEY=...1. Clock Definition (Admin)
↓
2. Scheduler (Every 30s tick)
- Looks ahead 48 hours
- Compiles clock slots into schedule entries
↓
3. Smart Block Materialization
- Executes rules against media library
- Selects tracks matching criteria
↓
4. Schedule Entry Creation
- Writes entries to database
- Publishes schedule_update event
↓
5. Executors Receive Event
- Update internal schedule cache
- Preload next track if needed
1. Executor Monitors Schedule
- Checks for upcoming entries (5 min lookahead)
↓
2. Executor Sends Play Command
- Checks priority system for preemption
- Sends gRPC LoadGraph + Play to media engine
↓
3. Media Engine Processes Audio
- Loads media file (filesystem or S3)
- Applies DSP graph (loudness, AGC, etc.)
- Crossfades with previous track (if configured)
↓
4. Stream Output
- Encodes to MP3/AAC/OGG (configurable)
- Sends to Icecast2 mount point
↓
5. Telemetry Feedback
- Media engine streams telemetry via gRPC
- Executor updates state (levels, position, metadata)
- WebSocket clients receive now_playing events
Priority Tiers (highest to lowest):
0. Emergency - Station alerts, breaking news
1. Live Override - Manual DJ takeover
2. Live Scheduled - Scheduled live shows
3. Automation - Regular scheduled content
4. Fallback - Dead air prevention
Priority Change Flow:
1. Priority Source Inserted
- API call or scheduled event
↓
2. Priority Service Evaluates
- Compares new priority to current
- Determines if preemption needed
↓
3. Event Published (if preemption)
- priority.emergency or priority.override
↓
4. Executors Receive Event
- Trigger crossfade to new source
- Update internal state
↓
5. Media Engine Switches
- Fades out current track
- Routes new source through DSP
↓
6. Priority Source Released
- When completed, publishes priority.released
- Executor returns to previous priority tier
┌─────────────────┐
│ grimnirradio │──── Local filesystem
└────────┬────────┘
│
┌────┴─────┐
│ SQLite DB│
└──────────┘
- In-memory event bus
- SQLite database
- Local filesystem storage
- No external dependencies
┌─────────────────┐
│ grimnirradio │──── S3 (optional)
└────────┬────────┘
│
┌────┴─────┬──────────┐
│PostgreSQL│ Redis │
└──────────┴──────────┘
- Single control plane instance
- PostgreSQL for reliability
- Redis for future scaling
- S3 for distributed media access
┌────────┐ ┌────────┐ ┌────────┐
│ API #1 │ │ API #2 │ │ API #3 │
│(Leader)│ │ │ │ │
└───┬────┘ └───┬────┘ └───┬────┘
│ │ │
└───────────┴───────────┘
│
┌───────────┴───────────┬──────────┐
│ │ │
┌───▼────┐ ┌───────▼───┐ ┌──▼────┐
│Postgres│ │ Redis │ │ S3 │
│Replica │ │ Cluster │ │Bucket │
└────────┘ └───────────┘ └───────┘
- Load Balancer (nginx/haproxy) distributes HTTP requests
- Leader Election via Redis - only leader runs scheduler
- Executor Distribution - stations hashed to specific instances
- Redis Cluster for event bus reliability
- S3 for shared media access across instances
- PostgreSQL Replication for database HA
JWT-Based:
Client Control Plane
│ │
├──── POST /auth/login ────▶
│ │
◀─── JWT Token ───────────┤
│ │
├──── API Request ────────▶
│ (Authorization: Bearer TOKEN)
│ │
◀─── Response ────────────┤
Roles:
- Admin: Full system access
- Manager: Station management
- DJ: Live broadcasting only
Recommended Firewall Rules:
# Public (clients)
80/tcp - HTTP redirect to HTTPS
443/tcp - HTTPS (API)
8000/tcp - Icecast2 streams
# Internal (between services)
5432/tcp - PostgreSQL (control plane ↔ database)
6379/tcp - Redis (control plane ↔ Redis)
9091/tcp - gRPC (control plane ↔ media engine)
TLS Configuration:
- API: Use reverse proxy (nginx/traefik) for TLS termination
- Database: Enable SSL connections (
sslmode=require) - Redis: Use AUTH password
- Icecast: Enable HTTPS for streams
Indexes:
-- Schedule lookups
CREATE INDEX idx_schedule_station_time ON schedule_entries(station_id, starts_at);
-- Media queries
CREATE INDEX idx_media_station ON media_items(station_id);
CREATE INDEX idx_media_genre ON media_items(genre);
-- Smart block rules
CREATE INDEX idx_smart_blocks_station ON smart_blocks(station_id);Connection Pooling:
GRIMNIR_DB_MAX_OPEN_CONNS=25
GRIMNIR_DB_MAX_IDLE_CONNS=10
GRIMNIR_DB_CONN_MAX_LIFETIME=300sBuffer Configuration:
MEDIAENGINE_BUFFER_SIZE=2097152 # 2MB buffer
MEDIAENGINE_BUFFER_MIN=65536 # 64KB minimum
MEDIAENGINE_BUFFER_MAX=10485760 # 10MB maximumCPU Affinity:
- Pin media engine to specific CPU cores for consistent performance
- Avoid CPU core sharing with control plane
Memory Management:
maxmemory 256mb
maxmemory-policy allkeys-lru
Persistence:
save "" # Disable RDB snapshots
appendonly yes # Enable AOF for events
appendfsync everysec # Balance durability/performance
Exposed at: http://localhost:9000/metrics
Key Metrics:
grimnir_scheduler_generation_duration_seconds
grimnir_executor_state_changes_total
grimnir_media_engine_connection_status
grimnir_api_request_duration_seconds
grimnir_db_query_duration_seconds
grimnir_event_bus_publishes_total
grimnir_priority_transitions_total
Enable:
GRIMNIR_TRACING_ENABLED=true
GRIMNIR_OTLP_ENDPOINT=localhost:4317
GRIMNIR_TRACING_SAMPLE_RATE=0.1Traces:
- API request spans
- Schedule generation spans
- Smart block execution spans
- gRPC call spans
Structured JSON Logs:
{
"level": "info",
"time": "2026-01-23T03:00:00Z",
"component": "scheduler",
"station_id": "abc123",
"message": "schedule generation complete",
"duration_ms": 45
}Log Levels:
-
debug: Verbose, development only -
info: Normal operations -
warn: Recoverable errors, degraded state -
error: Errors requiring attention
- Configuration Guide - Detailed configuration options
- Multi-Instance Setup - Horizontal scaling guide
- Observability - Monitoring and alerting
- Performance Tuning - Optimization guide
- Engineering Spec - Deep technical details
Getting Started
Core Concepts
Deployment
Integration
Operations
Development
Reference