use Foxws\Streamer\Facades\Streamer;
Streamer::open('input.mp4')
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withHlsMasterPlaylist('master.m3u8')
->export()
->save();
By default, Shaka Streamer packages video and audio as CMAF (fragmented MP4).
Because both DASH and HLS can describe the same CMAF segments, you can produce
both manifests from a single run. Set withMpdOutput() and
withHlsMasterPlaylist() together and you get both — no extra transcoding,
just one more manifest file:
Streamer::open('input.mp4')
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withMpdOutput('manifest.mpd')
->withHlsMasterPlaylist('master.m3u8')
->export()
->save();
You don't need to call withManifestFormat(['dash', 'hls']) yourself — it's
worked out automatically from whichever outputs you set. Call it explicitly
only if you want to be specific about which manifest(s) get generated.
You can read the source file from one disk and write the packaged output to another:
Streamer::fromDisk('s3')
->open('videos/input.mp4')
->addVideoStream('videos/input.mp4', 'video.mp4')
->addAudioStream('videos/input.mp4', 'audio.mp4')
->withHlsMasterPlaylist('master.m3u8')
->export()
->toDisk('export')
->toPath('streams/')
->withVisibility('public')
->save();
withAESEncryption() doesn't return $this — it returns an EncryptionKey
object instead. That means it breaks the fluent chain, so call it on its own
line rather than in the middle of a chain:
// AES-128 encryption with auto-generated key
$streamer = Streamer::open('input.mp4')
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withHlsMasterPlaylist('master.m3u8');
$encryptionKey = $streamer->withAESEncryption();
$streamer->export()->save();
// With key rotation (rotates every 60 seconds)
$streamer = Streamer::open('input.mp4')
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withHlsMasterPlaylist('master.m3u8');
$encryptionKey = $streamer->withAESEncryption('key', 'cenc');
$streamer->withKeyRotationDuration(60);
$streamer->export()->toDisk('s3')->save();
See the AES Encryption Guide for protection schemes, codec-specific examples, and key management.
You can serve HLS and DASH content with signed URLs — handy for S3, CDNs, or apps with multiple tenants.
HLS:
use Foxws\Streamer\Http\DynamicHLSPlaylist;
use Illuminate\Support\Facades\Storage;
return (new DynamicHLSPlaylist('s3'))
->open("videos/{$video->id}/master.m3u8")
->setKeyUrlResolver(fn (string $key) => Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$key}",
now()->addHour(),
))
->setMediaUrlResolver(fn (string $file) => Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$file}",
now()->addHours(2),
))
->setPlaylistUrlResolver(fn (string $playlist) => route('video.playlist', [
'video' => $video,
'playlist' => $playlist,
]))
->toResponse(request());
DASH:
use Foxws\Streamer\Http\DynamicDASHManifest;
use Illuminate\Support\Facades\Storage;
return (new DynamicDASHManifest('s3'))
->open("videos/{$video->id}/manifest.mpd")
->setMediaUrlResolver(fn (string $file) => Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$file}",
now()->addHours(2),
))
->setInitUrlResolver(fn (string $file) => Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$file}",
now()->addHours(2),
))
->toResponse(request());
See URL Resolvers for more details.
You can listen for these streaming lifecycle events:
| Event | Payload |
|---|---|
StreamingStarted |
MediaCollection $mediaCollection, array $options |
StreamingCompleted |
StreamerResult $result, float $executionTime |
StreamingFailed |
Exception $exception, float $executionTime |
After saving, you can inspect what happened:
$exporter = Streamer::open('input.mp4')
->addVideoStream('input.mp4', 'video.mp4')
->addAudioStream('input.mp4', 'audio.mp4')
->withHlsMasterPlaylist('master.m3u8')
->export();
$exporter->afterSaving(function ($exporter, $result) {
$summary = $exporter->getCopySummary();
// ['total' => 12, 'copied' => 12, 'failed' => 0, 'totalSize' => 8421376]
$keys = $result->getUploadedEncryptionKeys();
});
$exporter->toDisk('s3')->save();
Streamer facade → MediaOpener| Method | Description |
|---|---|
fromDisk($disk) |
Set the source filesystem disk |
open($paths) |
Open one or more media files |
openFromDisk($disk, $paths) |
Set disk and open files in one call |
get() |
Get the MediaCollection |
export() |
Start the export chain (returns MediaExporter) |
dynamicHLSPlaylist(?string $disk) |
Create a DynamicHLSPlaylist instance |
dynamicDASHManifest(?string $disk) |
Create a DynamicDASHManifest instance |
cleanupTemporaryFiles() |
Delete all temporary directories |
MediaOpener → Streamer)| Method | Description |
|---|---|
addVideoStream($input, $output, $options) |
Add a video stream |
addAudioStream($input, $output, $options) |
Add an audio stream |
addTextStream($input, $output, $options) |
Add a text/subtitle stream |
addStream(Stream|array $stream) |
Add a Stream value object or raw stream descriptor (in, stream, output, …) |
withHlsMasterPlaylist($path) |
Set HLS output |
withMpdOutput($path) |
Set DASH/MPD output |
withSegmentDuration(int $seconds) |
Set segment duration |
withManifestFormat(array $formats) |
Set manifest formats (e.g. ['dash', 'hls']) |
withResolutions(array $resolutions) |
Set encoding resolutions |
withVideoCodecs(array $codecs) |
Set video codecs (e.g. ['h264', 'hw:vp9']) |
withAudioCodecs(array $codecs) |
Set audio codecs (e.g. ['aac', 'opus']) |
withSegmentPerFile(bool $enabled) |
Enable segment-per-file output |
withLowLatencyDashMode(bool $enabled) |
Enable low-latency DASH |
withStreamingMode(string $mode) |
Set mode ('vod' or 'live') |
withEncryption(array $config) |
Set raw encryption config |
withAESEncryption($keyFilename, $scheme, $label) |
Auto-generate AES encryption key, returns EncryptionKey (not $this) |
withKeyRotationDuration(int $seconds) |
Enable key rotation (requires 'cenc' or 'cbcs') |
withOption($key, $value) |
Set a custom pipeline option |
withOptions(array $options) |
Set multiple custom pipeline options |
getCommand() |
Get the built config array (for debugging) |
MediaExporter (returned by export())| Method | Description |
|---|---|
toDisk($disk) |
Set target disk for output |
toPath(string $path) |
Set target subdirectory |
withVisibility(string $visibility) |
Set file visibility ('public', 'private') |
afterSaving(callable $callback) |
Register post-save callback |
save(?string $path) |
Execute packaging and copy files to disk |
getCommand() |
Get the built config array |
dd() |
Dump config and die |
getCopySummary() |
Get {total, copied, failed, totalSize} |
getCopiedFiles() |
Get array of successfully copied files |
getFailedFiles() |
Get array of failed file copies |
hasCopyFailures() |
Check if any files failed to copy |
DynamicHLSPlaylist| Method | Description |
|---|---|
open(string $path) |
Open a playlist file |
setKeyUrlResolver(callable) |
Resolve encryption key URLs |
setMediaUrlResolver(callable) |
Resolve media segment URLs |
setPlaylistUrlResolver(callable) |
Resolve sub-playlist URLs |
get() |
Get processed playlist content |
all() |
Get all processed playlists (master + variants) |
toResponse($request) |
Return as application/vnd.apple.mpegurl response |
DynamicDASHManifest| Method | Description |
|---|---|
open(string $path) |
Open a manifest file |
setMediaUrlResolver(callable) |
Resolve media segment URLs |
setInitUrlResolver(callable) |
Resolve initialization segment URLs |
get() |
Get processed manifest content |
toResponse($request) |
Return as application/dash+xml response |
Some of the key options in config/streamer.php:
| Option | Default | Description |
|---|---|---|
streamer.streamer_binary |
'shaka-streamer' |
Path to the Shaka Streamer binary |
timeout |
14400 (4h) |
Process timeout in seconds |
temporary_files_root |
storage_path('app/streamer/temp') |
Directory for temporary files |
cache_files_root |
'/dev/shm' |
Fast storage for small files (keys, manifests) |
log_channel |
null |
Log channel for streamer output |
See Configuration for all options and environment variables.