Workflow — Streaming Distribution
What: Ship live or VOD content out to every playback surface. Package once, protect optionally, deliver everywhere.
Skills used
ffmpeg-streaming, ffmpeg-whip, ffmpeg-rist-zmq, ffmpeg-hwaccel, ffmpeg-drm, media-shaka, media-gpac, mediamtx-server, gstreamer-pipeline, media-cloud-upload, media-batch, ffmpeg-quality, webrtc-spec, webrtc-pion, webrtc-mediasoup, webrtc-livekit, ffmpeg-docs.
Pipeline
Step 1 — Pick contribution protocol
Latency vs. reliability vs. tooling:
| Protocol |
Latency |
Pros |
Cons |
| WHIP (WebRTC) |
sub-second |
lowest latency; browser-native |
needs HTTPS in prod; ffmpeg 7+ |
| SRT |
120 ms default (tunable) |
reliable UDP, forward error recovery |
needs port forwarding |
| RTMP |
2–5 s |
ubiquitous ingest |
TCP, no FEC, aging |
| RIST |
low |
FEC + ARQ pro contribution |
fewer CDN endpoints |
Step 2 — Build the ABR ladder
Use ffmpeg-streaming with the split-scale-encode pattern — one decode, N rate/size variants emitted in parallel. Netflix-style default: 4 video renditions (240p, 480p, 720p, 1080p) + 2 audio tracks (stereo + 5.1). Force keyframe alignment with -g <fps*seg_len>, -keyint_min <fps*seg_len>, -sc_threshold 0.
Step 3 — Package
Canonical target is CMAF (unified fMP4 segments serving both HLS and DASH manifests). Use media-shaka (Shaka Packager) for one-command HLS+DASH output from the same segment set. For pure HLS classic, ffmpeg-streaming's hls muxer works directly.
Step 4 — Optional DRM
- HLS AES-128 — simplest; encrypts segment payload only.
- Widevine / PlayReady / FairPlay unified — CMAF with
cbcs scheme. media-shaka handles the three simultaneously; key server URL in ${user_config.SHAKA_KEY_SERVER_URL}.
- DASH CENC —
ffmpeg-drm or Shaka for ClearKey / Widevine.
Step 5 — Fanout via MediaMTX
The mediamtx-server skill ingests RTMP/SRT/WebRTC and republishes to every other protocol. One YAML → every playback client served.
Step 6 — CDN upload
Use media-cloud-upload: Cloudflare Stream (tus resumable), Mux (token id + secret), Bunny (PUT to storage zone), S3 (aws CLI / rclone). Tokens live in plugin userConfig.
Step 7 — Scale — SFU for large interactive audiences
For bidirectional many-to-many (> ~50 peers), deploy an SFU instead of MediaMTX: webrtc-livekit (Go + JWT minter), webrtc-mediasoup (Node), or webrtc-pion (Go, lower-level).
Variants
- Low-latency HLS (LL-HLS) —
-hls_segment_type fmp4, short -hls_time (1–2 s), -hls_flags +independent_segments+omit_endlist.
- DASH with SegmentTemplate — manifest-side list generation, no explicit segment enum.
- RIST + ZMQ live control — dynamic filter parameter changes at runtime via
ffmpeg-rist-zmq.
- SRT listener mode — receiver listens on a port, sender
?mode=caller.
- Multicast MPEG-TS — LAN distribution, no CDN needed.
Gotchas
- MP4 output needs
-movflags +faststart — second-pass rewrite to move moov atom to front. Required for progressive web playback.
-hls_time is a target, not a guarantee. Keyframe cadence determines actual segment length. Force alignment: -g <fps*seg_len> -keyint_min <fps*seg_len> -sc_threshold 0.
independent_segments flag required for CMAF/LL-HLS. ABR switching breaks without it.
- HLS AES-128 encrypts segment PAYLOAD only, not the playlist. Protect the key URI via server-side auth.
-hls_key_info_file needs a trailing newline on the IV line. Many editors strip it silently, then decryption fails.
- Widevine L1 requires hardware CDM (ChromeOS, Android TEE). Test locally with L3 in Chrome.
- FairPlay uses
cbcs scheme; Widevine/PlayReady default to cenc. For unified CMAF DRM, ALL three use cbcs.
- CMAF audio must be fMP4, not TS. Mixed TS-video + fMP4-audio HLS breaks Safari.
- WHIP in production needs HTTPS. Chrome blocks
getUserMedia over http except on localhost.
- ffmpeg WHIP requires FFmpeg 7+. Older builds silently fail.
- MediaMTX default ports: 1935 RTMP, 8554 RTSP, 8888 HLS, 8889 WebRTC, 8890 SRT, 9997 API. Open all the ones you use.
- MediaMTX
/v3/* Control API needs api: yes in config (off by default).
-re flag reads input at native framerate — essential for live from file; without it ffmpeg reads as fast as possible.
-f flv explicit for RTMP — ffmpeg can pick the wrong muxer from the URL alone.
- RTMP chunk size default is 128 bytes — for 4K/HEVC ingest use
-rtmp_chunk_size 4096.
- AAC LC floor — 64 k mono, 96 k stereo. Below that, use HE-AAC.
- LiveKit JWT tokens expire (default 6 h). Mint fresh tokens client-side on refresh.
- Pion SFU needs STUN/TURN for NAT traversal. Set
ICEServers in config.
- mediasoup needs Node 18+ and a platform-specific native
mediasoupworker build.
Example — 4-variant CMAF HLS + DASH with unified DRM
Invoke media-shaka to take a single master ProRes/MOV through Shaka Packager with 4 video renditions + AAC stereo + cbcs key rotation, emitting both master.m3u8 and manifest.mpd pointing at the same *.m4s segments. Upload with media-cloud-upload. QC the result with workflow-analysis-quality.
Related
workflow-live-production — where the contribution stream originates.
workflow-broadcast-delivery — for broadcast-spec contribution paths.
workflow-analysis-quality — VMAF + manifest validation on the encoded output.
1---2name: workflow-streaming-distribution3description: Deliver live or VOD content over HLS / DASH / RTMP / SRT / WHIP / RIST with adaptive bitrate ladders, DRM (Widevine / PlayReady / FairPlay cbcs), multi-protocol fanout via MediaMTX, and CDN upload (Cloudflare Stream / Mux / Bunny / S3). Use when the user says "stream to HLS", "build an ABR ladder", "add Widevine", "multi-bitrate DASH", "WHIP ingest", "low-latency HLS", "SRT contribution", "stream to multiple platforms", "SFU for WebRTC", or anything involving large-scale distribution.4---56# Workflow — Streaming Distribution78**What:** Ship live or VOD content out to every playback surface. Package once, protect optionally, deliver everywhere.910## Skills used1112`ffmpeg-streaming`, `ffmpeg-whip`, `ffmpeg-rist-zmq`, `ffmpeg-hwaccel`, `ffmpeg-drm`, `media-shaka`, `media-gpac`, `mediamtx-server`, `gstreamer-pipeline`, `media-cloud-upload`, `media-batch`, `ffmpeg-quality`, `webrtc-spec`, `webrtc-pion`, `webrtc-mediasoup`, `webrtc-livekit`, `ffmpeg-docs`.1314## Pipeline1516### Step 1 — Pick contribution protocol1718Latency vs. reliability vs. tooling:1920| Protocol | Latency | Pros | Cons |21|---|---|---|---|22| WHIP (WebRTC) | sub-second | lowest latency; browser-native | needs HTTPS in prod; ffmpeg 7+ |23| SRT | 120 ms default (tunable) | reliable UDP, forward error recovery | needs port forwarding |24| RTMP | 2–5 s | ubiquitous ingest | TCP, no FEC, aging |25| RIST | low | FEC + ARQ pro contribution | fewer CDN endpoints |2627### Step 2 — Build the ABR ladder2829Use `ffmpeg-streaming` with the split-scale-encode pattern — one decode, N rate/size variants emitted in parallel. Netflix-style default: 4 video renditions (240p, 480p, 720p, 1080p) + 2 audio tracks (stereo + 5.1). Force keyframe alignment with `-g <fps*seg_len>`, `-keyint_min <fps*seg_len>`, `-sc_threshold 0`.3031### Step 3 — Package3233Canonical target is CMAF (unified fMP4 segments serving both HLS and DASH manifests). Use `media-shaka` (Shaka Packager) for one-command HLS+DASH output from the same segment set. For pure HLS classic, `ffmpeg-streaming`'s `hls` muxer works directly.3435### Step 4 — Optional DRM3637- **HLS AES-128** — simplest; encrypts segment payload only.38- **Widevine / PlayReady / FairPlay unified** — CMAF with `cbcs` scheme. `media-shaka` handles the three simultaneously; key server URL in `${user_config.SHAKA_KEY_SERVER_URL}`.39- **DASH CENC** — `ffmpeg-drm` or Shaka for ClearKey / Widevine.4041### Step 5 — Fanout via MediaMTX4243The `mediamtx-server` skill ingests RTMP/SRT/WebRTC and republishes to every other protocol. One YAML → every playback client served.4445### Step 6 — CDN upload4647Use `media-cloud-upload`: Cloudflare Stream (tus resumable), Mux (token id + secret), Bunny (PUT to storage zone), S3 (aws CLI / rclone). Tokens live in plugin userConfig.4849### Step 7 — Scale — SFU for large interactive audiences5051For bidirectional many-to-many (> ~50 peers), deploy an SFU instead of MediaMTX: `webrtc-livekit` (Go + JWT minter), `webrtc-mediasoup` (Node), or `webrtc-pion` (Go, lower-level).5253## Variants5455- **Low-latency HLS** (LL-HLS) — `-hls_segment_type fmp4`, short `-hls_time` (1–2 s), `-hls_flags +independent_segments+omit_endlist`.56- **DASH with SegmentTemplate** — manifest-side list generation, no explicit segment enum.57- **RIST + ZMQ live control** — dynamic filter parameter changes at runtime via `ffmpeg-rist-zmq`.58- **SRT listener mode** — receiver listens on a port, sender `?mode=caller`.59- **Multicast MPEG-TS** — LAN distribution, no CDN needed.6061## Gotchas6263- **MP4 output needs `-movflags +faststart`** — second-pass rewrite to move moov atom to front. Required for progressive web playback.64- **`-hls_time` is a target, not a guarantee.** Keyframe cadence determines actual segment length. Force alignment: `-g <fps*seg_len> -keyint_min <fps*seg_len> -sc_threshold 0`.65- **`independent_segments` flag required for CMAF/LL-HLS.** ABR switching breaks without it.66- **HLS AES-128 encrypts segment PAYLOAD only**, not the playlist. Protect the key URI via server-side auth.67- **`-hls_key_info_file` needs a trailing newline on the IV line.** Many editors strip it silently, then decryption fails.68- **Widevine L1 requires hardware CDM** (ChromeOS, Android TEE). Test locally with L3 in Chrome.69- **FairPlay uses `cbcs` scheme; Widevine/PlayReady default to `cenc`.** For unified CMAF DRM, ALL three use `cbcs`.70- **CMAF audio must be fMP4, not TS.** Mixed TS-video + fMP4-audio HLS breaks Safari.71- **WHIP in production needs HTTPS.** Chrome blocks `getUserMedia` over http except on `localhost`.72- **ffmpeg WHIP requires FFmpeg 7+.** Older builds silently fail.73- **MediaMTX default ports: 1935 RTMP, 8554 RTSP, 8888 HLS, 8889 WebRTC, 8890 SRT, 9997 API.** Open all the ones you use.74- **MediaMTX `/v3/*` Control API needs `api: yes` in config** (off by default).75- **`-re` flag reads input at native framerate** — essential for live from file; without it ffmpeg reads as fast as possible.76- **`-f flv` explicit for RTMP** — ffmpeg can pick the wrong muxer from the URL alone.77- **RTMP chunk size default is 128 bytes** — for 4K/HEVC ingest use `-rtmp_chunk_size 4096`.78- **AAC LC floor** — 64 k mono, 96 k stereo. Below that, use HE-AAC.79- **LiveKit JWT tokens expire** (default 6 h). Mint fresh tokens client-side on refresh.80- **Pion SFU needs STUN/TURN** for NAT traversal. Set `ICEServers` in config.81- **mediasoup needs Node 18+ and a platform-specific native `mediasoupworker` build.**8283## Example — 4-variant CMAF HLS + DASH with unified DRM8485Invoke `media-shaka` to take a single master ProRes/MOV through Shaka Packager with 4 video renditions + AAC stereo + `cbcs` key rotation, emitting both `master.m3u8` and `manifest.mpd` pointing at the same `*.m4s` segments. Upload with `media-cloud-upload`. QC the result with `workflow-analysis-quality`.8687## Related8889- `workflow-live-production` — where the contribution stream originates.90- `workflow-broadcast-delivery` — for broadcast-spec contribution paths.91- `workflow-analysis-quality` — VMAF + manifest validation on the encoded output.