Encrypted Media Extensions

Digital rights management (DRM) is a systematic approach to copyright protection for digital media. The purpose of DRM is to prevent unauthorized redistribution of digital media and restrict the ways consumers can copy content they have purchased. The Encrypted Media Extensions (EME) API provides interfaces for controlling the playback of DRM content.

To encrypt a webm video file, use webm-encrypt:

npm install --global webm-encrypt # Encrypt a webm file. Generate 16-byte output_keyfile.key automatically. webme -i input.webm -o output.webm # Encrypt a webm file with a specific key webme -i input.webm -o output.webm -k keyfile.key # Decrypt a webm file webme -d -i encrypted.webm -o decrypted.webm -k keyfile.key Note on keys: The WebM Encryption Specification states that the key size for encryption must be 128 bit*. Consequently, the key file used must be 16 bytes (128 bits) of binary data.
In this simple example, the source file and clear-text license are hard-coded in the page. Only one session will ever be created (Clear Key).
RESETRUNFULL
<!DOCTYPE html><html>
<body style="height:300px">
<video src='/shared/movie_encrypted.webm' controls></video>
<script>

// Define a key: hardcoded in this example – this corresponds to the key generated, ie. output_keyfile.key
// Changing any value causes the decryption to fail.
var KEY = new Uint8Array([
  0x20, 0x9e, 0x89, 0xd1, 0x19, 0x97, 0x85, 0xb5,
  0xf2, 0xf0, 0x64, 0xbf, 0x28, 0xcf, 0xf5, 0x5f
]);

var config = [{
   initDataTypes: ['webm'],
   videoCapabilities: [{
      contentType: 'video/webm; codecs="vp8"'
   }]
}];

var video = document.querySelector('video');
video.addEventListener('encrypted', handleEncrypted, false);

navigator.requestMediaKeySystemAccess('org.w3.clearkey', config).then(
   keySystemAccess => keySystemAccess.createMediaKeys()
).then(
   createdMediaKeys => video.setMediaKeys(createdMediaKeys)
).catch(
   error => console.error('Failed to set up MediaKeys', error)
);

function handleEncrypted(event) {
   var session = video.mediaKeys.createSession();
   session.addEventListener('message', handleMessage, false);
   session.generateRequest(event.initDataType, event.initData).catch(
      error => console.error('Failed to generate a license request', error)
   );
}

function handleMessage(event) {
   // If you had a license server, you would make an asynchronous XMLHttpRequest with event.message as the body. The response from the server, as a
   // Uint8Array, would then be passed to session.update(). Instead, we will generate the license synchronously on the client, using the hard-coded KEY at the top.
   var license = generateLicense(event.message);
   var session = event.target;
   session.update(license).catch(
      error => console.error('Failed to update the session', error)
   );
}

function toBase64(u8arr) {
  return btoa(String.fromCharCode.apply(null, u8arr)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

function generateLicense(message) {
   var request = JSON.parse(new TextDecoder().decode(message));
   // We only know one key, so there should only be one key ID. A real license server could easily serve multiple keys.
   console.assert(request.kids.length === 1);
   var keyObj = {
      kty: 'oct',
      alg: 'A128KW',
      kid: request.kids[0],
      k: toBase64(KEY)
   };
   return new TextEncoder().encode(JSON.stringify({keys: [keyObj]}));
}

</script>
</body></html>