diff --git a/src/eme.js b/src/eme.js index cd22f1f..0edcd2e 100644 --- a/src/eme.js +++ b/src/eme.js @@ -2,6 +2,8 @@ import videojs from 'video.js'; import { requestPlayreadyLicense } from './playready'; import window from 'global/window'; import {mergeAndRemoveNull} from './utils'; +import {defaultGetCertificate as defaultFairplayGetCertificate, + defaultGetLicense as defaultFairplayGetLicense } from './fairplay'; export const getSupportedKeySystem = (keySystems) => { // As this happens after the src is set on the video, we rely only on the set src (we @@ -12,6 +14,7 @@ export const getSupportedKeySystem = (keySystems) => { Object.keys(keySystems).forEach((keySystem) => { // TODO use initDataTypes when appropriate const systemOptions = {}; + const initDataTypes = keySystems[keySystem].initDataTypes; const audioContentType = keySystems[keySystem].audioContentType; const videoContentType = keySystems[keySystem].videoContentType; @@ -25,6 +28,9 @@ export const getSupportedKeySystem = (keySystems) => { contentType: videoContentType }]; } + if (initDataTypes) { + systemOptions.initDataTypes = initDataTypes; + } if (!promise) { promise = window.navigator.requestMediaKeySystemAccess(keySystem, [systemOptions]); @@ -49,7 +55,6 @@ export const makeNewRequest = ({ const keySession = mediaKeys.createSession(); return new Promise((resolve, reject) => { - keySession.addEventListener('message', (event) => { getLicense(options, event.message) .then((license) => { @@ -206,10 +211,10 @@ const defaultGetLicense = (keySystemOptions) => (emeOptions, keyMessage, callbac }); }; -const promisifyGetLicense = (getLicenseFn, eventBus) => { +const promisifyGetLicense = (keySystem, getLicenseFn, eventBus) => { return (emeOptions, keyMessage) => { return new Promise((resolve, reject) => { - getLicenseFn(emeOptions, keyMessage, (err, license) => { + const callback = (err, license) => { if (eventBus) { eventBus.trigger('licenserequestattempted'); } @@ -219,7 +224,13 @@ const promisifyGetLicense = (getLicenseFn, eventBus) => { } resolve(license); - }); + }; + + if (keySystem.startsWith('com.apple.fps')) { + getLicenseFn(emeOptions, null, keyMessage, callback); + } else { + getLicenseFn(emeOptions, keyMessage, callback); + } }); }; }; @@ -228,15 +239,30 @@ const standardizeKeySystemOptions = (keySystem, keySystemOptions) => { if (typeof keySystemOptions === 'string') { keySystemOptions = { url: keySystemOptions }; } + if (typeof keySystemOptions.licenseUri !== 'undefined') { + keySystemOptions = { url: keySystemOptions.licenseUri }; + } if (!keySystemOptions.url && !keySystemOptions.getLicense) { throw new Error('Neither URL nor getLicense function provided to get license'); } + if (typeof keySystemOptions.certificateUri !== 'undefined') { + keySystemOptions.getCertificate = defaultFairplayGetCertificate(keySystemOptions); + } + if (keySystemOptions.url && !keySystemOptions.getLicense) { - keySystemOptions.getLicense = keySystem === 'com.microsoft.playready' ? - defaultPlayreadyGetLicense(keySystemOptions) : - defaultGetLicense(keySystemOptions); + if (keySystem === 'com.microsoft.playready') { + keySystemOptions.getLicense = defaultPlayreadyGetLicense(keySystemOptions); + } else if (keySystem.startsWith('com.apple.fps')) { + keySystemOptions.getLicense = defaultFairplayGetLicense(keySystemOptions); + } else { + keySystemOptions.getLicense = defaultGetLicense(keySystemOptions); + } + } + + if (keySystem.startsWith('com.apple.fps') && !keySystemOptions.getCertificate) { + throw new Error('Neither URL nor getCertificate provided'); } return keySystemOptions; @@ -252,6 +278,7 @@ export const standard5July2016 = ({ eventBus }) => { let keySystemPromise = Promise.resolve(); + const keySystem = keySystemAccess.keySystem; if (typeof video.mediaKeysObject === 'undefined') { // Prevent entering this path again. @@ -265,14 +292,14 @@ export const standard5July2016 = ({ keySystemPromise = new Promise((resolve, reject) => { // save key system for adding sessions - video.keySystem = keySystemAccess.keySystem; + video.keySystem = keySystem; keySystemOptions = standardizeKeySystemOptions( - keySystemAccess.keySystem, - options.keySystems[keySystemAccess.keySystem]); + keySystem, + options.keySystems[keySystem]); if (!keySystemOptions.getCertificate) { - resolve(keySystemAccess); + resolve(); return; } @@ -294,7 +321,7 @@ export const standard5July2016 = ({ certificate, createdMediaKeys, options, - getLicense: promisifyGetLicense(keySystemOptions.getLicense, eventBus), + getLicense: promisifyGetLicense(keySystem, keySystemOptions.getLicense, eventBus), removeSession, eventBus }); @@ -309,16 +336,22 @@ export const standard5July2016 = ({ } return keySystemPromise.then(() => { + let getLicenseFn; + + // addSession only needs getLicense if a key system has been determined + if (video.keySystem) { + getLicenseFn = standardizeKeySystemOptions(keySystem, + options.keySystems[keySystem]).getLicense; + // promisify the function + getLicenseFn = promisifyGetLicense(keySystem, getLicenseFn, eventBus); + } + return addSession({ video, initDataType, initData, 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, eventBus) : null, + getLicense: getLicenseFn, removeSession, eventBus }); diff --git a/src/plugin.js b/src/plugin.js index 2f2e18c..79d0db3 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,3 +1,4 @@ +import window from 'global/window'; import videojs from 'video.js'; import { standard5July2016, getSupportedKeySystem } from './eme'; import { @@ -212,56 +213,56 @@ const onPlayerReady = (player, emeError) => { setupSessions(player); - // Support EME 05 July 2016 - // Chrome 42+, Firefox 47+, Edge - player.tech_.el_.addEventListener('encrypted', (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, getOptions(player), player.eme.sessions, player.tech_) - .catch(emeError); - }); - // Support Safari EME with FairPlay - // (also used in early Chrome or Chrome with EME disabled flag) - player.tech_.el_.addEventListener('webkitneedkey', (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'); + if (window.MediaKeys) { + // Support EME 05 July 2016 + // Chrome 42+, Firefox 47+, Edge + player.tech_.el_.addEventListener('encrypted', (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, getOptions(player), player.eme.sessions, player.tech_) + .catch(emeError); + }); - // TODO it's possible that the video state must be cleared if reusing the same video - // element between sources - setupSessions(player); - handleWebKitNeedKeyEvent(event, getOptions(player), player.tech_) - .catch(emeError); - }); + } else if (window.WebKitMediaKeys) { + // Support Safari EME with FairPlay + // (also used in early Chrome or Chrome with EME disabled flag) + player.tech_.el_.addEventListener('webkitneedkey', (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'); - // EDGE still fires msneedkey, but should use encrypted instead - if (videojs.browser.IS_EDGE) { - return; - } + // TODO it's possible that the video state must be cleared if reusing the same video + // element between sources + setupSessions(player); + handleWebKitNeedKeyEvent(event, getOptions(player), player.tech_) + .catch(emeError); + }); - // IE11 Windows 8.1+ - // Since IE11 doesn't support promises, we have to use a combination of - // try/catch blocks and event handling to simulate promise rejection. - // Functionally speaking, there should be no discernible difference between - // the behavior of IE11 and those of other browsers. - player.tech_.el_.addEventListener('msneedkey', (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); - try { - handleMsNeedKeyEvent(event, getOptions(player), player.eme.sessions, player.tech_); - } catch (error) { - emeError(error); - } - }); - player.tech_.on('mskeyerror', emeError); - // TODO: refactor this plugin so it can use a plugin dispose - player.on('dispose', () => { - player.tech_.off('mskeyerror', emeError); - }); + } else if (window.MSMediaKeys) { + // IE11 Windows 8.1+ + // Since IE11 doesn't support promises, we have to use a combination of + // try/catch blocks and event handling to simulate promise rejection. + // Functionally speaking, there should be no discernible difference between + // the behavior of IE11 and those of other browsers. + player.tech_.el_.addEventListener('msneedkey', (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); + try { + handleMsNeedKeyEvent(event, getOptions(player), player.eme.sessions, player.tech_); + } catch (error) { + emeError(error); + } + }); + player.tech_.on('mskeyerror', emeError); + // TODO: refactor this plugin so it can use a plugin dispose + player.on('dispose', () => { + player.tech_.off('mskeyerror', emeError); + }); + } }; /** diff --git a/test/plugin.test.js b/test/plugin.test.js index e069f1e..0fd20bd 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -161,6 +161,11 @@ QUnit.test('initializeMediaKeys ms-prefix', function(assert) { let errors = 0; let keySession; let errorMessage; + const origMediaKeys = window.MediaKeys; + const origWebKitMediaKeys = window.WebKitMediaKeys; + + window.MediaKeys = undefined; + window.WebKitMediaKeys = undefined; if (!window.MSMediaKeys) { window.MSMediaKeys = () => {}; @@ -250,6 +255,8 @@ QUnit.test('initializeMediaKeys ms-prefix', function(assert) { assert.equal(errors, 3, 'error called on player 3 times'); assert.equal(this.player.error(), null, 'no error called on player with suppressError = true'); + window.MediaKeys = origMediaKeys; + window.WebKitMediaKeys = origWebKitMediaKeys; done(); }); this.clock.tick(1); @@ -262,7 +269,14 @@ QUnit.test('tech error listener is removed on dispose', function(assert) { const done = assert.async(1); let called = 0; const browser = videojs.browser; + const origMediaKeys = window.MediaKeys; + const origWebKitMediaKeys = window.WebKitMediaKeys; + window.MediaKeys = undefined; + window.WebKitMediaKeys = undefined; + if (!window.MSMediaKeys) { + window.MSMediaKeys = noop.bind(this); + } // let this test pass on edge videojs.browser = {IS_EDGE: false}; @@ -284,6 +298,8 @@ QUnit.test('tech error listener is removed on dispose', function(assert) { this.player.error = undefined; videojs.browser = browser; + window.MediaKeys = origMediaKeys; + window.WebKitMediaKeys = origWebKitMediaKeys; done(); });