Protone Media logo

Encrypted HLS with Laravel FFMpeg: Protect your videos with AES-128 Encryption

The Laravel FFmpeg package has supported HLS for over four years. It's a fantastic protocol to provide on-demand, adaptive streaming. Initially developed by Apple in the early days of iOS, it is now the most popular streaming format, and it works on Android and virtually every other browser as well. With the Laravel package, you can convert a single video into an HLS export that contains multiple bitrates and resolutions. This technique allows the browser or video player to automatically switch between formats depending on the network's bandwidth.

This week we released version 7.5, which brings support for Encrypted HLS. An HLS export consists of many segments: small pieces of video content that'll be stitched together at playback time. Now you can encrypt all segments automatically, either with a single-key or with multiple auto-generated keys. As the segments are unplayable without the encryption keys, you can put the keys behind authentication and authorization to limit the accessibility of your video content.

Adding single-key encryption to an HLS export is just two extra lines of code:

use ProtoneMedia\LaravelFFMpeg\Exporters\HLSExporter;

$encryptionKey = HLSExporter::generateEncryptionKey();

FFMpeg::open('video.mp4')
    ->exportForHLS()
    ->withEncryptionKey($encryptionKey)
    ->addFormat($lowBitrate)
    ->addFormat($midBitrate)
    ->addFormat($highBitrate)
    ->save('encrypted_video.m3u8');

If you want to use multiple keys, you need to store the auto-generated keys with a callback:

FFMpeg::open('video.mp4')
    ->exportForHLS()
    ->withRotatingEncryptionKey(function ($filename, $contents) {
        Storage::disk('secrets')->put($filename, $contents);

        // or...

        DB::table('hls_secrets')->insert([
            'filename' => $filename,
            'contents' => $contents,
        ]);
    })
    ->addFormat($lowBitrate)
    ->addFormat($midBitrate)
    ->addFormat($highBitrate)
    ->save('encrypted_video.m3u8');

You might be wondering: how does the browser handle the playback when the media segments and encryption keys are stored on different disks? We provide a tool for that as well. Let's take a look at the DynamicHLSPlaylist class.

HLS uses playlists, which hold references to all different segments and encryption keys. The DynamicHLSPlaylist class modifies these playlists on-demand and specifically for your application. It basically provides a way to route the encryption keys through your Laravel application. You can now perform additional logic, like authentication and authorization, before delivering the encryption key to the player. The media segments can still be served by the webserver or through a CDN without ever touching the Laravel application. You can read more about this feature in the documentation.

return FFMpeg::dynamicHLSPlaylist()
    ->fromDisk('public')
    ->open('encrypted_video.m3u8')
    ->setKeyUrlResolver(function ($key) {
        return route('video.key', ['key' => $key]);
    })
    ->setMediaUrlResolver(function ($segment) {
        return Storage::disk('public')->url($segment);
    })
    ->setPlaylistUrlResolver(function ($playlist) {
        return route('video.playlist', ['playlist' => $playlist]);
    });

Other features of the 7.5 release include support for PHP 8.0 and raw process output, which is great for use-cases like volume analysis. I'll do a live demonstration of the Encrypted HLS feature on YouTube, scheduled for December 24 at 13:30 CET.

Related posts

Want to stay up-to-date on Laravel news and packages?

Pascal Baljet on Twitter

Follow me on Twitter: @pascalbaljet

Pascal Baljet on Twitter

Subscribe to my YouTube channel

© 2013-2021 Protone Media B.V.