EVENT-DRIVEN / MEDIA
StreamHub — Distributed Video Pipeline
An event-driven video platform: presigned direct-to-storage uploads, BullMQ + ffmpeg HLS adaptive-bitrate transcoding, and a custom hls.js player — without the API ever buffering video bytes.
Overview
StreamHub decouples ingest from CPU-heavy transcoding the way production video platforms do: the client PUTs directly to MinIO/S3 via a presigned URL, the API only enqueues work, a separate transcoder service builds an HLS rendition ladder (1080p→360p) plus thumbnail, and status events flow back over a second BullMQ queue. Services scale independently and never call each other directly.
The Problem
Most upload demos stream video through the API and call ffmpeg inline — concurrent uploads pin the CPU the HTTP server needs. Production systems need direct-to-storage ingest, async retryable transcoding, and ownership-safe streaming without exposing raw bucket URLs.
Architecture
Key Decisions
Presigned uploads bypass the API
The client gets a presigned S3/MinIO URL and PUTs the file straight to object storage — the upload service never buffers video bytes.
Queue-isolated transcoder service
Transcoding is a BullMQ consumer with exponential backoff. The transcoder does not know the API exists; communication is only via queues and object storage.
HLS ladder + ownership-proxied playback
ffmpeg builds adaptive-bitrate HLS; the API proxies master.m3u8 and segments after ownership checks so raw bucket URLs never reach the browser.
Dual-queue status pipeline
Transcoder emits completion/failure on event.queue; an event.worker inside the upload service flips video status in Postgres — retries are independent of the HTTP path.
Failure Modes
Transcode burst under concurrent uploads
Jobs queue in Redis instead of taking down the API; workers scale to N replicas independently.
Transient ffmpeg / storage failure
BullMQ exponential backoff retries both the transcode and status-update queues.
Unauthorized stream access
Every stream/delete route enforces per-user ownership; cascading delete removes raw + all renditions from S3.
Token theft via localStorage
15-minute access + rotating 30-day refresh tokens live in httpOnly SameSite=strict cookies with bcrypt-hashed passwords.
Implementation
- Presigned direct-to-storage uploads with live progress
- BullMQ video.queue + event.queue with exponential backoff
- ffmpeg/ffprobe HLS ladder (up to 1080p) and mid-clip thumbnails
- Custom hls.js player with Auto ABR + manual quality override
- JWT auth with rotating refresh cookies and Prisma/Postgres models
- HLS proxy from object storage with ownership checks
- Full Docker Compose stack: MinIO, Postgres, Redis, both services, nginx client
Lessons
- —Never put ffmpeg on the request path — ingest and transcoding must fail and scale independently.
- —Presigned uploads turn the API into a control plane instead of a bandwidth bottleneck.
- —Proxying HLS behind ownership checks keeps private media private without signed cookie gymnastics in the player.