videojs-contrib-eme
Supports Encrypted Media Extensions for playback of encrypted content in Video.js
Maintenance Status: Stable
- Using
- API
- License
Using
By default, videojs-contrib-eme is not able to decrypt any audio/video.
In order to decrypt audio/video, a user must pass in either relevant license URIs, or methods specific to a source and its combination of key system and codec. These are provided to the plugin via either videojs-contrib-eme's plugin options or source options.
If you're new to DRM on the web, read about how EME is used to play protected content on the web.
Initialization
The videojs-contrib-eme plugin must be initialized before a source is loaded into the player:
player.eme();
player.src({
src: '<your url here>',
type: 'application/dash+xml',
keySystems: {
'com.widevine.alpha': '<YOUR URL HERE>'
}
});
FairPlay
For FairPlay, only keySystems
is used from the options passed into videojs-contrib-eme or provided as part of the source object.
There are two ways to configure FairPlay.
Get Certificate/License by URL
In this simpler implementation, you can provide URLs and allow videojs-contrib-eme to make the requests internally via default mechanisms.
When using this method, there are two required properties of the keySystems
object:
certificateUri
licenseUri
And there are two optional properties:
certificateHeaders
licenseHeaders
With this configuration, videojs-contrib-eme will behave in the following ways:
- It will fetch the certificate by making a GET request to your
certificateUri
with an expected response type ofarraybuffer
. Headers can be defined for this request via thecertificateHeaders
object. - The content ID will be interpreted from the
initData
. - It will fetch the license by making a POST request to your
licenseUri
with an expected response type ofarraybuffer
. This will have one default header ofContent-type: application/octet-stream
, but this can be overridden (or other headers added) usinglicenseHeaders
.
Below are examples of FairPlay configurations of this type:
{
keySystems: {
'com.apple.fps.1_0': {
certificateUri: '<CERTIFICATE_URL>',
licenseUri: '<LICENSE_URL>'
}
}
}
or
{
keySystems: {
'com.apple.fps.1_0': {
certificateUri: '<CERTIFICATE_URL>',
certificateHeaders: {
'Some-Header': 'value'
},
licenseUri: '<LICENSE_URL>',
licenseHeaders: {
'Some-Header': 'value'
}
}
}
}
Get Certificate/Content ID/License by Functions
You can control the license and certificate request processes by providing the following methods instead of the properties discussed above:
getCertificate()
- Allows asynchronous retrieval of a certificate.getContentId()
- Allows synchronous retrieval of a content ID. It takesemeOptions
, as well as theinitData
converted into a String.getLicense()
- Allows asynchronous retrieval of a license.
{
keySystems: {
'com.apple.fps.1_0': {
getCertificate: function(emeOptions, callback) {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
getContentId: function(emeOptions, contentId) {
// return content ID
},
getLicense: function(emeOptions, contentId, keyMessage, callback) {
// request key
// if err, callback(err)
// if success, callback(null, key) as arraybuffer
}
}
}
}
PlayReady for IE11 (Windows 8.1+)
PlayReady for IE11 (Windows 8.1+) only requires keySystems
from the options passed into videojs-contrib-eme or provided as part of the source object.
There are three ways to configure PlayReady for IE11 (Windows 8.1+).
Get License by Default
If the value of true
is provided, then a POST request will be made to the destinationURI
passed by the message from the browser, with the headers and body specified in the message.
keySystems: {
'com.microsoft.playready': true
}
Get Key by URL
If a URL is provided - either within an object or as a string - then a POST request will be made to the provided URL, with the headers and body specified in the message. Additionally, a licenseHeaders
object may be provided, if additional headers are required:
keySystems: {
'com.microsoft.playready': '<YOUR_KEY_URL>'
}
or
keySystems: {
'com.microsoft.playready': {
url: '<YOUR_KEY_URL>',
licenseHeaders: {
'Some-Header': 'value'
}
}
}
Get Key by Function
If a getKey
function is provided, then the function will be run with the message buffer and destinationURI
passed by the browser, and will expect a callback with the key:
{
keySystems: {
'com.microsoft.playready': {
getKey: function(emeOptions, destinationURI, buffer, callback) {
// request key
// if err, callback(err)
// if success, callback(null, key), where key is a Uint8Array
}
}
}
}
Other DRM Systems
For DRM systems that use the W3C EME specification as of 5 July 2016, only keySystems
and a way of obtaining the license are required.
Obtaining a license can be done in two ways.
Get License By URL
For simple use-cases, you may use a string as the license URL or a URL as a property of in the keySystems
entry:
{
keySystems: {
'org.w3.clearkey': '<YOUR_LICENSE_URL>',
'com.widevine.alpha': {
url: '<YOUR_LICENSE_URL>'
}
}
}
Get License By Function
For more complex integrations, you may pass a getLicense
function to fully control the license retrieval process:
{
keySystems: {
'org.w3.clearkey': {
getLicense: function(emeOptions, keyMessage, callback) {
// request license
// if err, callback(err)
// if success, callback(null, license)
}
}
}
}
Get Certificate by function
Although the license acquisition is the only required configuration, getCertificate()
is also supported if your source needs to retrieve a certificate, similar to the FairPlay implementation above.
Example:
{
'org.w3.clearkey': {
getCertificate: function(emeOptions, callback) {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
}
}
audioContentType/videoContentType
The audioContentType
and videoContentType
properties for non-FairPlay sources are used to determine if the system supports that codec and to create an appropriate keySystemAccess
object. If left out, it is possible that the system will create a keySystemAccess
object for the given key system, but will not be able to play the source due to the browser's inability to use that codec.
example:
{
keySystems: {
'org.w3.clearkey': {
audioContentType: 'audio/webm; codecs="vorbis"',
videoContentType: 'video/webm; codecs="vp9"',
}
}
}
audioRobustness/videoRobustness
If
audioRobustness
/videoRobustness
is not passed in for widevine, you will see a warning similar to:It is recommended that a robustness level be specified. Not specifying the robustness level could result in unexpected behavior
. Possible Options for widevine:SW_SECURE_CRYPTO
,SW_SECURE_DECODE
,HW_SECURE_CRYPTO
,HW_SECURE_DECODE
,HW_SECURE_ALL
The audioRobustness
and videoRobustness
properties for non-FairPlay sources are used to determine the robustness level of the DRM you are using.
Example:
{
keySystems: {
'com.widevine.alpha': {
audioRobustness: 'SW_SECURE_CRYPTO',
videoRobustness: 'SW_SECURE_CRYPTO'
}
}
}
MediaKeySystemConfiguration and supportedConfigurations
In addition to key systems options provided above, it is possible to directly provide the supportedConfigurations
array to use for the requestMediaKeySystemAccess
call. This allows for the entire range of options specified by the MediaKeySystemConfiguration object.
Note that if supportedConfigurations
is provided, it will override audioContentType
, videoContentType
, audioRobustness
, videoRobustness
, and persistentState
.
Example:
{
keySystems: {
'org.w3.clearkey': {
supportedConfigurations: [{
videoCapabilities: [{
contentType: 'video/webm; codecs="vp9"',
robustness: 'SW_SECURE_CRYPTO'
}],
audioCapabilities: [{
contentType: 'audio/webm; codecs="vorbis"',
robustness: 'SW_SECURE_CRYPTO'
}]
}],
'org.w3.clearkey': '<YOUR_LICENSE_URL>'
}
}
}
Get License Errors
The default getLicense()
functions pass an error to the callback if the license request returns a 4xx or 5xx response code. Depending on how the license server is configured, it is possible in some cases that a valid license could still be returned even if the response code is in that range. If you wish not to pass an error for 4xx and 5xx response codes, you may pass your own getLicense()
function with the keySystems
as described above.
API
Available Options
keySystems
This is the main option through which videojs-contrib-eme can be configured. It maps key systems by name (e.g. 'org.w3.clearkey'
) to an object for configuring that key system.
emeHeaders
This object can be a convenient way to specify default headers for all requests that are made by videojs-contrib-eme. These headers will override any headers that are set by videojs-contrib-eme internally, but can be further overridden by headers specified in keySystems
objects (e.g., certificateHeaders
or licenseHeaders
).
An emeHeaders
object should look like this:
emeHeaders: {
'Common-Header': 'value'
}
firstWebkitneedkeyTimeout
Default: 1000
The amount of time in milliseconds to wait on the first webkitneedkey
event before making the key request. This was implemented due to a bug in Safari where rendition switches at the start of playback can cause webkitneedkey
to fire multiple times, with only the last one being valid.
Setting Options per Source
This is the recommended way of setting most options. Each source may have a different set of requirements; so, it is best to define options on a per source basis.
To do this, attach the options to the source object you pass to player.src()
:
player.src({
// normal Video.js src and type options
src: '<URL>',
type: 'video/webm',
// eme options
emeHeaders: {
'Common-Header': 'value'
},
keySystems: {
'org.w3.clearkey': {
initDataTypes: ['cenc', 'webm'],
audioContentType: 'audio/webm; codecs="vorbis"',
videoContentType: 'video/webm; codecs="vp9"',
getCertificate: function(emeOptions, callback) {
// request certificate
// if err, callback(err)
// if success, callback(null, certificate)
},
getLicense: function(emeOptions, keyMessage, callback) {
// request license
// if err, callback(err)
// if success, callback(null, license)
}
}
}
});
Setting Options for All Sources
While setting options per source is recommended, some implementations may want to use plugin-level options.
These can be set during plugin invocation:
player.eme({
// Set Common-Header on ALL requests for ALL key systems.
emeHeaders: {
'Common-Header': 'value'
}
});
Plugin-level options may also be set after plugin initialization by assigning to the options property on the eme
object itself:
player.eme();
player.eme.options.emeHeaders = {
'Common-Header': 'value'
};
or
player.eme();
player.eme.options = {
emeHeaders: {
'Common-Header': 'value'
}
};
Header Hierarchy and Removal
Headers defined in the emeHeaders
option or in licenseHeaders
/certificateHeaders
objects within keySystems
can remove headers defined at lower levels without defining a new value. This can be done by setting their value to null
.
The hierarchy of header definitions is:
licenseHeaders/certificateHeaders > emeHeaders > internal defaults
In most cases, the header {'Content-type': 'application/octet-stream'}
is a default and cannot be overridden without writing your own getLicense()
function. This internal default can be overridden by either of the user-provided options.
Here's an example:
player.eme({
emeHeaders: {
// Remove the internal default Content-Type
'Content-Type': null,
'Custom-Foo': '<CUSTOM_FOO_VALUE>'
}
});
player.src({
src: '<URL>',
type: '<MIME_TYPE>',
keySystems: {
'com.apple.fps.1_0': {
certificateUri: '<CERTIFICATE_URL>',
certificateHeaders: {
'Custom-Foo': '<ANOTHER_CUSTOM_FOO_VALUE>'
},
licenseUri: '<LICENSE_URL>',
licenseHeaders: {
'License-Bar': '<LICENSE_BAR_VALUE>'
}
}
}
})
This will result in the following headers for the certificate request:
Custom-Foo: <ANOTHER_CUSTOM_FOO_VALUE>
And for the license request:
Custom-Foo: <CUSTOM_FOO_VALUE>
License-Bar: <LICENSE_BAR_VALUE>
emeOptions
All methods in a key system receive emeOptions
as their first argument.
The emeOptions
are an object which merges source-level options with plugin-level options.
NOTE: In these cases, plugin-level options will override the source-level options. This is used by libraries like VHS, but could be unintuitive. This is another reason to prefer source-level options in all cases!
It is available to make it easier to access options in custom key systems methods, so that you don't have to maintain your own references.
For example, if you needed to use a userId
for the getCertificate()
request, you could:
player.eme();
player.src({
keySystems: {
'org.w3.clearkey': {
getCertificate: function(emeOptions, callback) {
var userId = emeOptions.userId; // 'user-id'
// ...
},
getLicense: function(emeOptions, keyMessage, callback) {
var userId = emeOptions.userId; // 'user-id'
// ...
}
}
},
userId: 'user-id'
});
initializeMediaKeys()
player.eme.initializeMediaKeys()
sets up MediaKeys immediately on demand.
This is useful for setting up the video element for DRM before loading any content. Otherwise, the video element is set up for DRM on encrypted
events. This is not supported in Safari.
// additional plugin options
var emeOptions = {
keySystems: {
'org.w3.clearkey': {...}
}
};
var emeCallback = function(error) {
if (error) {
// do something with error
}
// do something else
};
var suppressErrorsIfPossible = true;
player.eme.initializeMediaKeys(emeOptions, emeCallback, suppressErrorsIfPossible);
When suppressErrorsIfPossible
is set to false
(the default) and an error occurs, the error handler will be invoked after the callback finishes and error()
will be called on the player. When set to true
and an error occurs, the error handler will not be invoked with the exception of mskeyerror
errors in IE11 since they cannot be suppressed asynchronously.
detectSupportedCDMs()
player.eme.detectSupportedCDMs()
is used to asynchronously detect and return a list of supported Content Decryption Modules (CDMs) in the current browser. It uses the EME API to request access to each key system and determine its availability. This function checks for the support of the following key systems: FairPlay, PlayReady, Widevine, and ClearKey.
Please use this function sparingly, as side-effects (namely calling navigator.requestMediaKeySystemAccess()
) can have user-visible effects, such as prompting for system resource permissions, which could be disruptive if invoked at inappropriate times. See requestMediaKeySystemAccess() documentation for more information.
player.eme.detectSupportedCDMs()
.then(supportedCDMs => {
// Sample output: {fairplay: false, playready: false, widevine: true, clearkey: true}
console.log(supportedCDMs);
});
Events
There are some events that are specific to this plugin.
licenserequestattempted
This event is triggered on the Video.js playback tech on the callback of every license request made by videojs-contrib-eme.
player.tech(true).on('licenserequestattempted', function(event) {
// Act on event
});
keystatuschange
When the status of a key changes, an event of type keystatuschange
will
be triggered on the Video.js playback tech. This helps you handle feedback to the user for situations like trying to play DRM-protected media on restricted devices.
player.tech(true).on('keystatuschange', function(event) {
// Event data:
// keyId
// status: usable, output-restricted, etc
// target: the MediaKeySession object that caused this event
});
This event is triggered directly from the underlying keystatuseschange
event, so the statuses should correspond to those listed in the spec.
keysessioncreated
When the key session is created, an event of type keysessioncreated
will be triggered on the Video.js playback tech.
player.tech().on('keysessioncreated', function(event) {
// note that there is no event data for keysessioncreated
});
License
Apache License, Version 2.0. View the license file