9.6 KiB
ADR-001: Cost-Efficient Self-Hosted 24/7 Synchronized Linear Video Streaming with Custom Chat (US-Centric, Cloudflare + R2 Optimized)
Status: Accepted
Date: 2026-08-02
Authors: Synthesized from Grok, GPT-Terra, DeepSeek, Gemini (research-assisted)
Decision Owners: Product & Engineering
Scope: One 24/7 linear video channel, custom viewer website, public/private chat, playlist-driven playout. Related ADRs: authentication, moderation, payments, multi-channel.
Context
We require a completely independent 24/7 linear video channel that broadcasts a continuous playlist of self-owned pre-recorded videos. Viewers consume it via a lightweight custom HTML/CSS/JavaScript single-page application (no reliance on YouTube, Twitch, Vimeo, or paid Cloudflare Stream tiers). The experience must include:
- True (or near-true) synchronized playback — all viewers see the same program timeline.
- Embedded custom chat with public rooms, private 1:1/direct messages, presence, moderation (mute/ban/report), rate limiting.
- “Now playing” schedule/metadata.
- Full control over source media, frontend, schedule, and chat data.
- Primary audience and operational origin in the United States (East/Central preferred for uploads and low latency).
Key distinctions:
- Playout: Continuous scheduled program from playlist.
- Packaging: FFmpeg → standard HLS (
.m3u8+.m4ssegments, 4–6s duration). - Delivery: Immutable segments served via Cloudflare edge caching + R2 (zero egress cost).
- Interaction: Separate lightweight WebSocket service for chat.
Bandwidth (not storage or compute) is the dominant cost driver. A single viewer at ~4 Mbps consumes ~1.3–1.8 TB/month; 100 concurrent viewers = ~130–180 TB/month. Direct VPS delivery is only viable for PoC/small audiences. HLS segments are typically .m4s files, with .m4s being the default for fMP4 segments.
Decision Drivers
- Minimize monthly cost (target <$25 for moderate scale).
- Full independence and ownership (no video platform lock-in).
- Strong US performance and low latency.
- True/near-true synchronization via live-style HLS (not pure VOD).
- Aggressive immutable caching of
.m4ssegments to achieve near-zero egress. - Browser-native playback (
hls.js+ native HLS). - Lightweight, maintainable stack with modest operational complexity.
- Support for custom multi-room + private chat with persistence and moderation.
- Scalability from tens to thousands of concurrent viewers without linear cost growth.
- Compliance with Cloudflare ToS (use R2 as origin for large video files).
Decision
Adopt a self-managed US-East playout + Cloudflare R2 + edge-cached HLS architecture:
1. Video Pipeline & Playout (US-East VPS)
- Location: Hetzner Cloud CX22 (or equivalent) in Ashburn, VA (~$4–8/mo, 2 vCPU/4 GB, 40 GB NVMe, 20 TB included traffic). Excellent peering.
- Tools:
- FFmpeg (concat demuxer +
while trueloop for auto-restart) for playlist playout and normalization (-c copywhere possible, CRF encoding). - MediaMTX (preferred), SRS, or Nginx-RTMP as lightweight media server/packager for continuous HLS output.
- Sliding window HLS: 4–6s segments, playlist length ~24–60s.
- FFmpeg (concat demuxer +
- Source Normalization (mandatory before scheduling):
- Container: MP4
- Video: H.264/AVC, yuv420p, constant 30 fps, keyframe interval 2s
- Audio: AAC-LC, 48 kHz, stereo, 128–192 kbps
- Bitrate targets: 720p30 (2.5–5 Mbps), 1080p30 (5–8 Mbps)
- Dashboard for playlist management, current file, health.
2. Storage & Delivery (Cloudflare R2 + CDN)
- HLS segments (
.m4s) and manifests uploaded to Cloudflare R2 (Eastern North America hintenam). - Custom domain (
media.linearvoid.tv) fronted by Cloudflare with Cache Rules:*.m4s(and init.mp4if using fMP4 later):Cache Everything, immutable, Edge TTL 24h–1yr (Cache-Control: public, max-age=31536000, immutable).*.m3u8: Short TTL (1–10s) ormust-revalidate(Cache-Control: public, max-age=1..10, must-revalidate).- Master playlist: TTL ~30s.
- Tiered Cache enabled where possible.
- R2 as origin satisfies ToS for large video; $0 egress, free 10 GB, then ~$0.015/GB-mo (Standard).
- Viewer player hits Cloudflare edge → vast majority of traffic served from cache (origin/R2 hit only once per region).
3. Frontend
- Static SPA (HTML + custom CSS + vanilla JS) hosted on Cloudflare Pages or same VPS/Nginx.
- Video player:
hls.js(Chromium/Firefox) or native HLS (Safari). Single livemaster.m3u8. - Embedded chat sidebar with room switcher, private DMs, now-playing overlay.
4. Custom Chat
- Lightweight backend (Go, Node.js + Socket.io, or Elixir) on same VPS.
- WebSockets (
wss://), Redis (presence, pub/sub, rate limits), PostgreSQL/SQLite (message history, moderation log, users). - Features: public rooms, private 1:1, presence, rate limiting, mute/timeout/ban/report, basic search.
- Persistence with retention policy. Moderation audit log. No E2EE by default (separate ADR if required).
5. Infrastructure Summary & Cost Model
- Primary: Hetzner Ashburn VPS (playout + chat + Nginx) + Cloudflare R2 + Pages.
- Monitoring: MediaMTX/SRS dashboard, UptimeRobot, systemd logs, Cloudflare analytics.
- Backup: Weekly R2/S3-compatible snapshots of playlist, DB, config.
- Fallback: Emergency slate/loop playlist, automated restart.
- Monthly Cost (early production, moderate US audience): $5–25 (VPS $4–8 + R2 $1–3 + negligible requests). Scales to hundreds/thousands of viewers with near-zero additional egress.
Initial Targets:
- Protocol: Standard HLS (
.m4ssegments). Defer LL-HLS/CMAF/WebRTC. - Max rendition: 720p30 initially (upgrade to 1080p30 or ABR later).
- Latency: 15–35s (acceptable for linear programming/chat).
Storage for 48 Hours of Source Footage
Formula: GB ≈ (bitrate_Mbps × duration_seconds) / 8 / 1024 (+5–10% overhead).
| Resolution | Video Bitrate | Total w/ Audio | 48h Raw | Recommended Allocation | R2 Cost/mo (Standard) |
|---|---|---|---|---|---|
| 720p30 | 2.5–4 Mbps | ~2.6–4.2 Mbps | 57–90 GB | ~80–110 GB | ~$1.20–$1.65 |
| 1080p30 | 5–8 Mbps | ~5.1–8.2 Mbps | 110–175 GB | ~130–180 GB | ~$1.95–$2.70 |
Plan 2–3× headroom for variants, backups, artwork, processing. Use R2 Infrequent Access if latency acceptable.
Rationale
- Hetzner Ashburn + Cloudflare R2 delivers unmatched price/performance for US traffic. R2’s $0 egress + edge caching eliminates the dominant bandwidth cost.
- HLS is the most compatible, cache-friendly, operationally simple protocol for one-to-many linear broadcast.
- FFmpeg + MediaMTX/SRS provides reliable playlist-driven continuous playout with low CPU.
- Immutable segment naming + targeted Cache Rules ensure high hit ratios while keeping live manifests fresh.
- Lightweight static frontend + separate lightweight chat service keeps complexity low.
- Starts simple (single VPS) and evolves (ABR, secondary origins, managed DBs, LL-HLS) without breaking changes.
This hybrid synthesizes the strongest elements from all evaluated ADRs while resolving minor variances (Hetzner vs generic VPS, direct origin vs R2, SQLite vs PostgreSQL+Redis, exact bitrates).
Considered Alternatives
- Pure VPS origin (no R2/CDN): Rejected for production (20 TB cap limits ~12–20 concurrent HD viewers).
- Managed platforms (YouTube/Twitch/Cloudflare Stream): Rejected (loss of control, higher cost at scale, ToS restrictions).
- WebRTC / P2P: High complexity, poor caching, not suited for passive linear broadcast.
- Owncast: Good for turnkey but less flexible for custom playlist/schedule/chat.
- LL-HLS or CMAF initially: Deferred (added complexity; standard HLS sufficient for v1).
Consequences
Positive:
- Near-zero egress cost → unlimited viewer scaling.
- Full ownership, US performance, synchronized linear experience.
- Extremely low TCO (~$5–25/mo).
- Cacheable, maintainable, evolvable architecture.
Negative / Risks & Technical Debt:
- Cloudflare ToS scrutiny for heavy video (mitigated by R2 path; monitor & have BunnyCDN fallback).
- HLS latency (15–35s) affects real-time chat feel.
- Single-origin SPOF (mitigate with DNS failover/secondary region later).
- Video normalization and cache-rule tuning required upfront.
- Chat moderation and abuse prevention operational burden.
- R2 request costs on cache misses (measure in production; Tiered Cache helps).
- No built-in E2EE for private chats (future ADR).
Operational Requirements:
- Monitor playout health, segment generation, cache hit ratio, R2 ops, WebSocket connections.
- Maintain fallback slate, emergency playlist, backup/restore procedures.
- Strict CORS, HTTPS, WAF, rate limiting, authenticated admin paths.
Implementation Plan
Phase 1 (Prototype): Hetzner VPS, normalized playlist, FFmpeg+MediaMTX → HLS to R2, basic hls.js page + Socket.io chat, cache rules, load test.
Phase 2 (MVP): Scheduler/metadata, PostgreSQL+Redis, full moderation, dashboards/alerts, emergency fallback.
Phase 3 (Scale): ABR, multi-region, LL-HLS evaluation, paid access controls.
References
- Cloudflare R2 pricing, custom domains, cache rules, Tiered Cache.
- MediaMTX / SRS / FFmpeg HLS documentation.
- Hetzner Cloud Ashburn pricing (2026).
- hls.js, Video.js, standard HLS best practices.
- Video bitrate/storage calculators.
This consolidated ADR merges all prior drafts. Future ADRs will detail Docker Compose, exact cache rules, playlist normalization scripts, chat schema, monitoring, and access control.