EME at a glance
Encrypted Media Extensions (EME) is a browser API that lets web applications talk to content protection systems so they can play encrypted audio and video. It is an extension to the HTMLMediaElement spec, which means browser support is optional: a browser can be fully spec-compliant without supporting EME, but if it doesn't, it simply can't play encrypted media.
EME aims to make both the application and the encrypted files portable across browsers and protection systems. The API flow is standardized, and the concept of Common Encryption lets the same encrypted content work with different DRM mechanisms. EME itself does not define any content protection system except Clear Key, which is mandatory for EME-supporting browsers.
Four external pieces interact in an EME implementation:
- Key System — A content protection (DRM) mechanism. Apart from Clear Key, EME leaves Key System implementations to third parties.
- Content Decryption Module (CDM) — A client-side software or hardware component that decrypts media. EME provides the interface for apps to reach CDMs but does not define how they work.
- License (Key) server — Supplies keys to the CDM. License negotiation is the application's responsibility, not EME's.
- Packaging service — Encodes and encrypts media before distribution.
Authentication is likewise outside EME's scope. An app like Netflix handles user login itself, then uses EME only to fetch keys for playback once the user's identity and entitlements are known.
The key acquisition flow
EME's operation follows a fixed sequence of events between the app, the browser, the CDM and the license server:
- The app tries to play audio or video that has encrypted streams.
- The browser detects the encryption and fires an
encryptedevent carryinginitDatametadata from the media. - The app handles the event. If no
MediaKeysobject is attached to the media element yet, it callsnavigator.requestMediaKeySystemAccess()to find an available Key System, gets aMediaKeySystemAccessobject, and creates aMediaKeysinstance representing a CDM and its key storage. - The app associates the
MediaKeysobject with the media element viasetMediaKeys()so its keys can be used during decoding. - The app creates a
MediaKeySessionby callingcreateSession()on theMediaKeysobject. - The app calls
generateRequest()on the session, passing theinitData, which causes the CDM to emit amessageevent containing a license request. - The app forwards that license request to the license server, typically via XHR.
- The app receives the license response and gives it to the CDM using the session's
update()method. - The CDM extracts a key (identified by Key ID) from the license, checks its policy, decrypts the media, and playback proceeds.
Multiple request/response round trips can happen between CDM and license server. All this communication is opaque to the browser and the app — only the CDM and license server understand the payloads, though the app can see the message type. The license request includes proof of the CDM's trust relationship and a key used to encrypt the content keys in the returned license.
Note that MediaKeys initialization should happen before the first encrypted event fires, and the app determines the license server URL independently of Key System selection. Also, the keys in a session can come from any valid license under the same MediaKeys for that media element.
What CDMs do and how they ship
EME itself does no decryption; it only exposes a MediaKeys API to CDMs. How a CDM actually decrypts is entirely outside the spec. CDMs can operate at different levels of the pipeline, from least to most robust:
- Decryption only, letting the normal media pipeline (for example a
<video>element) handle decoding. - Decryption plus decoding, handing rendered frames to the browser.
- Decryption, decoding and rendering entirely in hardware such as a GPU.
CDMs can reach the browser in different ways: bundled with it, distributed separately, built into the OS, included in firmware, or embedded in hardware. The EME spec doesn't prescribe any of these, but the browser is always responsible for vetting and exposing whatever CDMs it makes available. In practice, different browsers support different Key Systems — Chrome supports Widevine while IE11 supports PlayReady, for example.
Clear Key and Common Encryption
The Clear Key system is EME's only mandated Key System. It encrypts media with a key that is then supplied directly to the browser for playback — no license server, no separate CDM. Clear Key is fully interoperable across all EME-supporting browsers, which makes it useful for testing EME implementations and applications without needing a real license server. It is unlikely to protect commercial content, but for development and verification it removes the dependency on third-party DRM.
var video = document.querySelector('video');
var config = [{initDataTypes: ['webm'],
videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}]}];
if (!video.mediaKeys) {
navigator.requestMediaKeySystemAccess('org.w3.clearkey',
config).then(
function(keySystemAccess) {
var promise = keySystemAccess.createMediaKeys();
promise.catch(
console.error.bind(console, 'Unable to create MediaKeys')
);
promise.then(
function(createdMediaKeys) {
return video.setMediaKeys(createdMediaKeys);
}
).catch(
console.error.bind(console, 'Unable to set MediaKeys')
);
promise.then(
function(createdMediaKeys) {
var initData = new Uint8Array([...]);
var keySession = createdMediaKeys.createSession();
keySession.addEventListener('message', handleMessage,
false);
return keySession.generateRequest('webm', initData);
}
).catch(
console.error.bind(console,
'Unable to create or initialize key session')
);
}
);
}
function handleMessage(event) {
var keySession = event.target;
var license = new Uint8Array([...]);
keySession.update(license).catch(
console.error.bind(console, 'update() failed')
);
}
To exercise the Clear Key code path (adapted from the spec examples), you need an encrypted video. For WebM, the webm_crypt instructions describe how to encrypt; commercial packaging services also exist for ISO BMFF/MP4.
// Define a key: hardcoded in this example
// – this corresponds to the key used for encryption
var KEY = new Uint8Array([
0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
]);
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(
function(keySystemAccess) {
return keySystemAccess.createMediaKeys();
}
).then(
function(createdMediaKeys) {
return video.setMediaKeys(createdMediaKeys);
}
).catch(
function(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(
function(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(
function(error) {
console.error('Failed to update the session', error);
}
);
}
// Convert Uint8Array into base64 using base64url alphabet, without padding.
function toBase64(u8arr) {
return btoa(String.fromCharCode.apply(null, u8arr)).
replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}
// This takes the place of a license server.
// kids is an array of base64-encoded key IDs
// keys is an array of base64-encoded keys
function generateLicense(message) {
// Parse the clearkey license request.
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]
}));
}
Common Encryption (CENC) solves the problem of re-packaging content for every DRM. It is an ISO standard (a similar concept applies to WebM) that lets a provider encrypt and package content once per container/codec, then serve it to any CDM that supports Common Encryption. A video packaged for PlayReady could, for instance, stream to a browser whose Widevine CDM obtains a key from a Widevine license server. This is the opposite of legacy vertical-stack DRM, which tied content to one client and often to a proprietary runtime.
Why Media Source Extensions matters
Encrypted playback frequently goes hand in hand with adaptive streaming, and that's where Media Source Extensions (MSE) comes in. MSE is another HTMLMediaElement extension that lets JavaScript feed media to the element in chunks rather than handing it a complete file via a src URL.
<video src='foo.webm'></video>
The advantage for commercial providers is control: they can react to network conditions by switching bitrates mid-stream, as Netflix does. EME works with MSE-supplied streams exactly as it does with a plain src attribute — encryption is independent of how the media arrives.
A minimal MSE setup first creates a SourceBuffer for a media element:
var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
Then each video chunk is appended one after another to build the full stream:
reader.onload = function (e) {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
if (i === NUM_CHUNKS - 1) {
mediaSource.endOfStream();
} else {
if (video.paused) {
// start playing after first chunk is appended
video.play();
}
readChunk_(++i);
}
};
A simple MSE demonstration is available at simpl.info/mse, and a more complete explanation of stream chunking and bitrate switching lives in the HTML5 Rocks article on MSE.
DASH: The companion standard for adaptive streaming
Modern video delivery rarely happens over a stable, high-bandwidth connection. Users switch between Wi-Fi and cellular networks, move through congested areas, and use devices with widely varying display capabilities. To handle this, content providers need a way to adapt the bitrate of the media they serve in real time.
MPEG-DASH (Dynamic Adaptive Streaming over HTTP) is an open standard designed for exactly this purpose. It competes with proprietary alternatives like Apple's HTTP Live Streaming (HLS) and Microsoft's Smooth Streaming, but stands apart as the only HTTP-based adaptive bitrate method built on an open standard. Major platforms, including YouTube, already rely on it.
DASH's relationship with EME and MSE is direct: an MSE-based DASH client parses a manifest, downloads video segments at the most appropriate bitrate, and feeds them to a hungry <video> element using standard HTTP infrastructure. This enables commercial content providers to combine adaptive streaming with DRM-protected content, all within the browser.
The technology's four-part name describes its behavior:
- Dynamic: it responds to changing network conditions as they happen.
- Adaptive: it selects an appropriate audio or video bitrate for current circumstances.
- Streaming: it supports both live streaming and progressive download.
- HTTP: it rides on ordinary web servers instead of requiring specialized streaming infrastructure.
The workflow behind DASH is straightforward:
- Source media is encoded at multiple bitrates.
- Those encoded files are hosted on a plain HTTP server.
- A client-side web app decides which bitrate to request and plays it via DASH.
During the segmentation step, an XML manifest called a Media Presentation Description (MPD) is generated programmatically. It defines Adaptation Sets and Representations, along with durations and URLs. A sample MPD from the YouTube DASH demo player shows the structure:
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" mediaPresentationDuration="PT0H3M1.63S" minBufferTime="PT1.5S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
type="static">
<Period duration="PT0H3M1.63S" start="PT0S">
<AdaptationSet>
<ContentComponent contentType="video" id="1" />
<Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
<BaseURL>car-20120827-89.mp4</BaseURL>
<SegmentBase indexRange="674-1149">
<Initialization range="0-673" />
</SegmentBase>
</Representation>
<Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
<BaseURL>car-20120827-88.mp4</BaseURL>
<SegmentBase indexRange="708-1183">
<Initialization range="0-707" />
</SegmentBase>
</Representation>
…
</AdaptationSet>
</Period>
</MPD>
Although the DASH spec theoretically permits an MPD to be used directly as a video element's src, browser vendors have instead delegated DASH handling to JavaScript libraries built on MSE, such as dash.js. This design choice keeps the adaptation algorithm flexible—it can evolve without browser updates—and allows experimentation with alternative manifest formats and delivery methods. Google's Shaka Player likewise implements a DASH client with native EME support. Mozilla Developer Network provides guidance on segmenting video and constructing an MPD using WebM tools and FFmpeg.
Why DRM support in the browser matters now
The shift toward paid video over the web shows no signs of slowing. Well over 85% of mobile and desktop browsers support native <video> and <audio> playback, and industry estimates projected video to account for 80–90% of global consumer internet traffic by 2017. Meanwhile, browser vendors are steadily removing support for the plugin APIs that most legacy media players depend on. As that happens, standards-based pathways for protected content delivery—EME, MSE, and DASH working together—become increasingly central to the web's media ecosystem.
Resources for going deeper
Specifications and standards:
- EME spec: the latest Editor's Draft
- Common Encryption (CENC), ISO standard
- Media Source Extensions, W3C
- The ISO/IEC 23009-1 DASH standard (available as a PDF)
- DASH Industry Forum overview of the standard
Articles and demos:
- "What is EME?" by Henri Sivonen
- HTML5 Rocks' Media Source Extensions article
- BBC R&D's MPEG-DASH test streams blog post
- A DTG webinar covering related topics (now partially obsolete)



