Skip to content
DRM & Security

Encrypted Media Extensions (EME) Explained for Developers

How Encrypted Media Extensions let web players use Widevine, PlayReady and FairPlay. The full EME flow, code examples, robustness levels and common pitfalls.

Encrypted Media Extensions (EME) Explained for Developers
On this page 12 sections

Before 2013, protected video on the web meant plug-ins: Flash Access, Microsoft Silverlight with PlayReady, or custom browser extensions. When plug-ins were phased out, the web needed a standard way for a page to play encrypted video without seeing the keys. The answer was Encrypted Media Extensions (EME), a W3C Recommendation since 2017, now supported in every major browser.

This guide explains EME from a developer’s point of view: what it does and does not do, the objects involved, a full playback flow with code, how robustness levels map to DRM security levels, and the mistakes that cause most “DRM won’t play” bugs.

What EME is (and is not)

EME is a JavaScript API that connects three things:

  1. Your player: the JavaScript running on your page.
  2. The browser’s media pipeline: usually fed through Media Source Extensions (MSE).
  3. A Content Decryption Module (CDM): the closed DRM component, such as the Widevine CDM, PlayReady in Edge or FairPlay in Safari.

EME defines how the player asks for a DRM system, how it passes licence messages between the CDM and a licence server, and how the CDM reports key status. It does not define the licence protocol, the encryption format, or how the CDM protects keys. Those belong to the DRM system and to the Common Encryption standard.

That design keeps the page in control of networking and UI, while the secret work stays inside the CDM.

The main EME objects

Object What it represents
MediaKeySystemAccess Permission to use a particular key system with a particular configuration
MediaKeys A set of keys attached to a media element
MediaKeySession One licence exchange and the keys it produced
HTMLMediaElement.setMediaKeys() Attaches keys to a <video> element
encrypted event Fired when the media contains encryption initialisation data
message event Fired when the CDM wants to send a message (usually a licence request)
keystatuseschange event Fired when keys become usable, expire or are restricted

The EME flow step by step

1. Ask for a key system

const config = [{
  initDataTypes: ['cenc'],
  videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.640028"', robustness: 'SW_SECURE_DECODE' }],
  audioCapabilities: [{ contentType: 'audio/mp4; codecs="mp4a.40.2"', robustness: 'SW_SECURE_CRYPTO' }],
  persistentState: 'optional',
  sessionTypes: ['temporary'],
}];
const access = await navigator.requestMediaKeySystemAccess('com.widevine.alpha', config);

If the browser cannot satisfy the configuration, the promise rejects with NotSupportedError. This is how players detect DRM support, and it is exactly what our DRM support checker does for each system and robustness level.

Common key system strings:

  • com.widevine.alpha: Widevine
  • com.microsoft.playready.recommendation: PlayReady (modern string, with robustness levels)
  • com.apple.fps: FairPlay Streaming in Safari (older code uses the prefixed WebKitMediaKeys API and com.apple.fps.1_0)
  • org.w3.clearkey: ClearKey, for testing

2. Create MediaKeys and attach them

const mediaKeys = await access.createMediaKeys();
await video.setMediaKeys(mediaKeys);

For FairPlay, you usually also call mediaKeys.setServerCertificate() with your FairPlay application certificate before creating sessions.

3. Get the initialisation data

When the player feeds encrypted media to the video element, the browser fires an encrypted event containing initDataType (for example cenc) and initData (the pssh boxes). Many players do not wait for this event. They read the pssh or key ID straight from the DASH manifest or HLS playlist, which is faster.

4. Create a session and generate a request

const session = mediaKeys.createSession('temporary');
session.addEventListener('message', async (e) => {
  const res = await fetch(LICENSE_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/octet-stream', Authorization: 'Bearer ' + playbackToken },
    body: e.message,
  });
  await session.update(new Uint8Array(await res.arrayBuffer()));
});
await session.generateRequest(initDataType, initData);

The CDM produces an opaque licence request in the message event. Your code sends it to the licence server, along with your own authentication (a token, cookie or header). The server’s response goes back into session.update(). The keys never leave the CDM.

5. Watch key status

session.addEventListener('keystatuseschange', () => {
  session.keyStatuses.forEach((status, keyId) => console.log(status));
});

Statuses include usable, expired, output-restricted (for example, HDCP is not available for this key) and internal-error. A player should react, for instance by switching to a lower rendition when a key for the 4K track is output-restricted.

6. Play

Once keys are usable, the media pipeline decrypts and plays. Most of the time you will not write steps 1 to 5 yourself. Shaka Player, dash.js, hls.js, Video.js with its EME plugin and commercial players wrap them. It still pays to understand them, because the errors you see in production map directly onto these steps.

Robustness levels

The robustness field is how a page tells the CDM what security level it requires. For Widevine the strings are:

Robustness Meaning Rough equivalent
SW_SECURE_CRYPTO Software crypto L3
SW_SECURE_DECODE Software crypto and decode L3
HW_SECURE_CRYPTO Hardware crypto L2
HW_SECURE_DECODE Hardware crypto and decode L1
HW_SECURE_ALL Hardware crypto, decode and everything else L1

For PlayReady with the recommendation key system, the robustness values are 150, 2000 and 3000, corresponding to security levels SL150, SL2000 and SL3000.

