red line

How to Stream DRM-protected Video on the Web

Learn how to configure your player to acquire a license to decrypt encrypted video.
Video Player

Addressing the Problem

  • Problem – How can you serve videos on the web without letting the viewer copy them and serve them elsewhere?
  • Solution – Digital Rights Management (DRM) protects the video by requiring a valid license to play it.

Now the question is: how do we implement DRM on the web? Let’s break it down, and then we’ll do a demo.

Digital Rights Management (DRM)

Protecting video with DRM requires three parts:

  1. Video – the video is encrypted with a private key
  2. License – the license server provides a license to authenticated requests
  3. Player – the player requests the license from the license server (usually using some “token” to authenticate), and then uses the license to decrypt the video and play it.

There are multiple DRM technologies:

  • Fairplay – Apple – supported in Safari
  • Playready – Microsoft – supported in Edge
  • Widevine – Google – supported in Chrome, Firefox, and Android.

We will demo Widevine since has the widest support, but they all work similarly.

Before we get to a demo, let’s go over how the video is packaged and delivered to the browser.

Adaptive Bitrate (ABR) Streaming

DRM-protected videos are typically served using Adaptive Bitrate (ABR) Streaming, which has two parts:

  1. Server side – the video file is transcoded into multiple renditions (i.e resolutions), and a “manifest” file is created that lists all the options.
  2. Client side (the web page) – the video player reads the manifest and adjusts the resolution as needed to maintain continuous playback based on the viewer’s network conditions, i.e. slow network = low resolution, and vice versa.

There are multiple ABR technologies:

  • HTTP Live Streaming (HLS) – Apple
  • Smooth Streaming – Microsoft
  • Dynamic Adaptive Streaming over HTTP (DASH) – ISO Standard

We will demo DASH, since it is a generic standard, not owned by a single company, but they all work similarly. Also, it should be noted that the HTML video element does not directly support ABR streams, i.e. you can’t just set video.src = “http://my.site/my-DASH-manifest.mpd” and be done with it. You have to use JavaScript to manually send each ABR video chunk to the video element via the Media Source Extensions (MSE) API. Thankfully, many JavaScript libraries have been created to do exactly that, which allows us to skip over a lot of the nitty-gritty details, so that is what we will use in our demo. (The only exception to this is Safari does directly support HLS, so you can just set video.src = “http://my.site/my-HLS-manifest.m3u8”)

The ABR manifest file also lists the DRM needed to decrypt the video. A single manifest might contain multiple DRM types, which allows the player to select the DRM supported by the current browser.

Demo

Remember, to play DRM-protected video we need a Video, License, and Player. Encrypting the video and setting up a license server is outside the scope of this demo, so we will focus on the Player. Thankfully, Axinom provides encrypted videos and a license server for testing. Here are the three parts again:

  • A DRM-protected video – Axinom provides this to the public on GitHub.
  • A license server – Axinom provides this too on GitHub.
  • A JavaScript player library – Google provides the Shaka Player on GitHub. This library handles the nitty-gritty details of downloading the license and actually decrypting the video. We just tell it what video to play and what license server to use.

We will use the example from the Shaka Player tutorial as a starting point, and then add DRM.

				
					<!-- Include the Shaka Player js and css. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/3.2.0/shaka-player.compiled.js" integrity="sha512-9SpI4t+0FNlrOTC/bkahpYAV5gNXALQBZXk0zew1HQ9Am5iugZ7dgbIvNhL01GkyY+xDkdMagOAQizXUa2y/gQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<link data-minify="1" rel="stylesheet" href="https://www.soliddigital.com/wp-content/cache/min/1/ajax/libs/shaka-player/3.2.0/controls.min.css?ver=1711645550" crossorigin="anonymous" referrerpolicy="no-referrer">

<!-- Here is the video element that we will connect the Shaka Player to. -->
<video id="drm-demo" controls="" style="width: 100%"></video>

