Multi-Channel streaming setup
| api | ||
| config | ||
| icecast | ||
| .gitignore | ||
| docker-compose.yml | ||
| ffmpeg-ideas.md | ||
| init.sh | ||
| LICENSE | ||
| README.md | ||
| variables.env.dist | ||
Television
A multi-channel live streaming platform with automated scheduling, Discord integration, and protected video archive. Built on top of Datarhei Restreamer for video processing.
Features
- Multi-Channel Scheduling - Schedule streams by hour with priority-based automatic switching
- Automatic Fallback - Falls back to nearest scheduled stream when current ends
- Discord Bot Integration - Live notifications, EPG commands, and remote recording control
- Protected Video Archive - HMAC-based timecode authentication for secure access
- Live Recording - Record streams with automatic thumbnail generation
- HLS Adaptive Streaming - Quality selection via HLS.js with Plyr player
- Automated SSL - Let's Encrypt certificates via acme.sh
- Cloudflare Compatible - Proper handling of CF-Connecting-IP headers
Architecture
┌─────────────────┐
│ Internet │
└────────┬────────┘
│
┌────────▼────────┐
│ HAProxy │ :80, :443
│ (SSL, Routing) │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────────▼───────┐ ┌────▼─────┐ ┌──────▼──────┐
│ Flask API │ │Restreamer│ │ Icecast │
│ (:8080) │ │ (:8080) │ │ (:8000) │
│ │ │ (:1935) │ │ (optional) │
│ - Web UI │ │ (:6000) │ └─────────────┘
│ - Stream Mgmt │ └──────────┘
│ - Discord Bot │
│ - Archive │
└────────────────┘
Tech Stack
| Component | Technology |
|---|---|
| Backend | Python 3.13, Flask 3.1 |
| Frontend | Bootstrap 5, Plyr.js, HLS.js |
| Streaming | Datarhei Restreamer 2.12 |
| Proxy | HAProxy |
| Containerization | Docker Compose |
| SSL | acme.sh (Let's Encrypt) |
Installation
Prerequisites
- Docker and Docker Compose
- A domain name pointing to your server
- (Optional) Cloudflare for CDN/proxy
Quick Start
-
Clone and configure environment
git clone <repository-url> cd television cp variables.env.dist variables.envEdit
variables.envand set all required variables (see Configuration). -
Initialize data directories
./init.sh -
Start the acme.sh service
docker-compose up -d acme-sh -
Register ACME account
source variables.env docker exec acme.sh --register-account -m $EMAILCopy the
ACCOUNT_THUMBPRINTfrom the output and add it tovariables.env. -
Start the stack
docker-compose up -d --build --remove-orphans -
Issue SSL certificates
source variables.env docker exec acme.sh --issue -d $SERVER_NAME --stateless docker exec acme.sh --issue -d $CORE_API_HOSTNAME --stateless -
Install certificates
source variables.env docker exec acme.sh --install-cert -d $SERVER_NAME \ --reloadcmd "cat \$CERT_KEY_PATH \$CERT_FULLCHAIN_PATH > /certificates/$SERVER_NAME.pem" docker exec acme.sh --install-cert -d $CORE_API_HOSTNAME \ --reloadcmd "cat \$CERT_KEY_PATH \$CERT_FULLCHAIN_PATH > /certificates/$CORE_API_HOSTNAME.pem" -
Reload HAProxy
docker kill -s USR2 haproxy -
Set up automatic certificate renewal
Add to crontab:
0 0 1 * * docker exec acme.sh --cron && docker kill -s USR2 haproxy
Configuration
Environment Variables
| Variable | Description |
|---|---|
SERVER_NAME |
Main domain (e.g., example.com) |
EMAIL |
Admin email for ACME certificates |
ACCOUNT_THUMBPRINT |
ACME account thumbprint (from step 4) |
CORE_API_HOSTNAME |
Restreamer hostname (e.g., stream.example.com) |
CORE_API_AUTH_USERNAME |
Restreamer admin username |
CORE_API_AUTH_PASSWORD |
Restreamer admin password |
API_VOD_TOKEN |
Bearer token for video upload API |
FLASK_SECRET_KEY |
Flask session encryption key |
TIMECODE_SECRET_KEY |
HMAC key for archive timecodes |
DISCORDBOT_TOKEN |
Discord bot token |
DISCORDBOT_GUILD_ID |
Discord server ID |
DISCORDBOT_CHANNEL_ID |
Channel for bot messages |
Usage
Setting Up Streams
-
Access the Restreamer admin panel at
https://stream.example.com/ui -
Create channels and configure them with JSON metadata in the Description field:
{ "start_at": "21", "prio": 0, "details": "Evening show" }Field Description start_atHour (0-23), "now"for immediate, or"never"to disableprioPriority level (higher takes precedence) detailsOptional description for Discord announcements
Streaming URLs
SRT (recommended):
srt://SERVERADDR:6000?mode=caller&transtype=live&pkt_size=1316&streamid=STREAM-UUID.stream,mode:publish,token:CHANGEME
RTMP:
rtmp://SERVERADDR/STREAM-UUID.stream/CHANGEME
Discord Bot Commands
| Command | Description |
|---|---|
.hello |
Bot status check |
.time |
Current server time |
.epg |
Show stream schedule |
.now |
Current stream info |
.rec |
Start recording current stream |
.stop |
Stop recording |
API Endpoints
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/ |
GET | Public | Live stream player |
/archive |
GET/POST | Timecode | Video archive |
/request-timecode |
POST | Public | Request archive access |
/playhead |
GET | Public | Current stream info (JSON) |
/video |
POST | Bearer | Upload video files |
Project Structure
television/
├── api/
│ ├── app/
│ │ ├── static/ # CSS, JS, images
│ │ ├── templates/ # Jinja2 templates
│ │ ├── flask_api.py # Application entry point
│ │ ├── frontend.py # Routes and views
│ │ ├── stream_manager.py # Stream orchestration
│ │ ├── discord_bot_manager.py
│ │ └── timecode_manager.py
│ ├── Dockerfile
│ └── requirements.txt
├── config/
│ ├── haproxy/
│ │ └── haproxy.cfg
│ └── icecast/
│ └── icecast.xml.template
├── data/ # Runtime data (gitignored)
│ ├── certificates/
│ ├── restreamer/
│ └── recorder/
├── docker-compose.yml
├── variables.env.dist
└── init.sh
License
Unlicense - Public Domain