Leaving robustness empty works but triggers a console warning in Chrome, and you lose the chance to choose a security level on purpose. A common strategy is to request HW_SECURE_ALL first, fall back to SW_SECURE_DECODE, and tell your licence server which one succeeded so it can apply the right quality policy. The relationship between these levels and streaming quality is covered in Widevine L1 vs L2 vs L3.

Persistent licences and offline playback

EME supports a persistent-license session type, which lets a CDM store a licence and use it later without a network connection. Support is limited. Widevine persistent licences work on Android and ChromeOS but are generally not available to web pages on desktop Chrome. Most offline viewing therefore happens in native apps rather than browsers.

ClearKey: the testing key system

Every browser that supports EME also supports ClearKey. The licence is a small JSON object containing the key in plain base64. It gives no real protection, because the key is visible to JavaScript, but it is perfect for testing your packaging and player logic before you connect a real DRM vendor. If your content plays with ClearKey, your encryption and manifests are probably correct.

Common EME pitfalls

  • Serving over HTTP. EME only exists in secure contexts. navigator.requestMediaKeySystemAccess is undefined on plain HTTP pages.
  • Iframes without permission. If your player runs in an iframe, add allow="encrypted-media" to the iframe tag, or the call will fail.
  • Codec strings that do not match the content. The contentType in your configuration must describe what you will actually play. Asking for HEVC on a browser without HEVC support fails even if DRM is fine.
  • Forgetting FairPlay’s certificate. Safari will not produce a licence request without a server certificate.
  • Missing CORS headers on the licence server. The licence request is a cross-origin fetch, so the server must return the right Access-Control-Allow-* headers.
  • Mixing cenc and cbcs. FairPlay requires cbcs. If you package only in cenc, Safari will not play it.
  • Not handling output-restricted. Without a fallback, the player freezes when a user connects a non-HDCP monitor. See HDCP explained.
  • Private browsing. Some browsers limit or disable persistent state in private windows, which can break DRM that requires it.

EME and privacy

EME was controversial when it was standardised, because it brought closed components into the open web. Browsers responded with safeguards. CDMs run in sandboxes, EME is limited to secure origins, and per-origin identifiers are used so that a CDM cannot become a cross-site tracking cookie. Users can clear those identifiers with other site data, and Firefox lets users disable DRM entirely.

How EME fits the bigger picture

EME is one layer in a stack:

  1. Encoding and packaging: your video is encoded and encrypted with Common Encryption, usually as CMAF.
  2. Delivery: HLS or DASH manifests describe the segments and signal the DRM.
  3. Playback: MSE feeds segments to the browser and EME connects the CDM.
  4. Licensing: licence servers, typically from a multi-DRM provider, decide who gets keys.

When something fails, knowing which layer failed saves hours. A manifest error is not a DRM error, and a licence server 403 is not a codec problem.

Debugging EME in the browser

When DRM playback fails, these tools help:

  • Chrome’s media internals. Open chrome://media-internals in a new tab, start playback, and look at the player’s event log. It shows the key system, CDM errors and decoder problems.
  • Edge’s equivalent is edge://media-internals. Firefox exposes some details in about:support under Media.
  • Network tab. Filter for your licence server URL. Check the request is sent, the response status is 200, and the response body is binary licence data, not an HTML error page.
  • Console warnings. Chrome warns when robustness is empty and when configurations are not supported.
  • Shaka Player’s debug build logs every EME call if you use Shaka. Other players have similar debug modes.

A useful habit is to test with ClearKey first. If ClearKey plays and Widevine fails, the problem is on the licence side. If ClearKey also fails, look at packaging, codecs and manifests.

A minimal ClearKey test page

If you have never seen EME work end to end, a ClearKey test is the fastest way to build intuition. You need three things: a short video encrypted with a known key using Common Encryption, a DASH manifest or fMP4 file pointing to it, and a player configured with the key.

With Shaka Player the configuration is a few lines:

player.configure({ drm: { clearKeys: { '<key id in hex>': '<key in hex>' } } });
await player.load('https://example.com/test/manifest.mpd');

Shaka calls requestMediaKeySystemAccess('org.w3.clearkey'), builds a JSON licence from your keys and hands it to the browser, exactly as it would with a real licence server. Open chrome://media-internals alongside and you can watch the key session open and the keys become usable.

Once that works, switching to Widevine is mostly a matter of replacing clearKeys with a licence server URL and adding your authentication header. If the switch fails, you know the problem is on the licence side, not in your packaging. Our DRM support checker confirms which key systems a test machine offers before you start. For how licensing works across systems, see what is multi-DRM.

Summary

Encrypted Media Extensions is the browser API that lets JavaScript players use DRM without seeing keys. The player asks for a key system with requestMediaKeySystemAccess, attaches MediaKeys, relays licence messages from a MediaKeySession to a licence server, and watches key statuses. Robustness strings map to security levels, and most bugs come from HTTPS, CORS, codec strings, iframes and encryption mode mismatches. Use ClearKey to test, and a real DRM for production.

Frequently asked questions

Is EME the same as DRM?

No. EME is a browser API. It lets a web page talk to a DRM system's content decryption module, but it does not provide protection by itself. The DRM (Widevine, PlayReady, FairPlay) does.

Does EME work over http?

No. Browsers only expose EME in secure contexts, so the page must be served over HTTPS (or from localhost during development).

Can I use EME without a DRM vendor?

For testing, yes, using the ClearKey key system that every browser supports. For real protection you need a licence server for Widevine, PlayReady or FairPlay, usually through a vendor.

Keep reading