Self‑hosted live streaming server in Go. Ingest via SRT, transcode with FFmpeg to HLS, serve locally, and upload to Cloudflare R2 for scalable delivery. Secure APIs with HMAC request signing and JWT stream keys. Optional OpenTelemetry metrics.
- Overview
- Features
- Architecture
- Quick start
- Configuration (.env)
- Running (Docker and local)
- API reference (start/stop/status)
- Security (HMAC + JWT stream keys)
- Video playback
- Webhooks
- Metrics and observability
- Deployment notes
- License
Livetran exposes a simple HTTP API to manage a live stream lifecycle:
- Start a stream: creates an SRT listener on a random free port and generates a JWT‑backed stream key.
- Ingest: your encoder (e.g., OBS) publishes to the returned SRT URL.
- Transcode: FFmpeg converts the incoming SRT MPEG‑TS to HLS segments and playlists under
output/. - Upload: a watcher pushes
.tsand.m3u8files to Cloudflare R2; the first public playlist URL is returned via webhook. - Serve: HLS files are available locally under
/video/for testing, or via your R2 public URL in production.
- Secure SRT ingestion with JWT stream keys
- Simple REST API for start/stop/status
- FFmpeg HLS transcoding (single‑profile or ABR ladder)
- Cloudflare R2 uploads (S3‑compatible)
- Real‑time webhooks on status updates
- CORS enabled, HMAC‑SHA256 request verification
- Optional OpenTelemetry metrics export
See docs guide for details and diagram:
docs/guide/introduction.mdxdocs/arch.png
Prerequisites:
- Go 1.21+ if running locally
- FFmpeg installed (Docker image includes it)
- Cloudflare R2 bucket and credentials
- TLS keypair at
keys/localhost.pemandkeys/localhost-key.pem(self‑signed is fine for dev)
Clone and prepare .env:
cp .env.example .env # if you keep a template; otherwise create .env with the vars belowRequired:
- JWT_SECRET: HMAC secret for stream key JWT
- HMAC_SECRET: HMAC secret for signing REST request bodies
- R2_ACCOUNT_ID: Cloudflare account id for R2 (used in endpoint)
- R2_ACCESS_KEY: Cloudflare R2 access key id
- R2_SECRET_KEY: Cloudflare R2 secret access key
- BUCKET_NAME: Cloudflare R2 bucket to upload HLS artifacts
- CLOUDFLARE_PUBLIC_URL: Base public URL that serves your R2 objects (e.g., https://r2.example.com/hls)
Optional (metrics):
- ENABLE_METRICS: set
trueto enable OTLP metrics export - OTEL_EXPORTER_OTLP_ENDPOINT: default
localhost:4318 - OTEL_EXPORTER_OTLP_INSECURE:
trueto disable TLS for exporter (defaulttrue) - SERVICE_VERSION, ENV: resource attributes for metrics
Build and run:
docker build -t livetran .
docker run -d \
-p 8080:8080 \
--name livetran \
--env-file .env \
-v "$(pwd)/output:/app/output" \
-v "$(pwd)/keys:/app/keys:ro" \
livetrango mod download
go run ./cmd/main.goThe server listens on HTTPS at :8080 and expects TLS keys at keys/localhost.pem and keys/localhost-key.pem.
Base path: /api (all endpoints require HMAC request signing; see Security)
- Start stream
POST /api/start-stream
Content-Type: application/json
LT-SIGNATURE: <hex(hmac_sha256(body,HMAC_SECRET))>
{"stream_id":"req1","webhook_urls":["https://example.com/webhook"],"abr":true}Response:
{"success":true,"data":"Stream launching!"}- Stop stream
POST /api/stop-stream
Content-Type: application/json
LT-SIGNATURE: <hex(hmac_sha256(body,HMAC_SECRET))>
{"stream_id":"req1"}Response:
{"success":true,"data":"Stream stopped!"}- Status
GET /api/status
Content-Type: application/json
LT-SIGNATURE: <hex(hmac_sha256(body,HMAC_SECRET))>
{"stream_id":"req1"}Response (example):
{"success":true,"data":"Status: STREAMING"}HMAC request signing (all /api/* routes):
- Compute
hex(hmac_sha256(<raw body>, HMAC_SECRET))and set headerLT-SIGNATURE. - Requests without a valid signature are rejected.
JWT stream keys (SRT publish):
- When you start a stream, Livetran generates a JWT stream key for the given
stream_idusingJWT_SECRET. - Your encoder connects using the returned URL template:
srt://<server_ip>:<port>?streamid=mode=publish,rid=<stream_id>,token=<jwt> - The server validates that
tokenis valid, unexpired, and matchesrid.
Local testing endpoint (serves files from output/):
- HLS playlists/chunks:
GET /video/<file>- Content types:
.m3u8=>application/vnd.apple.mpegurl,.ts=>video/MP2TIn production, serve HLS from your Cloudflare R2 public URL.
- Content types:
- Set
abr=truein the start request to enable an HLS variant ladder (1080p/720p/480p), with a master playlist named<stream_id>_master.m3u8. - If
abr=false(default), a single playlist<stream_id>.m3u8is produced.
Provide one or more webhook_urls in start-stream to receive JSON updates. Example payload:
{
"Status": "READY|STREAMING|STOPPED",
"Update": "message",
"StreamLink": "https://.../req1_master.m3u8"
}Notes:
- The first time a public playlist is uploaded,
StreamLinkis included. - On ABR, link is emitted when the master playlist is available.
- Set
ENABLE_METRICS=trueto enable OpenTelemetry metrics export over OTLP/HTTP. - Configure exporter via
OTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_INSECURE. - A gauge
streams_info{status=idle|active|stopped}reports counts derived from the in‑memoryTaskManager. - Sample Grafana/Prometheus/Loki/OTel Collector configs are under
metrics/deployment/.
- Ensure valid TLS certs in
keys/for HTTPS server startup. - Persist
output/if you want local playback beyond container lifecycle (Docker volume provided). .gitignoreshould excludeoutput/, secrets, and local artifacts; keepkeys/secure.
Apache 2.0 — see LICENSE.