Encoding & Transcoding
Video encoding pipeline, profiles, and output formats
When you upload a video, NixStream transcodes it into adaptive streaming packages using FFmpeg, Google Shaka Packager, and Bento4. Work runs in background queue jobs so the admin panel stays responsive.
Upload to playback flow
Here is what happens after you upload a file:
- Upload accepted. The file is stored and a
VideoSourcerecord is created with status "pending". - Job dispatched.
VideoEncodingJobis pushed to thevideo-encodingRedis queue. - Transcoding starts. A queue worker calls
bin/media-vodwith your encoding profile settings. - Packaging. FFmpeg creates video and audio renditions. Shaka Packager builds HLS and DASH manifests.
- Thumbnails. Preview images and a thumbnail VTT sprite are generated.
- Complete. Status changes to "completed". The video is playable, shareable, and available via API.
If something fails, status becomes "failed". Check queue logs for the error.
Customizing the encoder
bin/media-vod is the NixStream encoding CLI (upstream). It's a CLI tool, not an HTTP API.
Laravel never calls an encoder service over the network. Queue workers shell out to the binary path in media_cli_path with profile settings from the database. To customize behavior:
- Tune encoding profiles in Settings > Encoding
- Run
bin/media-vod --helpfor CLI flags - Replace or rebuild the binary from the upstream repo if you need lower-level changes
Manifest edits after encode use bin/manifest (upstream), same subprocess pattern.
Output structure
Encoded files land in storage/app/public/videos/{date}/{slug}/outputs/:
outputs/
├── master.m3u8 # HLS master playlist
├── stream.mpd # DASH manifest
├── video/avc1/{1..6}/ # Multiple resolutions
├── audio/{lang}/ # Multi-audio tracks
├── subtitles/{lang}/ # Caption files
└── thumbnails/ # Poster and spriteEncoding profiles
Open Settings > Encoding to manage profiles. Each profile defines:
- Name: Shown in the upload UI
- Resolutions: Output ladder (360p through 1080p or higher)
- Bitrates: Target video bitrates per resolution
- Audio: AAC stereo settings
- Encryption: AES-128 always applied for HLS/CMAF (Edge decrypts at playback)
- Packaging: HLS, DASH, or both
New uploads use the default profile unless you override it per video or collection.
Queue workers
Encoding is CPU-heavy. You need dedicated workers processing the video-encoding queue.
Production command:
php artisan queue:work redis --queue=high,default,video-encoding --tries=3Docker runs two workers in the nixstream_queue container via Supervisor. Scale when your backlog grows:
docker compose up -d --scale queue=3Monitor worker status:
docker compose exec queue supervisorctl statusCheck queue depth:
docker compose exec redis redis-cli llen queues:video-encodingEdge delivery and AES-128 (VOD)
VOD HLS/CMAF packages are AES-128 encrypted when enabled on the encoding profile. Playback uses signed Edge URLs for HLS and DASH (MPD). Edge decrypts segments server-side; keys never reach the player.
Live streams use signed Edge URLs only. Segments are cleartext on disk (no #EXT-X-KEY in source playlists). Stream keys never appear in player URLs; Edge serves live media through opaque signed paths.
Local storage requires the Edge integration (auto-connected in Docker). AES-128 and signed tokens don't work with cloud storage alone, only if your CDN supports them. See Security, Edge media.
Configuration reference
| Key | Purpose |
|---|---|
media_cli_path | Path to bin/media-vod |
APP_ENCODING_MAX_RESOLUTION | Max output height (default 4000) |
FFMPEG_BINARIES | FFmpeg binary path |
SHAKA_PACKAGER_BINARIES | Shaka Packager path |
REDIS_QUEUE_RETRY_AFTER | Job timeout (7200 seconds in Docker) |
Tuning tips
- Match your resolution ladder to your audience. Mobile-first catalogs can use fewer rungs.
- Higher bitrates improve quality but increase storage and bandwidth costs.
- Test with a short clip before bulk-encoding a large library.
- Scale workers before a big upload batch, not after jobs start piling up.
- Watch disk space in
storage/app/public/videos/during heavy encoding periods.
When encoding gets stuck
If videos stay in "processing" for a long time, check worker status (supervisorctl status or docker compose exec queue supervisorctl status), read queue logs, confirm Redis is up, verify disk space, then restart workers if needed.
See troubleshooting for more detail.