UNPKG

videojs-contrib-eme

Version:

Supports Encrypted Media Extensions for playback of encrypted content in Video.js

235 lines (196 loc) 8.42 kB
'use strict'; 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;