Skip to content
← Projects
2026 ·Advanced work-in-progress · deployment-ready ·Featured

Livestreams

A self-hostable live-video platform — multi-protocol go-live, adaptive-quality playback, crash-safe recording, and one-click restreaming — that you own instead of renting from Mux or Livepeer.

GoffmpegPostgreSQLHLS

The problem

Self-hosters and small teams want to own their live-streaming platform instead of paying a managed provider like Mux or Livepeer. Livestreams lets creators go live over RTMP, SRT, or straight from the browser, then automatically transcodes to adaptive quality, records every session, and restreams to third-party platforms — all self-hosted, driving a clean dashboard.

Challenges

Multi-protocol ingest without trusting the client

SRS terminates RTMP/SRT and bridges browser WebRTC→RTMP, but authorization is driven by the app rather than SRS. SRS posts on_publish/on_unpublish/on_hls hooks to internal endpoints guarded by a shared secret, which validate the per-stream ingest key against Postgres; for WHIP the key never leaves the server via a same-origin proxy.

Adaptive-bitrate HLS transcoding with ffmpeg

A builder programmatically assembles the ffmpeg argument vector for a 3-rung ABR ladder (720p/480p/360p) using the split + scale + var_stream_map pattern with aligned GOPs. The same builder serves live (event playlist) and VOD (seekable), and omits audio maps when the source has no audio stream.

Long-running media jobs vs a request/response API

ffmpeg processes run for the whole session — incompatible with a short HTTP request. The system splits into an API binary and a worker binary coordinated through the River Postgres-backed queue; live workers override the default timeout to run until the publisher disconnects, and an 8s read-timeout prevents jobs hanging on RTMP inputs that don’t EOF cleanly.

Recording that survives an abrupt kill

Because the read-timeout kills ffmpeg mid-write, no normal MP4 trailer is written, so recordings are muxed as fragmented MP4 (frag_keyframe + empty_moov) and stay valid even when terminated. Header-only fragments under 64 KiB are skipped rather than saved as broken files, and valid recordings are uploaded and registered as assets with a thumbnail and seek-preview storyboard.

Implementation

API / worker split over a shared River queue

The API wires an insert-only River client behind an Enqueuer interface so handlers never import River directly, while the worker registers the executors: live transcode+recording, restream, VOD, clip, Whisper captions, and webhook delivery. Restream jobs return their River job id so a go-live-off toggle can cancel the running relay.

ffmpeg pipeline with live progress telemetry

VOD and clip jobs run ffmpeg with -progress pipe:1, parse each block into frame/fps/speed/bitrate/percent, and stream throttled telemetry and log lines over SSE, persisting logs so the timeline survives the job. The restream worker is a pure -c copy passthrough to an external RTMP/RTMPS target, decrypting the destination key on the fly.

HLS output and object storage

Workers write the ABR tree to a local HLS dir served as a dev origin (CDN-fronted in prod), with on-the-fly playlist rewriting that appends a signed token to every child URI for protected content. Recordings, VOD, clips, thumbnails, and captions go to MinIO/S3 through a small storage interface, read back via presigned URLs so ffmpeg streams directly from object storage.

Versioned REST API on chi

Everything sits under /v1 with layered chi middleware (request id, real IP, recoverer, CORS, timeout). Auth accepts JWT and lsk_ API keys, with a separate query-token path so EventSource can authorize SSE; public and rate-limited auth routes are grouped distinctly from the authenticated surface.

Real-time events and GeoIP analytics

Every pipeline step is persisted to stream_events and published to Redis pub/sub, fanned out to dashboards over SSE and to dispatchers for signed webhooks and notifications. Viewer QoS beacons feed analytics, and a GeoIP resolver degrades to a no-op when no MaxMind DB is configured.

Why this stack

Go
One module builds both the API and worker binaries; goroutines and exec.CommandContext fit supervising long-lived ffmpeg processes with clean cancellation.
chi
A lightweight net/http router whose composable middleware groups let header-auth, SSE query-token auth, rate-limited, and secret-guarded routes coexist in one tree.
PostgreSQL (pgx + sqlc)
The single source of truth, with sqlc generating type-safe queries and goose handling migrations.
River
Durable background jobs on the same Postgres — no extra broker — with per-job timeout overrides, retry/backoff for webhooks, and cancellation to stop live relays.
ffmpeg / SRS
SRS handles multi-protocol ingest and WebRTC→RTMP bridging with HTTP hooks for auth; ffmpeg does the ABR transcode, recording, clipping, and restreaming.
Redis
A low-latency pub/sub carrying stream and transcode-progress events, fanned out to SSE clients and webhook/notification dispatchers.

What it does

  • RTMP/SRT ingest with per-stream keys and publish hooks, plus browser go-live over WebRTC/WHIP
  • Automatic adaptive HLS (720p/480p/360p) from a built-in, CDN-frontable origin with signed-token playback
  • Crash-safe fragmented-MP4 recording, VOD upload/transcode, and clip cutting with thumbnails and storyboards
  • Restream/simulcast to YouTube/Twitch/any RTMP target with OAuth account linking
  • Live transcode progress and activity timeline over SSE, plus QoS and GeoIP analytics
  • Multi-tenant orgs with roles, JWT + API keys, signed webhooks with redelivery, and Whisper auto-captions

Get in touch

Building something interesting? Let's talk.

Whether it's a hard distributed-systems problem, a platform that needs to scale, or just a second opinion — I'm generally up for it.