Custom key acquisition for encrypted HLS in VideoJS

The basic problem is fairly simple: intercept the key acquisition XHR request, update URL and/or add authorization header. This way people won’t be able to just simply put the .m3u8 files into their video players and simply play it. This is a solution for people who want a little more protection for their EHLS videos while sacrificing native behaviour is acceptable.

Please note that browsers without Media Source Extension might have difficulties working with this solution (like iOS safari).

What do we have for starter

We have our index.m3u8 file that contains 3 quality levels with bitrate and dimensions for each:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1600000,RESOLUTION=854x480
sd/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3200000,RESOLUTION=1280x720
hd/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5300000,RESOLUTION=1920x1080
fhd/index.m3u8

Here is a quality level manifest file (the end was truncated):

# EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="key://0.key",IV=0x9C2403588BE41A691A73E686170DD85A
#EXTINF:5.760000,
index0.ts
#EXTINF:5.760000,
index1.ts
#EXT-X-KEY:METHOD=AES-128,URI="key://2.key",IV=0x372F336A8156359866D2AA7419A9B0F3
#EXTINF:3.840000,
index2.ts
...

Initalizing the player

So, here is the basic code:

When you try to run this one you should encounter something like this (which is okay for now):

Failed to load key://0.key: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.

Adding XHR interceptor

  • player.ready will fire when the player is ready but the manifest file is not yet been downloaded. This is important, becase VideoJS will only load http-streaming module (HLS) after the main manifest finished downloading.
  • player.on("loadedmatadata", (e) => {})is too late because it gets triggered after the selected quality manifest has been loaded and right after that the first key might get loaded instatly based on your settings (poster, preload etc).
  • player.on("loadstart") is being triggered after http-streaming module is loaded therefore the XHR object will be available trough player.tech().hls property.

Finding the right events might be tricky because there is no diagram how the internals work in VideoJS but you can check out all the events in the source here (just search for @event): https://github.com/videojs/video.js/blob/master/src/js/player.js

Conclusion

We also tried HLS.JS which is a more modern solution and we did the same XHR thing but the audio was (for some reason) broken.

If you have any comment, correction or anytign to add to this article please let me know.

Choosing the unknown

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store