videojs-contrib-eme
Version:
Supports Encrypted Media Extensions for playback of encrypted content in Video.js
235 lines (196 loc) • 8.42 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = require('video.js');
var _videoJs2 = _interopRequireDefault(_videoJs);
var _eme = require('./eme');
var _fairplay = require('./fairplay');
var _fairplay2 = _interopRequireDefault(_fairplay);
var _msPrefixed = require('./ms-prefixed');
var _msPrefixed2 = _interopRequireDefault(_msPrefixed);
var _utils = require('./utils');
var hasSession = function hasSession(sessions, initData) {
for (var i = 0; i < sessions.length; i++) {
// Other types of sessions may be in the sessions array that don't store the initData
// (for instance, PlayReady sessions on IE11).
if (!sessions[i].initData) {
continue;
}
// initData should be an ArrayBuffer by the spec:
// eslint-disable-next-line max-len
// @see [Media Encrypted Event initData Spec]{@link https://www.w3.org/TR/encrypted-media/#mediaencryptedeventinit}
//
// However, on some browsers it may come back with a typed array view of the buffer.
// This is the case for IE11, however, since IE11 sessions are handled differently
// (following the msneedkey PlayReady path), this coversion may not be important. It
// is safe though, and might be a good idea to retain in the short term (until we have
// catalogued the full range of browsers and their implementations).
if ((0, _utils.arrayBuffersEqual)((0, _utils.arrayBufferFrom)(sessions[i].initData), (0, _utils.arrayBufferFrom)(initData))) {
return true;
}
}
return false;
};
exports.hasSession = hasSession;
var handleEncryptedEvent = function handleEncryptedEvent(event, sourceOptions, sessions) {
if (!sourceOptions || !sourceOptions.keySystems) {
// return silently since it may be handled by a different system
return;
}
// "Initialization Data must be a fixed value for a given set of stream(s) or media
// data. It must only contain information related to the keys required to play a given
// set of stream(s) or media data."
// eslint-disable-next-line max-len
// @see [Initialization Data Spec]{@link https://www.w3.org/TR/encrypted-media/#initialization-data}
if (hasSession(sessions, event.initData)) {
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log('eme',
// 'Already have a configured session for init data, ignoring event.');
return;
}
sessions.push({ initData: event.initData });
(0, _eme.standard5July2016)({
video: event.target,
initDataType: event.initDataType,
initData: event.initData,
options: sourceOptions
});
};
exports.handleEncryptedEvent = handleEncryptedEvent;
var handleWebKitNeedKeyEvent = function handleWebKitNeedKeyEvent(event, sourceOptions) {
if (!sourceOptions.keySystems || !sourceOptions.keySystems[_fairplay.FAIRPLAY_KEY_SYSTEM]) {
// return silently since it may be handled by a different system
return;
}
// From Apple's example Safari FairPlay integration code, webkitneedkey is not repeated
// for the same content. Unless documentation is found to present the opposite, handle
// all webkitneedkey events the same (even if they are repeated).
return (0, _fairplay2['default'])({
video: event.target,
initData: event.initData,
options: sourceOptions
});
};
exports.handleWebKitNeedKeyEvent = handleWebKitNeedKeyEvent;
var handleMsNeedKeyEvent = function handleMsNeedKeyEvent(event, sourceOptions, sessions) {
if (!sourceOptions.keySystems || !sourceOptions.keySystems[_msPrefixed.PLAYREADY_KEY_SYSTEM]) {
// return silently since it may be handled by a different system
return;
}
// "With PlayReady content protection, your Web app must handle the first needKey event,
// but it must then ignore any other needKey event that occurs."
// eslint-disable-next-line max-len
// @see [PlayReady License Acquisition]{@link https://msdn.microsoft.com/en-us/library/dn468979.aspx}
//
// Usually (and as per the example in the link above) this is determined by checking for
// the existence of video.msKeys. However, since the video element may be reused, it's
// easier to directly manage the session.
if (sessions.reduce(function (acc, session) {
return acc || session.playready;
}, false)) {
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log('eme',
// 'An \'msneedkey\' event was receieved earlier, ignoring event.');
return;
}
sessions.push({ playready: true });
(0, _msPrefixed2['default'])({
video: event.target,
initData: event.initData,
options: sourceOptions
});
};
exports.handleMsNeedKeyEvent = handleMsNeedKeyEvent;
/**
* Configure a persistent sessions array and activeSrc property to ensure we properly
* handle each independent source's events. Should be run on any encrypted or needkey
* style event to ensure that the sessions reflect the active source.
*
* @function setupSessions
* @param {Player} player
*/
var setupSessions = function setupSessions(player) {
var src = player.src();
if (src !== player.eme.activeSrc) {
player.eme.activeSrc = src;
player.eme.sessions = [];
}
};
exports.setupSessions = setupSessions;
/**
* Function to invoke when the player is ready.
*
* This is a great place for your plugin to initialize itself. When this
* function is called, the player will have its DOM and child components
* in place.
*
* @function onPlayerReady
* @param {Player} player
* @param {Object} [options={}]
*/
var onPlayerReady = function onPlayerReady(player, options) {
if (player.$('.vjs-tech').tagName.toLowerCase() !== 'video') {
return;
}
setupSessions(player);
// Support EME 05 July 2016
// Chrome 42+, Firefox 47+, Edge
player.tech_.el_.addEventListener('encrypted', function (event) {
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log('eme', 'Received an \'encrypted\' event');
setupSessions(player);
handleEncryptedEvent(event, _videoJs2['default'].mergeOptions(options, player.currentSource()), player.eme.sessions);
});
// Support Safari EME with FairPlay
// (also used in early Chrome or Chrome with EME disabled flag)
player.tech_.el_.addEventListener('webkitneedkey', function (event) {
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log('eme', 'Received a \'webkitneedkey\' event');
// TODO it's possible that the video state must be cleared if reusing the same video
// element between sources
setupSessions(player);
handleWebKitNeedKeyEvent(event, _videoJs2['default'].mergeOptions(options, player.currentSource()));
});
// EDGE still fires msneedkey, but should use encrypted instead
if (_videoJs2['default'].browser.IS_EDGE) {
return;
}
// IE11 Windows 8.1+
player.tech_.el_.addEventListener('msneedkey', function (event) {
// TODO convert to videojs.log.debug and add back in
// https://github.com/videojs/video.js/pull/4780
// videojs.log('eme', 'Received an \'msneedkey\' event');
setupSessions(player);
handleMsNeedKeyEvent(event, _videoJs2['default'].mergeOptions(options, player.currentSource()), player.eme.sessions);
});
};
/**
* A video.js plugin.
*
* In the plugin function, the value of `this` is a video.js `Player`
* instance. You cannot rely on the player being in a "ready" state here,
* depending on how the plugin is invoked. This may or may not be important
* to you; if not, remove the wait for "ready"!
*
* @function eme
* @param {Object} [options={}]
* An object of options left to the plugin author to define.
*/
var eme = function eme() {
var _this = this;
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
this.ready(function () {
onPlayerReady(_this, _videoJs2['default'].mergeOptions({}, options));
});
this.eme.options = options;
};
// Register the plugin with video.js.
var registerPlugin = _videoJs2['default'].registerPlugin || _videoJs2['default'].plugin;
registerPlugin('eme', eme);
exports['default'] = eme;