This guide shows how to use withAESEncryption() with different video codecs.
use Foxws\Shaka\Filesystem\Media;
use Foxws\Shaka\Filesystem\MediaCollection;
use Foxws\Shaka\Support\Packager;
// Open your media
$media = Media::make('videos', 'input.mp4');
$packager = Packager::create();
$packager->open(MediaCollection::make([$media]));
// Enable encryption (uses cbc1 by default)
$encryptionKey = $packager->withAESEncryption();
// Add your streams
$packager->addStream([
'in' => $media->getLocalPath(),
'stream' => 'video',
'output' => 'encrypted_video.mp4',
]);
withAESEncryption() returns an EncryptionKey value object with key, keyId, and filePath properties.
H.264 is the most widely supported codec. Use cbc1 for the broadest compatibility:
$media = Media::make('videos', 'h264_video.mp4');
$packager->open(MediaCollection::make([$media]));
// Generate encryption key
$encryptionKey = $packager->withAESEncryption('h264.key', 'cbc1');
// Add video stream
$packager->addStream([
'in' => $media->getLocalPath(),
'stream' => 'video',
'output' => 'h264_encrypted.mp4',
]);
// The key is now at: $encryptionKey->filePath
// Key: $encryptionKey->key
// Key ID: $encryptionKey->keyId
Key rotation periodically swaps in a new encryption key, so a leaked key only exposes a limited window of content instead of the whole stream:
$media = Media::make('videos', 'input.mp4');
$packager->open(MediaCollection::make([$media]));
// Enable encryption with key rotation every 5 minutes
// Base name 'key' becomes: key_0.key, key_1.key, key_2.key, etc.
$encryptionKey = $packager->withAESEncryption(); // Uses the default 'key' base name
$packager->withKeyRotationDuration(60); // 60 seconds, a balanced choice
$packager->addVideoStream('input.mp4', 'video.mp4');
$packager->withHlsMasterPlaylist('master.m3u8');
$result = $packager->export();
| Interval | When to use it |
|---|---|
->withKeyRotationDuration(30) |
High security (Apple's recommendation for HLS) |
->withKeyRotationDuration(60) |
Balanced security |
->withKeyRotationDuration(300) |
Lower overhead (5 minutes), still secure |
->withKeyRotationDuration(900) |
Minimal rotation (15 minutes), for low-risk content |
Shaka Packager does all of this automatically:
#EXT-X-KEY tags for HLS)Players fetch the correct key for each segment on their own — you don't need to coordinate this yourself.
When you package with key rotation and then upload the result, every key that was generated is tracked for you:
$packager->withAESEncryption(); // Default: key_0.key, key_1.key, key_2.key...
$packager->withKeyRotationDuration(300);
$packager->addVideoStream('input.mp4', 'video.mp4');
$packager->withHlsMasterPlaylist('master.m3u8');
$result = $packager->export();
// Upload everything (segments + keys) to a private S3 bucket
$result->toDisk('s3', 'videos');
// Get every key that was uploaded, so you can store metadata in your database
$uploadedKeys = $result->getEncryptionKeys();
foreach ($uploadedKeys as $key) {
EncryptionKey::create([
'filename' => $key->filename, // e.g., "key_0.key", "key_1.key"
'path' => $key->path, // S3 path: "videos/key_0.key"
'key' => $key->content, // Hex-encoded key content
'video_id' => $video->id,
]);
}
That's it — toDisk() uploads both segments and encryption keys to your private S3 bucket in one step.
Use setKeyUrlResolver() to generate signed, temporary URLs on demand when you serve a playlist:
use Foxws\Shaka\Http\DynamicHLSPlaylist;
// In your controller
public function playlist(Video $video)
{
$playlist = (new DynamicHLSPlaylist('s3'))
->setKeyUrlResolver(function ($keyFilename) use ($video) {
// Generate a signed URL on demand (expires in 1 hour)
return Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$keyFilename}",
now()->addHour()
);
})
->open($video->hls_master_path);
return $playlist->toResponse(request());
}
Why this is worth doing:
See URL Resolvers for the full DynamicHLSPlaylist and DynamicDASHManifest API.
HEVC compresses better than H.264. Use cbcs for newer devices:
$media = Media::make('videos', 'hevc_video.mp4');
$packager->open(MediaCollection::make([$media]));
// Use cbcs for HEVC (better for newer devices)
$encryptionKey = $packager->withAESEncryption('hevc.key', 'cbcs');
$packager->addStream([
'in' => $media->getLocalPath(),
'stream' => 'video',
'output' => 'hevc_encrypted.mp4',
]);
AV1 is a modern, royalty-free codec with strong compression:
$media = Media::make('videos', 'av1_video.mp4');
$packager->open(MediaCollection::make([$media]));
// AV1 works with all protection schemes
$encryptionKey = $packager->withAESEncryption('av1.key', 'cenc');
$packager->addStream([
'in' => $media->getLocalPath(),
'stream' => 'video',
'output' => 'av1_encrypted.mp4',
]);
A protection scheme controls how encryption is applied to segments, and which players and devices can decrypt them.
| Scheme | Best for | Compatible with |
|---|---|---|
cbc1 (default) |
HLS, maximum browser compatibility | Safari, Chrome, Firefox, Edge, iOS, Android |
cbcs |
Newer platforms, better performance | iOS 10+, Android 7+, modern browsers |
cenc |
DASH (the standard scheme) | Most DASH players, EME-enabled browsers |
null (SAMPLE-AES) |
HLS without a protection scheme | HLS players, Apple devices |
Examples:
// cbc1 - default, most compatible
$encryptionKey = $packager->withAESEncryption('encryption.key', 'cbc1');
// cbcs - modern devices
$encryptionKey = $packager->withAESEncryption('encryption.key', 'cbcs');
// cenc - common encryption, the DASH standard
$encryptionKey = $packager->withAESEncryption('encryption.key', 'cenc');
// null - SAMPLE-AES, HLS-specific
$encryptionKey = $packager->withAESEncryption('hls.key', null);
Every method above also accepts a Foxws\Shaka\Support\ProtectionScheme enum case (ProtectionScheme::Cbc1, ::Cbcs, ::Cenc, ::Cens) instead of a raw string, if you'd rather avoid typos in the scheme name.
Package several codecs with a single shared encryption key:
$h264 = Media::make('videos', 'h264.mp4');
$hevc = Media::make('videos', 'hevc.mp4');
$av1 = Media::make('videos', 'av1.mp4');
$collection = MediaCollection::make([$h264, $hevc, $av1]);
$packager->open($collection);
// One key for all codecs (with an optional label for organization)
$encryptionKey = $packager->withAESEncryption('master.key', 'cbc1', 'multi');
// Add streams for each codec
$packager->addStream([
'in' => $h264->getLocalPath(),
'stream' => 'video',
'output' => 'h264_1080p.mp4',
]);
$packager->addStream([
'in' => $hevc->getLocalPath(),
'stream' => 'video',
'output' => 'hevc_1080p.mp4',
]);
$packager->addStream([
'in' => $av1->getLocalPath(),
'stream' => 'video',
'output' => 'av1_1080p.mp4',
]);
// All streams are encrypted with the same key
$result = $packager->export();
For more advanced setups, give each codec its own key:
// H.264 with its own key
$packagerH264 = Packager::create();
$packagerH264->open(MediaCollection::make([Media::make('videos', 'h264.mp4')]));
$keyH264 = $packagerH264->withAESEncryption('h264.key');
// HEVC with its own key
$packagerHevc = Packager::create();
$packagerHevc->open(MediaCollection::make([Media::make('videos', 'hevc.mp4')]));
$keyHevc = $packagerHevc->withAESEncryption('hevc.key');
// AV1 with its own key
$packagerAv1 = Packager::create();
$packagerAv1->open(MediaCollection::make([Media::make('videos', 'av1.mp4')]));
$keyAv1 = $packagerAv1->withAESEncryption('av1.key');
// Each codec now has its own, unique encryption key
A complete HLS packaging example with encryption:
$media = Media::make('videos', 'video.mp4');
$packager->open(MediaCollection::make([$media]));
// Generate encryption key
$encryptionKey = $packager->withAESEncryption('encryption.key', 'cbc1');
// Add video variants
$packager->builder()
->addVideoStream($media->getLocalPath(), 'video_1080p.m3u8', ['bandwidth' => '5000000'])
->addVideoStream($media->getLocalPath(), 'video_720p.m3u8', ['bandwidth' => '3000000'])
->addAudioStream($media->getLocalPath(), 'audio.m3u8', ['language' => 'en'])
->withHlsMasterPlaylist('master.m3u8');
$result = $packager->export();
// The encryption key is referenced in the HLS playlist.
// Players fetch 'encryption.key' to decrypt segments.
A complete DASH packaging example with encryption:
$media = Media::make('videos', 'video.mp4');
$packager->open(MediaCollection::make([$media]));
// Use cenc for DASH
$encryptionKey = $packager->withAESEncryption('encryption.key', 'cenc');
$packager->builder()
->addVideoStream($media->getLocalPath(), 'video_1080p.mp4', ['bandwidth' => '5000000'])
->addVideoStream($media->getLocalPath(), 'video_720p.mp4', ['bandwidth' => '3000000'])
->addAudioStream($media->getLocalPath(), 'audio.mp4', ['language' => 'en'])
->withMpdOutput('manifest.mpd');
$result = $packager->export();
The encryption key is stored in two places:
Cache storage (RAM disk when available) — fast, temporary storage used while the key is generated.
/dev/shm on Linux, or your system's temp directory otherwise.PACKAGER_CACHE_FILES_ROOT environment variable.Export directory — a copy that goes out with the rest of the packaging output, for upload to cloud storage.
$keyFilename parameter.$encryptionKey = $packager->withAESEncryption('my-custom-key.bin');
// Key is in cache: /dev/shm/random-hash/my-custom-key.bin
// Key is in export: /tmp/packager-temp/random-hash/my-custom-key.bin
// Both copies contain identical key data
echo $encryptionKey->filePath; // Cache path
echo $encryptionKey->key; // Hex-encoded 128-bit key
echo $encryptionKey->keyId; // Hex-encoded key ID
In production, store keys in a private S3 bucket and use setKeyUrlResolver() to generate signed URLs on demand, rather than exposing the bucket publicly:
use Foxws\Shaka\Http\DynamicHLSPlaylist;
use Illuminate\Support\Facades\Storage;
// In your controller
public function streamVideo(Video $video)
{
$this->authorize('view', $video);
$playlist = (new DynamicHLSPlaylist('s3'))
->setKeyUrlResolver(function ($keyFilename) use ($video) {
// Generate a fresh signed URL for each key request
return Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$keyFilename}",
now()->addHour()
);
})
->setMediaUrlResolver(function ($segmentFilename) use ($video) {
// Also sign segment URLs, so the whole chain stays private
return Storage::disk('s3')->temporaryUrl(
"videos/{$video->id}/{$segmentFilename}",
now()->addHours(2)
);
})
->open($video->hls_master_path);
return $playlist->toResponse(request());
}
Why this is worth doing:
Confirm your input video is actually encoded with the codec you expect:
ffmpeg -i video.mp4
# Look for "Video: h264" or "Video: hevc" or "Video: av1"
Different devices support different protection schemes:
| Device/player | Recommended scheme |
|---|---|
| Safari/iOS | cbc1 or null (SAMPLE-AES) |
| Chrome/Android | cbc1, cbcs, or cenc |
| DASH players | cenc |
| HLS players | cbc1 or null |
Make sure the key file is copied into your export directory:
// The package copies the key for you automatically
$encryptionKey = $packager->withAESEncryption('encryption.key');
// The key is now in both the cache and export temp directories.
// When you export/upload, the key file is included automatically.
See the Troubleshooting guide for more issues and solutions.
/**
* Enable AES-128 encryption with auto-generated keys.
*
* When used with withKeyRotationDuration(), the filename becomes a base name
* (e.g., 'key' becomes 'key_0.key', 'key_1.key', 'key_2.key', etc.).
*
* @param string $keyFilename Base name for key file (default: 'key')
* @param ProtectionScheme|string|null $protectionScheme 'cbc1', 'cbcs', 'cenc', 'cens', or null for SAMPLE-AES
* @param string|null $label Optional label for multi-key scenarios
*/
public function withAESEncryption(
string $keyFilename = 'key',
ProtectionScheme|string|null $protectionScheme = 'cbc1',
?string $label = null
): EncryptionKey
/**
* Enable key rotation for encryption.
*
* @param int $seconds Duration in seconds before rotating to a new key
* @return self
*/
public function withKeyRotationDuration(int $seconds): self
EncryptionKey is a readonly value object with three properties:
final readonly class EncryptionKey
{
public string $key;
public string $keyId;
public ?string $filePath;
}
PackagerResult::getEncryptionKeys() returns an array of Foxws\Shaka\Support\EncryptionKeyFile value objects, each with path, filename, and content (hex-encoded) properties.