videojs-contrib-eme
Version:
Supports Encrypted Media Extensions for playback of encrypted content in Video.js
2,176 lines • 75.7 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
},{}],2:[function(require,module,exports){
(function (global){
var topLevel = typeof global !== 'undefined' ? global :
typeof window !== 'undefined' ? window : {}
var minDoc = require('min-document');
if (typeof document !== 'undefined') {
module.exports = document;
} else {
var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
if (!doccy) {
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
}
module.exports = doccy;
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"min-document":1}],3:[function(require,module,exports){
(function (global){
if (typeof window !== "undefined") {
module.exports = window;
} else if (typeof global !== "undefined") {
module.exports = global;
} else if (typeof self !== "undefined"){
module.exports = self;
} else {
module.exports = {};
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],4:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _playready = require('./playready');
var getSupportedKeySystem = function getSupportedKeySystem(_ref) {
var video = _ref.video;
var keySystems = _ref.keySystems;
// As this happens after the src is set on the video, we rely only on the set src (we
// do not change src based on capabilities of the browser in this plugin).
var promise = undefined;
Object.keys(keySystems).forEach(function (keySystem) {
// TODO use initDataTypes when appropriate
var systemOptions = {};
var audioContentType = keySystems[keySystem].audioContentType;
var videoContentType = keySystems[keySystem].videoContentType;
if (audioContentType) {
systemOptions.audioCapabilities = [{
contentType: audioContentType
}];
}
if (videoContentType) {
systemOptions.videoCapabilities = [{
contentType: videoContentType
}];
}
if (!promise) {
promise = navigator.requestMediaKeySystemAccess(keySystem, [systemOptions]);
} else {
promise = promise['catch'](function (e) {
return navigator.requestMediaKeySystemAccess(keySystem, [systemOptions]);
});
}
});
return promise;
};
var makeNewRequest = function makeNewRequest(_ref2) {
var mediaKeys = _ref2.mediaKeys;
var initDataType = _ref2.initDataType;
var initData = _ref2.initData;
var options = _ref2.options;
var getLicense = _ref2.getLicense;
var keySession = mediaKeys.createSession();
keySession.addEventListener('message', function (event) {
getLicense(options, event.message).then(function (license) {
return keySession.update(license);
})['catch'](_videoJs2['default'].log.error.bind(_videoJs2['default'].log.error, 'failed to get and set license'));
}, false);
keySession.generateRequest(initDataType, initData)['catch'](_videoJs2['default'].log.error.bind(_videoJs2['default'].log.error, 'Unable to create or initialize key session'));
};
var addSession = function addSession(_ref3) {
var video = _ref3.video;
var initDataType = _ref3.initDataType;
var initData = _ref3.initData;
var options = _ref3.options;
var getLicense = _ref3.getLicense;
if (video.mediaKeysObject) {
makeNewRequest({
mediaKeys: video.mediaKeysObject,
initDataType: initDataType,
initData: initData,
options: options,
getLicense: getLicense
});
} else {
video.pendingSessionData.push({ initDataType: initDataType, initData: initData });
}
};
var setMediaKeys = function setMediaKeys(_ref4) {
var video = _ref4.video;
var certificate = _ref4.certificate;
var createdMediaKeys = _ref4.createdMediaKeys;
var options = _ref4.options;
var getLicense = _ref4.getLicense;
video.mediaKeysObject = createdMediaKeys;
if (certificate) {
createdMediaKeys.setServerCertificate(certificate);
}
for (var i = 0; i < video.pendingSessionData.length; i++) {
var data = video.pendingSessionData[i];
makeNewRequest({
mediaKeys: video.mediaKeysObject,
initDataType: data.initDataType,
initData: data.initData,
options: options,
getLicense: getLicense
});
}
video.pendingSessionData = [];
return video.setMediaKeys(createdMediaKeys);
};
var defaultPlayreadyGetLicense = function defaultPlayreadyGetLicense(url) {
return function (emeOptions, keyMessage, callback) {
(0, _playready.requestPlayreadyLicense)(url, keyMessage, function (err, response, responseBody) {
if (err) {
callback(err);
return;
}
callback(null, responseBody);
});
};
};
var defaultGetLicense = function defaultGetLicense(url) {
return function (emeOptions, keyMessage, callback) {
_videoJs2['default'].xhr({
uri: url,
method: 'POST',
responseType: 'arraybuffer',
body: keyMessage,
headers: {
'Content-type': 'application/octet-stream'
}
}, function (err, response, responseBody) {
if (err) {
callback(err);
return;
}
callback(null, responseBody);
});
};
};
var promisifyGetLicense = function promisifyGetLicense(getLicenseFn) {
return function (emeOptions, keyMessage) {
return new Promise(function (resolve, reject) {
getLicenseFn(emeOptions, keyMessage, function (err, license) {
if (err) {
reject(err);
}
resolve(license);
});
});
};
};
var standardizeKeySystemOptions = function standardizeKeySystemOptions(keySystem, keySystemOptions) {
if (typeof keySystemOptions === 'string') {
keySystemOptions = { url: keySystemOptions };
}
if (!keySystemOptions.url && !keySystemOptions.getLicense) {
throw new Error('Neither URL nor getLicense function provided to get license');
}
if (keySystemOptions.url && !keySystemOptions.getLicense) {
keySystemOptions.getLicense = keySystem === 'com.microsoft.playready' ? defaultPlayreadyGetLicense(keySystemOptions.url) : defaultGetLicense(keySystemOptions.url);
}
return keySystemOptions;
};
var standard5July2016 = function standard5July2016(_ref5) {
var video = _ref5.video;
var initDataType = _ref5.initDataType;
var initData = _ref5.initData;
var options = _ref5.options;
if (typeof video.mediaKeysObject === 'undefined') {
var _ret = (function () {
// Prevent entering this path again.
video.mediaKeysObject = null;
// Will store all initData until the MediaKeys is ready.
video.pendingSessionData = [];
var certificate = undefined;
var keySystemOptions = undefined;
var keySystemPromise = getSupportedKeySystem({
video: video,
keySystems: options.keySystems
});
if (!keySystemPromise) {
_videoJs2['default'].log.error('No supported key system found');
return {
v: undefined
};
}
keySystemPromise.then(function (keySystemAccess) {
return new Promise(function (resolve, reject) {
// save key system for adding sessions
video.keySystem = keySystemAccess.keySystem;
keySystemOptions = standardizeKeySystemOptions(keySystemAccess.keySystem, options.keySystems[keySystemAccess.keySystem]);
if (!keySystemOptions.getCertificate) {
resolve(keySystemAccess);
return;
}
keySystemOptions.getCertificate(options, function (err, cert) {
if (err) {
reject(err);
return;
}
certificate = cert;
resolve(keySystemAccess);
});
});
}).then(function (keySystemAccess) {
return keySystemAccess.createMediaKeys();
}).then(function (createdMediaKeys) {
return setMediaKeys({
video: video,
certificate: certificate,
createdMediaKeys: createdMediaKeys,
options: options,
getLicense: promisifyGetLicense(keySystemOptions.getLicense)
});
})['catch'](_videoJs2['default'].log.error.bind(_videoJs2['default'].log.error, 'Failed to create and initialize a MediaKeys object'));
})();
if (typeof _ret === 'object') return _ret.v;
}
addSession({
video: video,
initDataType: initDataType,
initData: initData,
options: options,
// if key system has not been determined then addSession doesn't need getLicense
getLicense: video.keySystem ? promisifyGetLicense(standardizeKeySystemOptions(video.keySystem, options.keySystems[video.keySystem]).getLicense) : null
});
};
exports.standard5July2016 = standard5July2016;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./playready":7}],5:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
var _utils = require('./utils');
var FAIRPLAY_KEY_SYSTEM = 'com.apple.fps.1_0';
exports.FAIRPLAY_KEY_SYSTEM = FAIRPLAY_KEY_SYSTEM;
var concatInitDataIdAndCertificate = function concatInitDataIdAndCertificate(_ref) {
var initData = _ref.initData;
var id = _ref.id;
var cert = _ref.cert;
if (typeof id === 'string') {
id = (0, _utils.stringToUint16Array)(id);
}
// layout:
// [initData]
// [4 byte: idLength]
// [idLength byte: id]
// [4 byte:certLength]
// [certLength byte: cert]
var offset = 0;
var buffer = new ArrayBuffer(initData.byteLength + 4 + id.byteLength + 4 + cert.byteLength);
var dataView = new DataView(buffer);
var initDataArray = new Uint8Array(buffer, offset, initData.byteLength);
initDataArray.set(initData);
offset += initData.byteLength;
dataView.setUint32(offset, id.byteLength, true);
offset += 4;
var idArray = new Uint16Array(buffer, offset, id.length);
idArray.set(id);
offset += idArray.byteLength;
dataView.setUint32(offset, cert.byteLength, true);
offset += 4;
var certArray = new Uint8Array(buffer, offset, cert.byteLength);
certArray.set(cert);
return new Uint8Array(buffer, 0, buffer.byteLength);
};
var addKey = function addKey(_ref2) {
var video = _ref2.video;
var contentId = _ref2.contentId;
var initData = _ref2.initData;
var cert = _ref2.cert;
var options = _ref2.options;
var getLicense = _ref2.getLicense;
return new Promise(function (resolve, reject) {
if (!video.webkitKeys) {
video.webkitSetMediaKeys(new _globalWindow2['default'].WebKitMediaKeys(FAIRPLAY_KEY_SYSTEM));
}
if (!video.webkitKeys) {
reject('Could not create MediaKeys');
return;
}
var keySession = video.webkitKeys.createSession('video/mp4', concatInitDataIdAndCertificate({ id: contentId, initData: initData, cert: cert }));
if (!keySession) {
reject('Could not create key session');
return;
}
keySession.contentId = contentId;
keySession.addEventListener('webkitkeymessage', function (event) {
getLicense(options, contentId, event.message, function (err, license) {
if (err) {
reject(err);
return;
}
keySession.update(new Uint8Array(license));
});
});
keySession.addEventListener('webkitkeyadded', function (event) {
resolve(event);
});
// for testing purposes, adding webkitkeyerror must be the last item in this method
keySession.addEventListener('webkitkeyerror', function (event) {
reject(event);
});
});
};
var defaultGetCertificate = function defaultGetCertificate(certificateUri) {
return function (emeOptions, callback) {
_videoJs2['default'].xhr({
uri: certificateUri,
responseType: 'arraybuffer'
}, function (err, response, responseBody) {
if (err) {
callback(err);
return;
}
callback(null, new Uint8Array(responseBody));
});
};
};
var defaultGetContentId = function defaultGetContentId(emeOptions, initData) {
return (0, _utils.getHostnameFromUri)((0, _utils.uint8ArrayToString)(initData));
};
var defaultGetLicense = function defaultGetLicense(licenseUri) {
return function (emeOptions, contentId, keyMessage, callback) {
_videoJs2['default'].xhr({
uri: licenseUri,
method: 'POST',
responseType: 'arraybuffer',
body: keyMessage,
headers: {
'Content-type': 'application/octet-stream'
}
}, function (err, response, responseBody) {
if (err) {
callback(err);
return;
}
callback(null, responseBody);
});
};
};
var fairplay = function fairplay(_ref3) {
var video = _ref3.video;
var initData = _ref3.initData;
var options = _ref3.options;
var fairplayOptions = options.keySystems[FAIRPLAY_KEY_SYSTEM];
var getCertificate = fairplayOptions.getCertificate || defaultGetCertificate(fairplayOptions.certificateUri);
var getContentId = fairplayOptions.getContentId || defaultGetContentId;
var getLicense = fairplayOptions.getLicense || defaultGetLicense(fairplayOptions.licenseUri);
return new Promise(function (resolve, reject) {
getCertificate(options, function (err, cert) {
if (err) {
reject(err);
return;
}
resolve(cert);
});
}).then(function (cert) {
return addKey({
video: video,
cert: cert,
initData: initData,
getLicense: getLicense,
options: options,
contentId: getContentId(options, initData)
});
})['catch'](_videoJs2['default'].log.error.bind(_videoJs2['default'].log.error));
};
exports['default'] = fairplay;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./utils":9,"global/window":3}],6:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
var _playready = require('./playready');
var PLAYREADY_KEY_SYSTEM = 'com.microsoft.playready';
exports.PLAYREADY_KEY_SYSTEM = PLAYREADY_KEY_SYSTEM;
var addKeyToSession = function addKeyToSession(options, session, event) {
var playreadyOptions = options.keySystems[PLAYREADY_KEY_SYSTEM];
if (typeof playreadyOptions.getKey === 'function') {
playreadyOptions.getKey(options, event.destinationURL, event.message.buffer, function (err, key) {
if (err) {
_videoJs2['default'].log.error('Unable to get key: ' + err);
return;
}
session.update(key);
});
return;
}
if (typeof playreadyOptions === 'string') {
playreadyOptions = { url: playreadyOptions };
}
var url = playreadyOptions.url || event.destinationURL;
(0, _playready.requestPlayreadyLicense)(url, event.message.buffer, function (err, response) {
if (err) {
_videoJs2['default'].log.error('Unable to request key from url: ' + url);
return;
}
session.update(new Uint8Array(response.body));
});
};
exports.addKeyToSession = addKeyToSession;
var createSession = function createSession(video, initData, options) {
var session = video.msKeys.createSession('video/mp4', initData);
if (!session) {
_videoJs2['default'].log.error('Could not create key session.');
return;
}
// 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', function (event) {
addKeyToSession(options, session, event);
});
session.addEventListener('mskeyerror', function (event) {
_videoJs2['default'].log.error('Unexpected key error from key session with ' + ('code: ' + session.error.code + ' and systemCode: ' + session.error.systemCode));
});
};
exports.createSession = createSession;
exports['default'] = function (_ref) {
var video = _ref.video;
var initData = _ref.initData;
var options = _ref.options;
// 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.
if (video.msKeys) {
delete video.msKeys;
}
try {
video.msSetMediaKeys(new _globalWindow2['default'].MSMediaKeys(PLAYREADY_KEY_SYSTEM));
} catch (e) {
_videoJs2['default'].log.error('Unable to create media keys for PlayReady key system. Error: ' + e.message);
return;
}
createSession(video, initData, options);
};
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./playready":7,"global/window":3}],7:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
/**
* Parses the EME key message XML to extract HTTP headers and the Challenge element to use
* in the PlayReady license request.
*
* @param {ArrayBuffer} message key message from EME
* @return {Object} an object containing headers and the message body to use in the
* license request
*/
var getMessageContents = function getMessageContents(message) {
var xml = new DOMParser().parseFromString(
// TODO do we want to support UTF-8?
String.fromCharCode.apply(null, new Uint16Array(message)), 'application/xml');
var headersElement = xml.getElementsByTagName('HttpHeaders')[0];
var headers = {};
if (headersElement) {
var headerNames = headersElement.getElementsByTagName('name');
var headerValues = headersElement.getElementsByTagName('value');
for (var i = 0; i < headerNames.length; i++) {
headers[headerNames[i].childNodes[0].nodeValue] = headerValues[i].childNodes[0].nodeValue;
}
}
var challengeElement = xml.getElementsByTagName('Challenge')[0];
var challenge = undefined;
if (challengeElement) {
challenge = _globalWindow2['default'].atob(challengeElement.childNodes[0].nodeValue);
}
return {
headers: headers,
message: challenge
};
};
exports.getMessageContents = getMessageContents;
var requestPlayreadyLicense = function requestPlayreadyLicense(url, messageBuffer, callback) {
var _getMessageContents = getMessageContents(messageBuffer);
var headers = _getMessageContents.headers;
var message = _getMessageContents.message;
_videoJs2['default'].xhr({
uri: url,
method: 'post',
headers: headers,
body: message,
responseType: 'arraybuffer'
}, callback);
};
exports.requestPlayreadyLicense = requestPlayreadyLicense;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"global/window":3}],8:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
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;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./eme":4,"./fairplay":5,"./ms-prefixed":6,"./utils":9}],9:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
var stringToUint16Array = function stringToUint16Array(string) {
// 2 bytes for each char
var buffer = new ArrayBuffer(string.length * 2);
var array = new Uint16Array(buffer);
for (var i = 0; i < string.length; i++) {
array[i] = string.charCodeAt(i);
}
return array;
};
exports.stringToUint16Array = stringToUint16Array;
var uint8ArrayToString = function uint8ArrayToString(array) {
return String.fromCharCode.apply(null, new Uint16Array(array.buffer));
};
exports.uint8ArrayToString = uint8ArrayToString;
var getHostnameFromUri = function getHostnameFromUri(uri) {
var link = document.createElement('a');
link.href = uri;
return link.hostname;
};
exports.getHostnameFromUri = getHostnameFromUri;
var arrayBuffersEqual = function arrayBuffersEqual(arrayBuffer1, arrayBuffer2) {
if (arrayBuffer1 === arrayBuffer2) {
return true;
}
if (arrayBuffer1.byteLength !== arrayBuffer2.byteLength) {
return false;
}
var dataView1 = new DataView(arrayBuffer1);
var dataView2 = new DataView(arrayBuffer2);
for (var i = 0; i < dataView1.byteLength; i++) {
if (dataView1.getUint8(i) !== dataView2.getUint8(i)) {
return false;
}
}
return true;
};
exports.arrayBuffersEqual = arrayBuffersEqual;
var arrayBufferFrom = function arrayBufferFrom(bufferOrTypedArray) {
if (bufferOrTypedArray instanceof Uint8Array || bufferOrTypedArray instanceof Uint16Array) {
return bufferOrTypedArray.buffer;
}
return bufferOrTypedArray;
};
exports.arrayBufferFrom = arrayBufferFrom;
},{}],10:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _srcEme = require('../src/eme');
_qunit2['default'].module('videojs-contrib-eme eme');
_qunit2['default'].test('accepts a license URL as an option', function (assert) {
var done = assert.async();
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
var callbacks = {};
_videoJs2['default'].xhr = function (options) {
xhrCalls.push(options);
};
navigator.requestMediaKeySystemAccess = function (keySystem, options) {
return new Promise(function (resolve, reject) {
callbacks.requestMediaKeySystemAccess = resolve;
});
};
(0, _srcEme.standard5July2016)({
video: {},
initDataType: '',
initData: '',
options: {
keySystems: {
'com.widevine.alpha': 'some-url'
}
}
});
var session = new _videoJs2['default'].EventTarget();
callbacks.requestMediaKeySystemAccess({
keySystem: 'com.widevine.alpha',
createMediaKeys: function createMediaKeys() {
return {
createSession: function createSession() {
return session;
}
};
}
});
setTimeout(function () {
session.trigger({
type: 'message',
message: 'the-message'
});
assert.equal(xhrCalls.length, 1, 'made one XHR');
assert.deepEqual(xhrCalls[0], {
uri: 'some-url',
method: 'POST',
responseType: 'arraybuffer',
body: 'the-message',
headers: {
'Content-type': 'application/octet-stream'
}
}, 'made request with proper options');
_videoJs2['default'].xhr = origXhr;
done();
});
});
_qunit2['default'].test('accepts a license URL as property', function (assert) {
var done = assert.async();
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
var callbacks = {};
_videoJs2['default'].xhr = function (options) {
xhrCalls.push(options);
};
navigator.requestMediaKeySystemAccess = function (keySystem, options) {
return new Promise(function (resolve, reject) {
callbacks.requestMediaKeySystemAccess = resolve;
});
};
(0, _srcEme.standard5July2016)({
video: {},
initDataType: '',
initData: '',
options: {
keySystems: {
'com.widevine.alpha': {
url: 'some-url'
}
}
}
});
var session = new _videoJs2['default'].EventTarget();
callbacks.requestMediaKeySystemAccess({
keySystem: 'com.widevine.alpha',
createMediaKeys: function createMediaKeys() {
return {
createSession: function createSession() {
return session;
}
};
}
});
setTimeout(function () {
session.trigger({
type: 'message',
message: 'the-message'
});
assert.equal(xhrCalls.length, 1, 'made one XHR');
assert.deepEqual(xhrCalls[0], {
uri: 'some-url',
method: 'POST',
responseType: 'arraybuffer',
body: 'the-message',
headers: {
'Content-type': 'application/octet-stream'
}
}, 'made request with proper options');
_videoJs2['default'].xhr = origXhr;
done();
});
});
_qunit2['default'].test('5 July 2016 lifecycle', function (assert) {
assert.expect(40);
var done = assert.async();
var callbacks = {};
var callCounts = {
requestMediaKeySystemAccess: 0,
getCertificate: 0,
getLicense: 0,
createSession: 0,
keySessionGenerateRequest: 0,
keySessionUpdate: 0,
createMediaKeys: 0
};
navigator.requestMediaKeySystemAccess = function (keySystem, options) {
callCounts.requestMediaKeySystemAccess++;
return new Promise(function (resolve, reject) {
callbacks.requestMediaKeySystemAccess = resolve;
});
};
var getCertificate = function getCertificate(emeOptions, callback) {
callCounts.getCertificate++;
callbacks.getCertificate = callback;
};
var getLicense = function getLicense(emeOptions, keyMessage, callback) {
callCounts.getLicense++;
callbacks.getLicense = callback;
};
var _setMediaKeys = undefined;
var video = {
setMediaKeys: function setMediaKeys(mediaKeys) {
_setMediaKeys = mediaKeys;
}
};
var options = {
keySystems: {
'org.w3.clearkey': {
getCertificate: getCertificate,
getLicense: getLicense
}
}
};
var keySessionEventListeners = {};
var mediaKeys = {
createSession: function createSession() {
callCounts.createSession++;
return {
addEventListener: function addEventListener(name, callback) {
keySessionEventListeners[name] = callback;
},
generateRequest: function generateRequest() {
callCounts.keySessionGenerateRequest++;
return new Promise(function () {});
},
update: function update() {
callCounts.keySessionUpdate++;
}
};
}
};
var keySystemAccess = {
keySystem: 'org.w3.clearkey',
createMediaKeys: function createMediaKeys() {
callCounts.createMediaKeys++;
return mediaKeys;
}
};
(0, _srcEme.standard5July2016)({
video: video,
initDataType: '',
initData: '',
options: options
});
// Step 1: get key system
assert.equal(callCounts.requestMediaKeySystemAccess, 1, 'access requested');
assert.equal(callCounts.getCertificate, 0, 'certificate not requested');
assert.equal(callCounts.createMediaKeys, 0, 'media keys not created');
assert.notEqual(mediaKeys, _setMediaKeys, 'media keys not yet set');
assert.equal(callCounts.createSession, 0, 'key session not created');
assert.equal(callCounts.keySessionGenerateRequest, 0, 'key session request not made');
assert.equal(callCounts.getLicense, 0, 'license not requested');
assert.equal(callCounts.keySessionUpdate, 0, 'key session not updated');
callbacks.requestMediaKeySystemAccess(keySystemAccess);
// requestMediaKeySystemAccess promise resolution
setTimeout(function () {
// Step 2: get certificate
assert.equal(callCounts.requestMediaKeySystemAccess, 1, 'access requested');
assert.equal(callCounts.getCertificate, 1, 'certificate requested');
assert.equal(callCounts.createMediaKeys, 0, 'media keys not created');
assert.notEqual(mediaKeys, _setMediaKeys, 'media keys not yet set');
assert.equal(callCounts.createSession, 0, 'key session not created');
assert.equal(callCounts.keySessionGenerateRequest, 0, 'key session request not made');
assert.equal(callCounts.getLicense, 0, 'license not requested');
assert.equal(callCounts.keySessionUpdate, 0, 'key session not updated');
callbacks.getCertificate(null, '');
// getCertificate promise resolution
setTimeout(function () {
// Step 3: create media keys, set them, and generate key session request
assert.equal(callCounts.requestMediaKeySystemAccess, 1, 'access requested');
assert.equal(callCounts.getCertificate, 1, 'certificate requested');
assert.equal(callCounts.createMediaKeys, 1, 'media keys created');
assert.equal(mediaKeys, _setMediaKeys, 'media keys set');
assert.equal(callCounts.createSession, 1, 'key session created');
assert.equal(callCounts.keySessionGenerateRequest, 1, 'key session request made');
assert.equal(callCounts.getLicense, 0, 'license not requested');
assert.equal(callCounts.keySessionUpdate, 0, 'key session not updated');
keySessionEventListeners.message({});
// Step 4: get license
assert.equal(callCounts.requestMediaKeySystemAccess, 1, 'access requested');
assert.equal(callCounts.getCertificate, 1, 'certificate requested');
assert.equal(callCounts.createMediaKeys, 1, 'media keys created');
assert.equal(mediaKeys, _setMediaKeys, 'media keys set');
assert.equal(callCounts.createSession, 1, 'key session created');
assert.equal(callCounts.keySessionGenerateRequest, 1, 'key session request made');
assert.equal(callCounts.getLicense, 1, 'license requested');
assert.equal(callCounts.keySessionUpdate, 0, 'key session not updated');
callbacks.getLicense();
// getLicense promise resolution
setTimeout(function () {
// Step 5: update key session
assert.equal(callCounts.requestMediaKeySystemAccess, 1, 'access requested');
assert.equal(callCounts.getCertificate, 1, 'certificate requested');
assert.equal(callCounts.createMediaKeys, 1, 'media keys created');
assert.equal(mediaKeys, _setMediaKeys, 'media keys set');
assert.equal(callCounts.createSession, 1, 'key session created');
assert.equal(callCounts.keySessionGenerateRequest, 1, 'key session request made');
assert.equal(callCounts.getLicense, 1, 'license requested');
assert.equal(callCounts.keySessionUpdate, 1, 'key session updated');
done();
});
});
});
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/eme":4}],11:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _srcFairplay = require('../src/fairplay');
var _srcFairplay2 = _interopRequireDefault(_srcFairplay);
_qunit2['default'].module('videojs-contrib-eme fairplay');
_qunit2['default'].test('lifecycle', function (assert) {
assert.expect(19);
var done = assert.async();
var initData = new Uint8Array([1, 2, 3, 4]).buffer;
var callbacks = {};
var callCounts = {
getCertificate: 0,
getLicense: 0,
updateKeySession: 0,
createSession: 0
};
var getCertificate = function getCertificate(emeOptions, callback) {
callCounts.getCertificate++;
callbacks.getCertificate = callback;
};
var getLicense = function getLicense(emeOptions, contentId, keyMessage, callback) {
callCounts.getLicense++;
callbacks.getLicense = callback;
};
var options = {
keySystems: {
'com.apple.fps.1_0': {
getCertificate: getCertificate,
getLicense: getLicense,
// not needed due to mocking
getContentId: function getContentId() {
return 'some content id';
}
}
}
};
// trap event listeners
var keySessionEventListeners = {};
var updateKeySession = function updateKeySession(key) {
callCounts.updateKeySession++;
};
var onKeySessionCreated = undefined;
var createSession = function createSession(type, concatenatedData) {
callCounts.createSession++;
return {
addEventListener: function addEventListener(name, callback) {
keySessionEventListeners[name] = callback;
if (name === 'webkitkeyerror') {
// Since we don't have a way of executing code at the end of addKey's promise,
// we assume that adding the listener for webkitkeyerror is the last run code
// within the promise.
onKeySessionCreated();
}
},
update: updateKeySession
};
};
// mock webkitKeys to avoid browser specific calls and enable us to verify ordering
var video = {
webkitKeys: {
createSession: createSession
}
};
(0, _srcFairplay2['default'])({ video: video, initData: initData, options: options }).then(function () {
done();
});
// Step 1: getCertificate
assert.equal(callCounts.getCertificate, 1, 'getCertificate has been called');
assert.equal(callCounts.createSession, 0, 'a key session has not been created');
assert.equal(callCounts.getLicense, 0, 'getLicense has not been called');
assert.equal(callCounts.updateKeySession, 0, 'updateKeySession has not been called');
callbacks.getCertificate(null, new Uint16Array([4, 5, 6, 7]).buffer);
onKeySessionCreated = function () {
// Step 2: create a key session
assert.equal(callCounts.getCertificate, 1, 'getCertificate has been called');
assert.equal(callCounts.createSession, 1, 'a key session has been created');
assert.equal(callCounts.getLicense, 0, 'getLicense has not been called');
assert.equal(callCounts.updateKeySession, 0, 'updateKeySession has not been called');
assert.ok(keySessionEventListeners.webkitkeymessage, 'added an event listener for webkitkeymessage');
assert.ok(keySessionEventListeners.webkitkeyadded, 'added an event listener for webkitkeyadded');
assert.ok(keySessionEventListeners.webkitkeyerror, 'added an event listener for webkitkeyerror');
keySessionEventListeners.webkitkeymessage({});
// Step 3: get the key on webkitkeymessage
assert.equal(callCounts.getCertificate, 1, 'getCertificate has been called');
assert.equal(callCounts.createSession, 1, 'a key session has been created');
assert.equal(callCounts.getLicense, 1, 'getLicense has been called');
assert.equal(callCounts.updateKeySession, 0, 'updateKeySession has not been called');
callbacks.getLicense(null, []);
// Step 4: update the key session with the key
assert.equal(callCounts.getCertificate, 1, 'getCertificate has been called');
assert.equal(callCounts.createSession, 1, 'a key session has been created');
assert.equal(callCounts.getLicense, 1, 'getLicense has been called');
assert.equal(callCounts.updateKeySession, 1, 'updateKeySession has been called');
keySessionEventListeners.webkitkeyadded();
};
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/fairplay":5}],12:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
var _playreadyMessage = require('./playready-message');
var _srcMsPrefixed = require('../src/ms-prefixed');
var _srcMsPrefixed2 = _interopRequireDefault(_srcMsPrefixed);
var _utils = require('./utils');
var _utils2 = _interopRequireDefault(_utils);
_qunit2['default'].module('videojs-contrib-eme ms-prefixed', {
beforeEach: function beforeEach() {
var _this = this;
this.origMSMediaKeys = _globalWindow2['default'].MSMediaKeys;
_globalWindow2['default'].MSMediaKeys = function () {};
var session = new _videoJs2['default'].EventTarget();
session.keys = [];
session.update = function (key) {
return session.keys.push(key);
};
// mock the video since the APIs won't be available on non IE11 browsers
var video = {
msSetMediaKeys: function msSetMediaKeys() {
video.msKeys = {
createSession: function createSession() {
return _this.session;
}
};
}
};
this.session = session;
this.video = video;
},
afterEach: function afterEach() {
_globalWindow2['default'].MSMediaKeys = this.origMSMediaKeys;
}
});
_qunit2['default'].test('overwrites msKeys', function (assert) {
var origMsKeys = {};
this.video.msKeys = origMsKeys;
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': true
}
}
});
assert.notEqual(this.video.msKeys, origMsKeys, 'overwrote msKeys');
});
_qunit2['default'].test('logs error when on key error', function (assert) {
var origErrorLog = _videoJs2['default'].log.error;
var errorMessage = undefined;
_videoJs2['default'].log.error = function (message) {
errorMessage = message;
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': true
}
}
});
this.session.error = {
code: 5,
systemCode: 9
};
this.session.trigger('mskeyerror');
assert.equal(errorMessage, 'Unexpected key error from key session with code: 5 and systemCode: 9', 'logged error message');
_videoJs2['default'].log.error = origErrorLog;
});
_qunit2['default'].test('calls getKey when provided on key message', function (assert) {
var passedOptions = null;
var passedDestinationURL = null;
var passedBuffer = null;
var passedCallback = null;
var getKeyCallback = function getKeyCallback(callback) {
callback(null, 'a key');
};
var emeOptions = {
keySystems: {
'com.microsoft.playready': {
getKey: function getKey(options, destinationURL, buffer, callback) {
passedOptions = options;
passedDestinationURL = destinationURL;
passedBuffer = buffer;
passedCallback = callback;
getKeyCallback(callback);
}
}
}
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: emeOptions
});
assert.notOk(passedOptions, 'getKey not called');
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'url',
message: {
buffer: 'buffer'
}
});
assert.equal(passedOptions, emeOptions, 'getKey called with options');
assert.equal(passedDestinationURL, 'url', 'getKey called with destinationURL');
assert.equal(passedBuffer, 'buffer', 'getKey called with buffer');
assert.equal(typeof passedCallback, 'function', 'getKey called with callback');
assert.equal(this.session.keys.length, 1, 'added key to session');
assert.equal(this.session.keys[0], 'a key', 'added correct key to session');
var origErrorLog = _videoJs2['default'].log.error;
var errorMessage = undefined;
_videoJs2['default'].log.error = function (message) {
errorMessage = message;
};
getKeyCallback = function (callback) {
callback('an error', 'an errored key');
};
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'url',
message: {
buffer: 'buffer'
}
});
assert.equal(errorMessage, 'Unable to get key: an error', 'logs error when callback has an error');
assert.equal(this.session.keys.length, 1, 'did not add a new key');
_videoJs2['default'].log.error = origErrorLog;
});
_qunit2['default'].test('makes request when nothing provided on key message', function (assert) {
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
_videoJs2['default'].xhr = function (config, callback) {
return xhrCalls.push({ config: config, callback: callback });
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': true
}
}
});
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'destination-url',
message: {
buffer: (0, _playreadyMessage.createMessageBuffer)()
}
});
assert.equal(xhrCalls.length, 1, 'one xhr request');
assert.equal(xhrCalls[0].config.uri, 'destination-url', 'made request to destinationURL');
assert.deepEqual(xhrCalls[0].config.headers, {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}, 'uses headers from message');
assert.equal(xhrCalls[0].config.body, _playreadyMessage.challengeElement, 'sends the challenge element');
assert.equal(xhrCalls[0].config.method, 'post', 'request is a post');
assert.equal(xhrCalls[0].config.responseType, 'arraybuffer', 'responseType is an arraybuffer');
var origErrorLog = _videoJs2['default'].log.error;
var errorMessage = undefined;
_videoJs2['default'].log.error = function (message) {
errorMessage = message;
};
var response = {
body: _utils2['default'].stringToArrayBuffer('key value')
};
xhrCalls[0].callback('an error', response);
assert.equal(errorMessage, 'Unable to request key from url: destination-url', 'logs error when callback has an error');
assert.equal(this.session.keys.length, 0, 'no key added to session');
xhrCalls[0].callback(null, response);
assert.equal(this.session.keys.length, 1, 'key added to session');
assert.deepEqual(this.session.keys[0], new Uint8Array(response.body), 'correct key added to session');
_videoJs2['default'].log.error = origErrorLog;
_videoJs2['default'].xhr = origXhr;
});
_qunit2['default'].test('makes request on key message when empty object provided in options', function (assert) {
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
_videoJs2['default'].xhr = function (config, callback) {
return xhrCalls.push({ config: config, callback: callback });
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': {}
}
}
});
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'destination-url',
message: {
buffer: (0, _playreadyMessage.createMessageBuffer)()
}
});
assert.equal(xhrCalls.length, 1, 'one xhr request');
assert.equal(xhrCalls[0].config.uri, 'destination-url', 'made request to destinationURL');
assert.deepEqual(xhrCalls[0].config.headers, {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}, 'uses headers from message');
assert.equal(xhrCalls[0].config.body, _playreadyMessage.challengeElement, 'sends the challenge element');
assert.equal(xhrCalls[0].config.method, 'post', 'request is a post');
assert.equal(xhrCalls[0].config.responseType, 'arraybuffer', 'responseType is an arraybuffer');
_videoJs2['default'].xhr = origXhr;
});
_qunit2['default'].test('makes request with provided url string on key message', function (assert) {
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
_videoJs2['default'].xhr = function (config, callback) {
return xhrCalls.push({ config: config, callback: callback });
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': 'provided-url'
}
}
});
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'destination-url',
message: {
buffer: (0, _playreadyMessage.createMessageBuffer)([{
name: 'Content-Type',
value: 'text/xml; charset=utf-8'
}, {
name: 'SOAPAction',
value: '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}])
}
});
assert.equal(xhrCalls.length, 1, 'one xhr request');
assert.equal(xhrCalls[0].config.uri, 'provided-url', 'made request to provided-url');
assert.deepEqual(xhrCalls[0].config.headers, {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}, 'uses headers from message');
assert.equal(xhrCalls[0].config.body, _playreadyMessage.challengeElement, 'sends the challenge element');
assert.equal(xhrCalls[0].config.method, 'post', 'request is a post');
assert.equal(xhrCalls[0].config.responseType, 'arraybuffer', 'responseType is an arraybuffer');
var origErrorLog = _videoJs2['default'].log.error;
var errorMessage = undefined;
_videoJs2['default'].log.error = function (message) {
errorMessage = message;
};
var response = {
body: _utils2['default'].stringToArrayBuffer('key value')
};
xhrCalls[0].callback('an error', response);
assert.equal(errorMessage, 'Unable to request key from url: provided-url', 'logs error when callback has an error');
assert.equal(this.session.keys.length, 0, 'no key added to session');
xhrCalls[0].callback(null, response);
assert.equal(this.session.keys.length, 1, 'key added to session');
assert.deepEqual(this.session.keys[0], new Uint8Array(response.body), 'correct key added to session');
_videoJs2['default'].log.error = origErrorLog;
_videoJs2['default'].xhr = origXhr;
});
_qunit2['default'].test('makes request with provided url on key message', function (assert) {
var origXhr = _videoJs2['default'].xhr;
var xhrCalls = [];
_videoJs2['default'].xhr = function (config, callback) {
return xhrCalls.push({ config: config, callback: callback });
};
(0, _srcMsPrefixed2['default'])({
video: this.video,
initData: '',
options: {
keySystems: {
'com.microsoft.playready': {
url: 'provided-url'
}
}
}
});
this.session.trigger({
type: 'mskeymessage',
destinationURL: 'destination-url',
message: {
buffer: (0, _playreadyMessage.createMessageBuffer)([{
name: 'Content-Type',
value: 'text/xml; charset=utf-8'
}, {
name: 'SOAPAction',
value: '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}])
}
});
assert.equal(xhrCalls.length, 1, 'one xhr request');
assert.equal(xhrCalls[0].config.uri, 'provided-url', 'made request to provided-url');
assert.deepEqual(xhrCalls[0].config.headers, {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}, 'uses headers from message');
assert.equal(xhrCalls[0].config.body, _playreadyMessage.challengeElement, 'sends the challenge element');
assert.equal(xhrCalls[0].config.method, 'post', 'request is a post');
assert.equal(xhrCalls[0].config.responseType, 'arraybuffer', 'responseType is an arraybuffer');
var origErrorLog = _videoJs2['default'].log.error;
var errorMessage = undefined;
_videoJs2['default'].log.error = function (message) {
errorMessage = message;
};
var response = {
body: _utils2['default'].stringToArrayBuffer('key value')
};
xhrCalls[0].callback('an error', response);
assert.equal(errorMessage, 'Unable to request key from url: provided-url', 'logs error when callback has an error');
assert.equal(this.session.keys.length, 0, 'no key added to session');
xhrCalls[0].callback(null, response);
assert.equal(this.session.keys.length, 1, 'key added to session');
assert.deepEqual(this.session.keys[0], new Uint8Array(response.body), 'correct key added to session');
_videoJs2['default'].log.error = origErrorLog;
_videoJs2['default'].xhr = origXhr;
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/ms-prefixed":6,"./playready-message":13,"./utils":16,"global/window":3}],13:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _globalWindow = require('global/window');
var _globalWindow2 = _interopRequireDefault(_globalWindow);
var _utils = require('./utils');
var _utils2 = _interopRequireDefault(_utils);
var license = 'this is a license';
exports.license = license;
var challengeElement = '\n <?xml version="1.0" encoding="utf-8"?>\n <soap:Envelope\n xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\n xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n xmlns:xsd="http://www.w3.org/2001/XMLSchema">\n <soap:Body>\n <AcquireLicenseResponse xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">\n <AcquireLicenseResult>\n <Response xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols/messages">\n <LicenseResponse xmlns="http://schemas.microsoft.com/DRM/2007/03/protocols">\n <Version>1</Version>\n <Licenses>\n <License>' + license + '</License>\n </Licenses>\n </LicenseResponse>\n </Response>\n </AcquireLicenseResult>\n </AcquireLicenseResponse>\n </soap:Body>\n </soap:Envelope>';
exports.challengeElement = challengeElement;
var defaultHeaders = [{
name: 'Content-Type',
value: 'text/xml; charset=utf-8'
}, {
name: 'SOAPAction',
value: '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}];
var createHeaders = function createHeaders(headers) {
return headers.reduce(function (acc, header) {
return acc + ('\n <HttpHeader>\n <name>' + header.name + '</name>\n <value>' + header.value + '</value>\n </HttpHeader>\n ');
}, '');
};
var createMessageBuffer = function createMessageBuffer(headers) {
headers = headers || defaultHeaders;
// can't use TextEncoder because Safari doesn't support it
return _utils2['default'].stringToArrayBuffer('\n <PlayReadyKeyMessage type="LicenseAcquisition">\n <LicenseAcquisition Version="1">\n <Challenge encoding="base64encoded">' + _globalWindow2['default'].btoa(challengeElement) + '</Challenge>\n <HttpHeaders>' + createHeaders(headers) + '</HttpHeaders>\n </LicenseAcquisition>\n </PlayReadyKeyMessage>');
};
exports.createMessageBuffer = createMessageBuffer;
},{"./utils":16,"global/window":3}],14:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _srcPlayready = require('../src/playready');
var _playreadyMessage = require('./playready-message');
_qunit2['default'].module('playready');
_qunit2['default'].test('getMessageContents parses message contents', function (assert) {
var _getMessageContents = (0, _srcPlayready.getMessageContents)((0, _playreadyMessage.createMessageBuffer)());
var headers = _getMessageContents.headers;
var message = _getMessageContents.message;
assert.deepEqual(headers, {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': '"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"'
}, 'parses headers');
assert.deepEqual(message, _playreadyMessage.challengeElement, 'parses challenge element');
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/playready":7,"./playready-message":13}],15:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _globalDocument = require('global/document');
var _globalDocument2 = _interopRequireDefault(_globalDocument);
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _sinon = (typeof window !== "undefined" ? window['sinon'] : typeof global !== "undefined" ? global['sinon'] : null);
var _sinon2 = _interopRequireDefault(_sinon);
var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
var _videoJs2 = _interopRequireDefault(_videoJs);
var _srcPlugin = require('../src/plugin');
var _srcPlugin2 = _interopRequireDefault(_srcPlugin);
var Player = _videoJs2['default'].getComponent('Player');
_qunit2['default'].test('the environment is sane', function (assert) {
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
assert.strictEqual(typeof _sinon2['default'], 'object', 'sinon exists');
assert.strictEqual(typeof _videoJs2['default'], 'function', 'videojs exists');
assert.strictEqual(typeof _srcPlugin2['default'], 'function', 'plugin is a function');
});
_qunit2['default'].module('videojs-contrib-eme', {
beforeEach: function beforeEach() {
// Mock the environment's timers because certain things - particularly
// player readiness - are asynchronous in video.js 5. This MUST come
// before any player is created; otherwise, timers could get created
// with the actual timer methods!
this.clock = _sinon2['default'].useFakeTimers();
this.fixture = _globalDocument2['default'].getElementById('qunit-fixture');
this.video = _globalDocument2['default'].createElement('video');
this.fixture.appendChild(this.video);
this.player = (0, _videoJs2['default'])(this.video);
},
afterEach: function afterEach() {
this.player.dispose();
this.clock.restore();
}
});
_qunit2['default'].test('registers itself with video.js', function (assert) {
assert.strictEqual(typeof Player.prototype.eme, 'function', 'videojs-contrib-eme plugin was registered');
});
_qunit2['default'].test('exposes options', function (assert) {
assert.notOk(this.player.eme.options, 'options is unavailable at start');
this.player.eme();
assert.deepEqual(this.player.eme.options, {}, 'options defaults to empty object once initialized');
this.video = _globalDocument2['default'].createElement('video');
this.video.setAttribute('data-setup', JSON.stringify({
plugins: {
eme: {
applicationId: 'application-id',
publisherId: 'publisher-id'
}
}
}));
this.fixture.appendChild(this.video);
this.player = (0, _videoJs2['default'])(this.video);
assert.ok(this.player.eme.options, 'exposes options');
assert.strictEqual(this.player.eme.options.applicationId, 'application-id', 'exposes applicationId');
assert.strictEqual(this.player.eme.options.publisherId, 'publisher-id', 'exposes publisherId');
});
_qunit2['default'].module('plugin guard functions');
_qunit2['default'].test('handleEncryptedEvent checks for required options', function (assert) {
var initData1 = new Uint8Array([1, 2, 3]).buffer;
var event = {
// mock video target to prevent errors since it's a pain to mock out the continuation
// of functionality on a successful pass through of the guards
target: {},
initData: initData1
};
var options = {};
var sessions = [];
(0, _srcPlugin.handleEncryptedEvent)(event, options, sessions);
assert.equal(sessions.length, 0, 'did not create a session when no options');
options = { keySystems: {} };
(0, _srcPlugin.handleEncryptedEvent)(event, options, sessions);
assert.equal(sessions.length, 1, 'created a session when keySystems in options');
assert.equal(sessions[0].initData, initData1, 'captured initData in the session');
var initData2 = new Uint8Array([4, 5, 6]).buffer;
event.initData = initData2;
(0, _srcPlugin.handleEncryptedEvent)(event, options, sessions);
assert.equal(sessions.length, 2, 'created a new session when new init data');
assert.equal(sessions[0].initData, initData1, 'retained session init data');
assert.equal(sessions[1].initData, initData2, 'added new session init data');
event.initData = new Uint8Array([1, 2, 3]).buffer;
(0, _srcPlugin.handleEncryptedEvent)(event, options, sessions);
assert.equal(sessions.length, 2, 'no new session when same init data');
assert.equal(sessions[0].initData, initData1, 'retained session init data');
assert.equal(sessions[1].initData, initData2, 'retained session init data');
});
_qunit2['default'].test('handleMsNeedKeyEvent checks for required options', function (assert) {
var event = {
// mock video target to prevent errors since it's a pain to mock out the continuation
// of functionality on a successful pass through of the guards
target: {},
initData: new Uint8Array([1, 2, 3])
};
var options = {};
var sessions = [];
(0, _srcPlugin.handleMsNeedKeyEvent)(event, options, sessions);
assert.equal(sessions.length, 0, 'no session created when no options');
options = { keySystems: {} };
(0, _srcPlugin.handleMsNeedKeyEvent)(event, options, sessions);
assert.equal(sessions.length, 0, 'no session created when no PlayReady key system');
options = { keySystems: { 'com.microsoft.notplayready': true } };
(0, _srcPlugin.handleMsNeedKeyEvent)(event, options, sessions);
assert.equal(sessions.length, 0, 'no session created when no proper PlayReady key system');
options = { keySystems: { 'com.microsoft.playready': true } };
(0, _srcPlugin.handleMsNeedKeyEvent)(event, options, sessions);
assert.equal(sessions.length, 1, 'session created');
assert.ok(sessions[0].playready, 'created a PlayReady session');
var createdSession = sessions[0];
// even when there's new init data, we should not create a new session
event.initData = new Uint8Array([4, 5, 6]);
(0, _srcPlugin.handleMsNeedKeyEvent)(event, options, sessions);
assert.equal(sessions.length, 1, 'no new session created');
assert.equal(sessions[0], createdSession, 'did not replace session');
});
_qunit2['default'].test('handleWebKitNeedKeyEvent checks for required options', function (assert) {
var event = {};
var options = {};
assert.notOk((0, _srcPlugin.handleWebKitNeedKeyEvent)(event, options), 'no return when no options');
options = { keySystems: {} };
assert.notOk((0, _srcPlugin.handleWebKitNeedKeyEvent)(event, options), 'no return when no FairPlay key system');
options = { keySystems: { 'com.apple.notfps.1_0': {} } };
assert.notOk((0, _srcPlugin.handleWebKitNeedKeyEvent)(event, options), 'no return when no proper FairPlay key system');
options = { keySystems: { 'com.apple.fps.1_0': {} } };
assert.ok((0, _srcPlugin.handleWebKitNeedKeyEvent)(event, options), 'valid return when proper FairPlay key system');
});
_qunit2['default'].module('plugin isolated functions');
_qunit2['default'].test('hasSession determines if a session exists', function (assert) {
// cases in spec (where initData should always be an ArrayBuffer)
var initData = new Uint8Array([1, 2, 3]).buffer;
assert.notOk((0, _srcPlugin.hasSession)([], initData), 'false when no sessions');
assert.ok((0, _srcPlugin.hasSession)([{ initData: initData }], initData), 'true when initData is present in a session');
assert.ok((0, _srcPlugin.hasSession)([{}, { initData: new Uint8Array([1, 2, 3]).buffer }], initData), 'true when same initData contents present in a session');
assert.notOk((0, _srcPlugin.hasSession)([{ initData: new Uint8Array([1, 2]).buffer }], initData), 'false when initData contents not present in a session');
// cases outside of spec (where initData is not always an ArrayBuffer)
assert.ok((0, _srcPlugin.hasSession)([{ initData: new Uint8Array([1, 2, 3]) }], initData), 'true even if session initData is a typed array and initData is an ArrayBuffer');
assert.ok((0, _srcPlugin.hasSession)([{ initData: new Uint8Array([1, 2, 3]).buffer }], new Uint8Array([1, 2, 3])), 'true even if session initData is an ArrayBuffer and initData is a typed array');
assert.ok((0, _srcPlugin.hasSession)([{ initData: new Uint8Array([1, 2, 3]) }], new Uint8Array([1, 2, 3])), 'true even if both session initData and initData are typed arrays');
});
_qunit2['default'].test('setupSessions sets up sessions for new sources', function (assert) {
// mock the player with an eme plugin object attached to it
var _src = 'some-src';
var player = { eme: {}, src: function src() {
return _src;
} };
(0, _srcPlugin.setupSessions)(player);
assert.ok(Array.isArray(player.eme.sessions), 'creates a sessions array when none exist');
assert.equal(player.eme.sessions.length, 0, 'sessions array is empty');
assert.equal(player.eme.activeSrc, 'some-src', 'set activeSrc property');
(0, _srcPlugin.setupSessions)(player);
assert.equal(player.eme.sessions.length, 0, 'sessions array is still empty');
assert.equal(player.eme.activeSrc, 'some-src', 'activeSrc property did not change');
player.eme.sessions.push({});
_src = 'other-src';
(0, _srcPlugin.setupSessions)(player);
assert.equal(player.eme.sessions.length, 0, 'sessions array reset');
assert.equal(player.eme.activeSrc, 'other-src', 'activeSrc property changed');
player.eme.sessions.push({});
(0, _srcPlugin.setupSessions)(player);
assert.equal(player.eme.sessions.length, 1, 'sessions array unchanged');
assert.equal(player.eme.activeSrc, 'other-src', 'activeSrc property unchanged');
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/plugin":8,"global/document":2}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var stringToArrayBuffer = function stringToArrayBuffer(string) {
var buffer = new ArrayBuffer(string.length * 2);
var typedArray = new Uint16Array(buffer);
for (var i = 0; i < string.length; i++) {
typedArray[i] = string.charCodeAt(i);
}
return buffer;
};
exports["default"] = {
stringToArrayBuffer: stringToArrayBuffer
};
module.exports = exports["default"];
},{}],17:[function(require,module,exports){
(function (global){
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _qunit = (typeof window !== "undefined" ? window['QUnit'] : typeof global !== "undefined" ? global['QUnit'] : null);
var _qunit2 = _interopRequireDefault(_qunit);
var _srcUtils = require('../src/utils');
_qunit2['default'].module('utils');
_qunit2['default'].test('arrayBuffersEqual checks if two array buffers are equal', function (assert) {
assert.ok((0, _srcUtils.arrayBuffersEqual)(new ArrayBuffer(3), new ArrayBuffer(3)), 'same size empty array buffers are equal');
assert.notOk((0, _srcUtils.arrayBuffersEqual)(new ArrayBuffer(2), new ArrayBuffer(3)), 'different size empty array buffers are not equal');
var arrayBuffer = new ArrayBuffer(10);
assert.ok((0, _srcUtils.arrayBuffersEqual)(arrayBuffer, arrayBuffer), 'same array buffer is equal');
assert.ok((0, _srcUtils.arrayBuffersEqual)(new Uint8Array([1, 2, 3]).buffer, new Uint8Array([1, 2, 3]).buffer), 'array buffers with same content are equal');
assert.notOk((0, _srcUtils.arrayBuffersEqual)(new Uint8Array([1, 2, 3]).buffer, new Uint8Array([1, 2, 4]).buffer), 'array buffers with different content are not equal');
assert.notOk((0, _srcUtils.arrayBuffersEqual)(new Uint8Array([1, 2, 3]).buffer, new Uint8Array([1, 2]).buffer), 'array buffers with different content lengths are not equal');
});
_qunit2['default'].test('arrayBufferFrom returns buffer from typed arrays', function (assert) {
var uint8Array = new Uint8Array([1, 2, 3]);
var buffer = (0, _srcUtils.arrayBufferFrom)(uint8Array);
assert.ok(buffer instanceof ArrayBuffer, 'returned an ArrayBuffer');
assert.equal(buffer, uint8Array.buffer, 'buffer is the Uint8Array\'s buffer');
var uint16Array = new Uint16Array([4, 5, 6]);
buffer = (0, _srcUtils.arrayBufferFrom)(uint16Array);
assert.ok(buffer instanceof ArrayBuffer, 'returned an ArrayBuffer');
assert.equal(buffer, uint16Array.buffer, 'buffer is the Uint16Array\'s buffer');
buffer = (0, _srcUtils.arrayBufferFrom)(buffer);
assert.ok(buffer instanceof ArrayBuffer, 'buffer is still an ArrayBuffer');
assert.equal(buffer, uint16Array.buffer, 'buffer is the same buffer');
});
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../src/utils":9}]},{},[10,11,12,14,15,17]);