videojs-contrib-eme
Version:
Supports Encrypted Media Extensions for playback of encrypted content in Video.js
172 lines (146 loc) • 5.6 kB
JavaScript
/**
* The W3C Working Draft of 22 October 2013 seems to be the best match for
* the ms-prefixed API. However, it should only be used as a guide; it is
* doubtful the spec is 100% implemented as described.
*
* @see https://www.w3.org/TR/2013/WD-encrypted-media-20131022
* @see https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/mt598601(v=vs.85)
*/
import window from 'global/window';
import { requestPlayreadyLicense } from './playready';
import { getMediaKeySystemConfigurations } from './utils';
import EmeError from './consts/errors';
import { safeTriggerOnEventBus } from './eme';
export const PLAYREADY_KEY_SYSTEM = 'com.microsoft.playready';
export const addKeyToSession = (options, session, event, eventBus, emeError) => {
let playreadyOptions = options.keySystems[PLAYREADY_KEY_SYSTEM];
if (typeof playreadyOptions.getKey === 'function') {
playreadyOptions.getKey(options, event.destinationURL, event.message.buffer, (err, key) => {
if (err) {
const metadata = {
errorType: EmeError.EMEFailedToRequestMediaKeySystemAccess,
config: getMediaKeySystemConfigurations(options.keySystems)
};
emeError(err, metadata);
safeTriggerOnEventBus(eventBus, {
message: 'Unable to get key: ' + err,
target: session,
type: 'mskeyerror'
});
return;
}
session.update(key);
safeTriggerOnEventBus(eventBus, {
type: 'keysessionupdated',
keySession: session
});
});
return;
}
if (typeof playreadyOptions === 'string') {
playreadyOptions = {url: playreadyOptions};
} else if (typeof playreadyOptions === 'boolean') {
playreadyOptions = {};
}
if (!playreadyOptions.url) {
playreadyOptions.url = event.destinationURL;
}
const callback = (err, responseBody) => {
if (eventBus) {
safeTriggerOnEventBus(eventBus, { type: 'licenserequestattempted' });
}
if (err) {
const metadata = {
errorType: EmeError.EMEFailedToGenerateLicenseRequest,
keySystem: PLAYREADY_KEY_SYSTEM
};
emeError(err, metadata);
safeTriggerOnEventBus(eventBus, {
message: 'Unable to request key from url: ' + playreadyOptions.url,
target: session,
type: 'mskeyerror'
});
return;
}
session.update(new Uint8Array(responseBody));
};
if (playreadyOptions.getLicense) {
playreadyOptions.getLicense(options, event.message.buffer, callback);
} else {
requestPlayreadyLicense(PLAYREADY_KEY_SYSTEM, playreadyOptions, event.message.buffer, options, callback);
}
};
export const createSession = (video, initData, options, eventBus, emeError) => {
// Note: invalid mime type passed here throws a NotSupportedError
const session = video.msKeys.createSession('video/mp4', initData);
if (!session) {
const error = new Error('Could not create key session.');
const metadata = {
errorType: EmeError.EMEFailedToCreateMediaKeySession,
keySystem: PLAYREADY_KEY_SYSTEM
};
emeError(error, metadata);
throw error;
}
safeTriggerOnEventBus(eventBus, {
type: 'keysessioncreated',
keySession: session
});
// Note that mskeymessage may not always be called for PlayReady:
//
// "If initData contains a PlayReady object that contains an OnDemand header, only a
// keyAdded event is returned (as opposed to a keyMessage event as described in the
// Encrypted Media Extension draft). Similarly, if initData contains a PlayReady object
// that contains a key identifier in the hashed data storage (HDS), only a keyAdded
// event is returned."
// eslint-disable-next-line max-len
// @see [PlayReady License Acquisition]{@link https://msdn.microsoft.com/en-us/library/dn468979.aspx}
session.addEventListener('mskeymessage', (event) => {
safeTriggerOnEventBus(eventBus, {
type: 'keymessage',
messageEvent: event
});
addKeyToSession(options, session, event, eventBus, emeError);
});
session.addEventListener('mskeyerror', (event) => {
const metadata = {
errorType: EmeError.EMEFailedToCreateMediaKeySession,
keySystem: PLAYREADY_KEY_SYSTEM
};
emeError(session.error, metadata);
safeTriggerOnEventBus(eventBus, {
message: 'Unexpected key error from key session with ' +
`code: ${session.error.code} and systemCode: ${session.error.systemCode}`,
target: session,
type: 'mskeyerror'
});
});
session.addEventListener('mskeyadded', () => {
safeTriggerOnEventBus(eventBus, {
target: session,
type: 'mskeyadded'
});
});
};
export default ({video, initData, options, eventBus, emeError}) => {
// Although by the standard examples the presence of video.msKeys is checked first to
// verify that we aren't trying to create a new session when one already exists, here
// sessions are managed earlier (on the player.eme object), meaning that at this point
// any existing keys should be cleaned up.
// TODO: Will this break rotation? Is it safe?
if (video.msKeys) {
delete video.msKeys;
}
try {
video.msSetMediaKeys(new window.MSMediaKeys(PLAYREADY_KEY_SYSTEM));
} catch (e) {
const metadata = {
errorType: EmeError.EMEFailedToCreateMediaKeys,
keySystem: PLAYREADY_KEY_SYSTEM
};
emeError(e, metadata);
throw new Error('Unable to create media keys for PlayReady key system. ' +
'Error: ' + e.message);
}
createSession(video, initData, options, eventBus, emeError);
};