Players
How to configure Shaka Player and Video.js to play your protected content.
What you need #
Rather not assemble it yourself?
Two things: the manifest URL and the license server URLs. The player picks the DRM system on its own; you just hand it the addresses.
- Widevine
https://drm.airi.live/v2/widevine- PlayReady
https://drm.airi.live/v2/playready- FairPlay
https://drm.airi.live/fairplay- WisePlay
https://drm.airi.live/v2/wiseplay
These URLs are public, and that is fine
If you have no packaged content yet, Encrypt your video has a script — Node and Python — that does the whole path: request keys, transcode with ffmpeg, encrypt in cbcs.
Shaka Player #
The most complete for DASH and HLS, and what we recommend if you are starting fresh.
import shaka from 'shaka-player/dist/shaka-player.compiled.js'; shaka.polyfill.installAll(); const video = document.querySelector('video'); const player = new shaka.Player(); await player.attach(video); player.configure({ drm: { servers: { 'com.widevine.alpha': 'https://drm.airi.live/v2/widevine', 'com.microsoft.playready': 'https://drm.airi.live/v2/playready', 'com.apple.fps': 'https://drm.airi.live/fairplay' } } }); await player.load('https://your-bucket.s3.amazonaws.com/movie/manifest.mpd'); video.play();
That is all for Widevine and PlayReady. FairPlay additionally needs the Apple certificate:
player.configure({ drm: { servers: { 'com.apple.fps': 'https://drm.airi.live/fairplay' }, advanced: { 'com.apple.fps': { // Certificate issued by Apple to your developer account serverCertificateUri: 'https://your-bucket.s3.amazonaws.com/fairplay.cer' } } } }); // FairPlay works over HLS only await player.load('https://your-bucket.s3.amazonaws.com/movie/master.m3u8');
Which system the browser picks, and why it matters
When a device has more than one, the player chooses on its own — and it does so before knowing what your rule will demand. Edge has both Widevine and PlayReady: it tends to pick Widevine, which on desktop is software-only (L3). If your rule asks for hardware the licence is refused, even though PlayReady could have served it.
It does not switch systems on its own
There are two ways to steer it. The first is to state a preference:
player.configure({ drm: { servers: { /* ... */ }, preferredKeySystems: ['com.microsoft.playready'] } });
The second is better when you want the browser to rule out the ones that cannot serve you: declare the robustness you need, and the browser itself rejects a system that cannot meet it, letting the player move to the next.
player.configure({ drm: { servers: { /* ... */ }, advanced: { 'com.widevine.alpha': { videoRobustness: 'HW_SECURE_DECODE' }, 'com.microsoft.playready': { videoRobustness: '3000' } } } });
On Windows, asking for SL3000 like this is not enough — and it fails in the worst way
com.microsoft.playready gets the software one, whose certificate is SL2000 however capable the machine is. Ask it for SL3000 and the licence server refuses — correctly, because the client you presented cannot reach it — on a machine that could have played.
The hardware client answers only when named, and the name is changed with keySystemsMapping, not preferredKeySystems: the latter only reorders candidates already in the manifest, and the manifest carries the base name. Prefer a name that is absent and Shaka falls back to manifest order, which can land you on a different DRM without a word.// Check the hardware client exists before asking for it. const hw = await navigator.requestMediaKeySystemAccess( 'com.microsoft.playready.recommendation.3000', [{ initDataTypes: ['cenc'], videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.640028"', robustness: '3000' }] }] ).then(() => true, () => false); if (hw) { player.configure('drm.keySystemsMapping', { 'com.microsoft.playready': 'com.microsoft.playready.recommendation.3000' }); }
And when there is no hardware client, withdraw the quality before requesting it
player.configure('restrictions.maxHeight', 720) before load(). PlayReady does not deny, it answers a server error, and recovering from that mid-load is an open Shaka problem (#5421, #2135): the quality is discarded but playback never starts. Every refused attempt is also a licence you are billed for.
One warning about that cap: leave out every variant — maxHeight 720 on content that only has 1080p, say — and Shaka throws RESTRICTIONS_CANNOT_BE_MET (4012), which is a critical error rather than a downgrade. Compute the cap from the heights that exist, not from a fixed number.That robustness has to match your rule
On the demo you can force each system and watch the effect on your own machine.
Seeing what is happening
When something fails, this is what you want to look at:
player.addEventListener('error', (event) => { console.error('code', event.detail.code, event.detail.data); }); // While playing: player.drmInfo(); // { keySystem, encryptionScheme, keyIds } player.getKeyStatuses(); // { '<kid>': 'usable' | 'output-restricted' | ... } player.getStats(); // width, height, streamBandwidth
`output-restricted` is not a configuration error
Video.js #
If you already use Video.js, DRM comes through the videojs-contrib-eme plugin. It is not bundled.
npm install video.js videojs-contrib-eme
import videojs from 'video.js'; import 'videojs-contrib-eme'; const player = videojs('my-video'); player.eme(); // must be enabled explicitly player.src({ src: 'https://your-bucket.s3.amazonaws.com/movie/manifest.mpd', type: 'application/dash+xml', keySystems: { 'com.widevine.alpha': 'https://drm.airi.live/v2/widevine', 'com.microsoft.playready': 'https://drm.airi.live/v2/playready' } });
Forgetting `player.eme()` is the most common mistake
src().FairPlay on Video.js takes more manual work, since you have to supply the certificate and transform the challenge:
player.src({ src: 'https://your-bucket.s3.amazonaws.com/movie/master.m3u8', type: 'application/x-mpegURL', keySystems: { 'com.apple.fps.1_0': { certificateUri: 'https://your-bucket.s3.amazonaws.com/fairplay.cer', licenseUri: 'https://drm.airi.live/fairplay' } } });
For FairPlay, Shaka is easier
When it does not play #
Nearly every failure is one of these five, in order of frequency.
1. Nothing loads, and the console mentions CORS
The bucket holding the segments does not allow requests from your domain. Rule this out first, because the symptom looks like a DRM problem and is not. Allow GET and HEAD.
2. Works in Chrome but not in Safari
Two possible causes, worth checking in this order: the content is encrypted in cenc rather than cbcs — FairPlay does not understand cenc — or the Apple certificate is missing. The first means repackaging; the second is configuration.
3. It plays, but stays on the low quality
It is working as intended. The device does not meet the security level the high quality demands, so the license server gives it the one it can have. Check on the demo: it tells you what level your own machine has.
4. The license server returns 400 or 403
The video’s keyId matches none of your keys. Usually it was packaged with a test key and a different one requested afterwards, or the key_id was passed to the packager in base64 rather than hexadecimal.
5. A robustness error on load
The browser does not offer the level the content asks for. On desktop Chrome that is normal for anything demanding hardware: that browser is L3.
The demo diagnoses your device
cenc, cbcs or both. When something will not play, start there.