<script>
// These values are pulled from the Axinom GitHub page above.
const axManifestUri = 'https://media.axprod.net/TestVectors/v9-MultiFormat/Encrypted_Cenc/Manifest_1080p.mpd';
const axLicenseServer = "https://drm-widevine-licensing.axtest.net/AcquireLicense";
const axLicenseHeader = "X-AxDRM-Message";
const axLicenseToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiNjllNTQwODgtZTllMC00NTMwLThjMWEtMWViNmRjZDBkMTRlIiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsInZlcnNpb24iOjIsImxpY2Vuc2UiOnsiYWxsb3dfcGVyc2lzdGVuY2UiOnRydWV9LCJjb250ZW50X2tleXNfc291cmNlIjp7ImlubGluZSI6W3siaWQiOiJmOGM4MGMyNS02OTBmLTQ3MzYtODEzMi00MzBlNWM2OTk0Y2UiLCJlbmNyeXB0ZWRfa2V5IjoiaVhxNDlaODlzOGRDajBqbTJBN1h6UT09IiwidXNhZ2VfcG9saWN5IjoiUG9saWN5IEEifV19LCJjb250ZW50X2tleV91c2FnZV9wb2xpY2llcyI6W3sibmFtZSI6IlBvbGljeSBBIiwicGxheXJlYWR5Ijp7Im1pbl9kZXZpY2Vfc2VjdXJpdHlfbGV2ZWwiOjE1MCwicGxheV9lbmFibGVycyI6WyI3ODY2MjdEOC1DMkE2LTQ0QkUtOEY4OC0wOEFFMjU1QjAxQTciXX19XX19.k9OlwW0rUwuf5d5Eb0iO98AFR3qp7qKdFzSbg2PQj78";

function initApp() {
  // Install built-in polyfills to patch browser incompatibilities.
  shaka.polyfill.installAll();

  // Check to see if the browser supports the basic APIs Shaka needs.
  if (shaka.Player.isBrowserSupported()) {
    // Everything looks good!
    initPlayer();
  } else {
    // This browser does not have the minimum set of APIs we need.
    console.error('Browser not supported!');
  }
}

async function initPlayer() {
  // Create a Player instance.
  const video = document.getElementById('drm-demo');
  const player = new shaka.Player(video);

  // Listen for error events.
  player.addEventListener('error', onErrorEvent);
  
  // Set the widevine license server url.
  player.configure({
    drm: {
      servers: {
        'com.widevine.alpha': axLicenseServer,
      }
    }
  });
  
  // Add the license token to the license request.
  player.getNetworkingEngine().registerRequestFilter(function(type, request) {
    // Only add headers to license requests:
    if (type == shaka.net.NetworkingEngine.RequestType.LICENSE) {
      // This is the specific header name and value the server wants:
      request.headers[axLicenseHeader] = axLicenseToken;
    }
  });

  // Try to load a manifest.
  // This is an asynchronous process.
  try {
    await player.load(axManifestUri);
    // This runs if the asynchronous load is successful.
    console.log('The video has now been loaded!');
  } catch (e) {
    // onError is executed if the asynchronous load fails.
    onError(e);
  }
}

function onErrorEvent(event) {
  // Extract the shaka.util.Error object from the event.
  onError(event.detail);
}

function onError(error) {
  // Log the error.
  console.error('Error code', error.code, 'object', error);
}

document.addEventListener('DOMContentLoaded', initApp);
</script>
				
			

And here is our demo running:

Next Steps

I hope the above gave you a better understanding of DRM than you had before. Test your understanding by trying out the items below.
  • Remove DRM from the demo above to see what happens when you try to play the video.
  • The Axinom video supports Fairplay and Playready too. Use the Axinom Github page and the Shaka Player docs to add Fairplay and Playready support to our demo above.
Once you are comfortable with implementing DRM, you just might be ready to launch your own video service!
Related resources