From fdb57e30ec679dc934808d4c680745e62b163498 Mon Sep 17 00:00:00 2001 From: Gary Katsevman Date: Tue, 19 Oct 2021 16:41:34 -0400 Subject: [PATCH] revert: fix: use in-spec EME for versions of Safari which support it (#142) (#145) This reverts commit 58976556e19bd210a075b0190b0d0d45084b98c6. --- README.md | 1 - src/eme.js | 140 ++++++++++++++------------------------------ src/fairplay.js | 2 +- src/plugin.js | 29 ++++----- src/utils.js | 2 +- test/eme.test.js | 64 +++----------------- test/plugin.test.js | 51 +++------------- 7 files changed, 76 insertions(+), 213 deletions(-) diff --git a/README.md b/README.md index d0930ca..2c07d40 100644 --- a/README.md +++ b/README.md @@ -386,7 +386,6 @@ player.src({ }, keySystems: { 'org.w3.clearkey': { - initDataTypes: ['cenc', 'webm'], audioContentType: 'audio/webm; codecs="vorbis"', videoContentType: 'video/webm; codecs="vp9"', getCertificate: function(emeOptions, callback) { diff --git a/src/eme.js b/src/eme.js index 97781c2..2fca35d 100644 --- a/src/eme.js +++ b/src/eme.js @@ -3,13 +3,6 @@ import { requestPlayreadyLicense } from './playready'; import window from 'global/window'; import {mergeAndRemoveNull} from './utils'; import {httpResponseHandler} from './http-handler.js'; -import { - defaultGetCertificate as defaultFairplayGetCertificate, - defaultGetLicense as defaultFairplayGetLicense, - defaultGetContentId as defaultFairplayGetContentId -} from './fairplay'; - -const isFairplayKeySystem = (str) => str.startsWith('com.apple.fps'); /** * Returns an array of MediaKeySystemConfigurationObjects provided in the keySystem @@ -22,21 +15,16 @@ const isFairplayKeySystem = (str) => str.startsWith('com.apple.fps'); * @return {Object[]} * Array of MediaKeySystemConfigurationObjects */ -export const getSupportedConfigurations = (keySystem, keySystemOptions) => { +export const getSupportedConfigurations = (keySystemOptions) => { if (keySystemOptions.supportedConfigurations) { return keySystemOptions.supportedConfigurations; } - const isFairplay = isFairplayKeySystem(keySystem); + // TODO use initDataTypes when appropriate const supportedConfiguration = {}; - const initDataTypes = keySystemOptions.initDataTypes || - // fairplay requires an explicit initDataTypes - (isFairplay ? ['sinf'] : null); const audioContentType = keySystemOptions.audioContentType; const audioRobustness = keySystemOptions.audioRobustness; - const videoContentType = keySystemOptions.videoContentType || - // fairplay requires an explicit videoCapabilities/videoContentType - (isFairplay ? 'video/mp4' : null); + const videoContentType = keySystemOptions.videoContentType; const videoRobustness = keySystemOptions.videoRobustness; const persistentState = keySystemOptions.persistentState; @@ -64,10 +52,6 @@ export const getSupportedConfigurations = (keySystem, keySystemOptions) => { supportedConfiguration.persistentState = persistentState; } - if (initDataTypes) { - supportedConfiguration.initDataTypes = initDataTypes; - } - return [supportedConfiguration]; }; @@ -78,7 +62,7 @@ export const getSupportedKeySystem = (keySystems) => { let promise; Object.keys(keySystems).forEach((keySystem) => { - const supportedConfigurations = getSupportedConfigurations(keySystem, keySystems[keySystem]); + const supportedConfigurations = getSupportedConfigurations(keySystems[keySystem]); if (!promise) { promise = @@ -100,10 +84,8 @@ export const makeNewRequest = (requestOptions) => { options, getLicense, removeSession, - eventBus, - contentId + eventBus } = requestOptions; - const keySession = mediaKeys.createSession(); eventBus.trigger('keysessioncreated'); @@ -115,8 +97,7 @@ export const makeNewRequest = (requestOptions) => { if (event.messageType !== 'license-request' && event.messageType !== 'license-renewal') { return; } - - getLicense(options, event.message, contentId) + getLicense(options, event.message) .then((license) => { resolve(keySession.update(license)); }) @@ -211,27 +192,29 @@ export const addSession = ({ initData, options, getLicense, - contentId, removeSession, eventBus }) => { - const sessionData = { + if (video.mediaKeysObject) { + return makeNewRequest({ + mediaKeys: video.mediaKeysObject, + initDataType, + initData, + options, + getLicense, + removeSession, + eventBus + }); + } + + video.pendingSessionData.push({ initDataType, initData, options, getLicense, removeSession, - eventBus, - contentId - }; - - if (video.mediaKeysObject) { - sessionData.mediaKeys = video.mediaKeysObject; - return makeNewRequest(sessionData); - } - - video.pendingSessionData.push(sessionData); - + eventBus + }); return Promise.resolve(); }; @@ -278,8 +261,7 @@ export const addPendingSessions = ({ options: data.options, getLicense: data.getLicense, removeSession: data.removeSession, - eventBus: data.eventBus, - contentId: data.contentId + eventBus: data.eventBus })); } @@ -310,10 +292,10 @@ export const defaultGetLicense = (keySystemOptions) => (emeOptions, keyMessage, }, httpResponseHandler(callback, true)); }; -const promisifyGetLicense = (keySystem, getLicenseFn, eventBus) => { - return (emeOptions, keyMessage, contentId) => { +const promisifyGetLicense = (getLicenseFn, eventBus) => { + return (emeOptions, keyMessage) => { return new Promise((resolve, reject) => { - const callback = function(err, license) { + getLicenseFn(emeOptions, keyMessage, (err, license) => { if (eventBus) { eventBus.trigger('licenserequestattempted'); } @@ -323,13 +305,7 @@ const promisifyGetLicense = (keySystem, getLicenseFn, eventBus) => { } resolve(license); - }; - - if (isFairplayKeySystem(keySystem)) { - getLicenseFn(emeOptions, contentId, new Uint8Array(keyMessage), callback); - } else { - getLicenseFn(emeOptions, keyMessage, callback); - } + }); }); }; }; @@ -339,36 +315,14 @@ const standardizeKeySystemOptions = (keySystem, keySystemOptions) => { keySystemOptions = { url: keySystemOptions }; } - if (!keySystemOptions.url && keySystemOptions.licenseUri) { - keySystemOptions.url = keySystemOptions.licenseUri; - } - if (!keySystemOptions.url && !keySystemOptions.getLicense) { - throw new Error(`Missing url/licenseUri or getLicense in ${keySystem} keySystem configuration.`); - } - - const isFairplay = isFairplayKeySystem(keySystem); - - if (isFairplay && keySystemOptions.certificateUri && !keySystemOptions.getCertificate) { - keySystemOptions.getCertificate = defaultFairplayGetCertificate(keySystemOptions); - } - - if (isFairplay && !keySystemOptions.getCertificate) { - throw new Error(`Missing getCertificate or certificateUri in ${keySystem} keySystem configuration.`); - } - - if (isFairplay && !keySystemOptions.getContentId) { - keySystemOptions.getContentId = defaultFairplayGetContentId; + throw new Error('Neither URL nor getLicense function provided to get license'); } if (keySystemOptions.url && !keySystemOptions.getLicense) { - if (keySystem === 'com.microsoft.playready') { - keySystemOptions.getLicense = defaultPlayreadyGetLicense(keySystemOptions); - } else if (isFairplay) { - keySystemOptions.getLicense = defaultFairplayGetLicense(keySystemOptions); - } else { - keySystemOptions.getLicense = defaultGetLicense(keySystemOptions); - } + keySystemOptions.getLicense = keySystem === 'com.microsoft.playready' ? + defaultPlayreadyGetLicense(keySystemOptions) : + defaultGetLicense(keySystemOptions); } return keySystemOptions; @@ -384,21 +338,6 @@ export const standard5July2016 = ({ eventBus }) => { let keySystemPromise = Promise.resolve(); - const keySystem = keySystemAccess.keySystem; - let keySystemOptions; - - // try catch so that we return a promise rejection - try { - keySystemOptions = standardizeKeySystemOptions( - keySystem, - options.keySystems[keySystem] - ); - } catch (e) { - return Promise.reject(e); - } - - const contentId = keySystemOptions.getContentId ? - keySystemOptions.getContentId(options, initData) : null; if (typeof video.mediaKeysObject === 'undefined') { // Prevent entering this path again. @@ -408,10 +347,16 @@ export const standard5July2016 = ({ video.pendingSessionData = []; let certificate; + let keySystemOptions; keySystemPromise = new Promise((resolve, reject) => { // save key system for adding sessions - video.keySystem = keySystem; + video.keySystem = keySystemAccess.keySystem; + + keySystemOptions = standardizeKeySystemOptions( + keySystemAccess.keySystem, + options.keySystems[keySystemAccess.keySystem] + ); if (!keySystemOptions.getCertificate) { resolve(keySystemAccess); @@ -447,17 +392,18 @@ export const standard5July2016 = ({ } return keySystemPromise.then(() => { - // if key system has not been determined then addSession doesn't need getLicense - const getLicense = video.keySystem ? - promisifyGetLicense(keySystem, keySystemOptions.getLicense, eventBus) : null; + const {getLicense} = standardizeKeySystemOptions( + video.keySystem, + options.keySystems[video.keySystem] + ); return addSession({ video, initDataType, initData, options, - getLicense, - contentId, + // if key system has not been determined then addSession doesn't need getLicense + getLicense: video.keySystem ? promisifyGetLicense(getLicense, eventBus) : null, removeSession, eventBus }); diff --git a/src/fairplay.js b/src/fairplay.js index c5ec89c..4f0dfaa 100644 --- a/src/fairplay.js +++ b/src/fairplay.js @@ -128,7 +128,7 @@ export const defaultGetCertificate = (fairplayOptions) => { }; }; -export const defaultGetContentId = (emeOptions, initData) => { +const defaultGetContentId = (emeOptions, initData) => { return getHostnameFromUri(uint8ArrayToString(initData)); }; diff --git a/src/plugin.js b/src/plugin.js index 666f654..da16959 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -225,18 +225,7 @@ const onPlayerReady = (player, emeError) => { setupSessions(player); - if (window.MediaKeys) { - // Support EME 05 July 2016 - // Chrome 42+, Firefox 47+, Edge, Safari 12.1+ on macOS 10.14+ - 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); - }); - } else if (window.WebKitMediaKeys) { + if (window.WebKitMediaKeys) { const handleFn = (event) => { // TODO convert to videojs.log.debug and add back in // https://github.com/videojs/video.js/pull/4780 @@ -289,6 +278,18 @@ const onPlayerReady = (player, emeError) => { } }); + } else if (window.MediaKeys) { + // Support EME 05 July 2016 + // Chrome 42+, Firefox 47+, Edge, Safari 12.1+ on macOS 10.14+ + 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); + }); + } else if (window.MSMediaKeys) { // IE11 Windows 8.1+ // Since IE11 doesn't support promises, we have to use a combination of @@ -363,7 +364,7 @@ const eme = function(options = {}) { setupSessions(player); - if (window.MediaKeys) { + if (player.tech_.el_.setMediaKeys) { handleEncryptedEvent(mockEncryptedEvent, mergedEmeOptions, player.eme.sessions, player.tech_) .then(() => callback()) .catch((error) => { @@ -372,7 +373,7 @@ const eme = function(options = {}) { emeError(error); } }); - } else if (window.MSMediaKeys) { + } else if (player.tech_.el_.msSetMediaKeys) { const msKeyHandler = (event) => { player.tech_.off('mskeyadded', msKeyHandler); player.tech_.off('mskeyerror', msKeyHandler); diff --git a/src/utils.js b/src/utils.js index c5f990e..2f97b60 100644 --- a/src/utils.js +++ b/src/utils.js @@ -14,7 +14,7 @@ export const stringToUint16Array = (string) => { }; export const uint8ArrayToString = (array) => { - return String.fromCharCode.apply(null, new Uint8Array(array.buffer || array)); + return String.fromCharCode.apply(null, new Uint16Array(array.buffer)); }; export const getHostnameFromUri = (uri) => { diff --git a/test/eme.test.js b/test/eme.test.js index e374525..d6c3b79 100644 --- a/test/eme.test.js +++ b/test/eme.test.js @@ -1,6 +1,5 @@ import QUnit from 'qunit'; import videojs from 'video.js'; -import window from 'global/window'; import { defaultGetLicense, standard5July2016, @@ -574,7 +573,7 @@ if (!videojs.browser.IS_ANY_SAFARI) { }); } -QUnit.test('errors when missing url/licenseUri or getLicense', function(assert) { +QUnit.test('errors when neither url nor getLicense is given', function(assert) { const options = { keySystems: { 'com.widevine.alpha': {} @@ -593,32 +592,7 @@ QUnit.test('errors when missing url/licenseUri or getLicense', function(assert) }).catch((err) => { assert.equal( err, - 'Error: Missing url/licenseUri or getLicense in com.widevine.alpha keySystem configuration.', - 'correct error message' - ); - done(); - }); -}); - -QUnit.test('errors when missing certificateUri and getCertificate for fairplay', function(assert) { - const options = { - keySystems: { - 'com.apple.fps': {url: 'fake-url'} - } - }; - const keySystemAccess = { - keySystem: 'com.apple.fps' - }; - const done = assert.async(); - - standard5July2016({ - video: {}, - keySystemAccess, - options - }).catch((err) => { - assert.equal( - err, - 'Error: Missing getCertificate or certificateUri in com.apple.fps keySystem configuration.', + 'Error: Neither URL nor getLicense function provided to get license', 'correct error message' ); done(); @@ -1113,25 +1087,6 @@ QUnit.test('licenseHeaders keySystems property overrides emeHeaders value', func }); }); -QUnit.test('sets required fairplay defaults if not explicitly configured', function(assert) { - const origRequestMediaKeySystemAccess = window.navigator.requestMediaKeySystemAccess; - - window.navigator.requestMediaKeySystemAccess = (keySystem, systemOptions) => { - assert.ok( - systemOptions[0].initDataTypes.indexOf('sinf') !== -1, - 'includes required initDataType' - ); - assert.ok( - systemOptions[0].videoCapabilities[0].contentType.indexOf('video/mp4') !== -1, - 'includes required video contentType' - ); - }; - - getSupportedKeySystem({'com.apple.fps': {}}); - - window.requestMediaKeySystemAccess = origRequestMediaKeySystemAccess; -}); - QUnit.module('session management'); QUnit.test('addSession saves options', function(assert) { @@ -1144,11 +1099,9 @@ QUnit.test('addSession saves options', function(assert) { const getLicense = () => ''; const removeSession = () => ''; const eventBus = { trigger: () => {} }; - const contentId = null; addSession({ video, - contentId, initDataType, initData, options, @@ -1165,8 +1118,7 @@ QUnit.test('addSession saves options', function(assert) { options, getLicense, removeSession, - eventBus, - contentId + eventBus }], 'saved options into pendingSessionData array' ); @@ -1238,7 +1190,7 @@ QUnit.module('videojs-contrib-eme getSupportedConfigurations'); QUnit.test('includes audio and video content types', function(assert) { assert.deepEqual( - getSupportedConfigurations('com.widevine.alpha', { + getSupportedConfigurations({ audioContentType: 'audio/mp4; codecs="mp4a.40.2"', videoContentType: 'video/mp4; codecs="avc1.42E01E"' }), @@ -1256,7 +1208,7 @@ QUnit.test('includes audio and video content types', function(assert) { QUnit.test('includes audio and video robustness', function(assert) { assert.deepEqual( - getSupportedConfigurations('com.widevine.alpha', { + getSupportedConfigurations({ audioRobustness: 'SW_SECURE_CRYPTO', videoRobustness: 'SW_SECURE_CRYPTO' }), @@ -1274,7 +1226,7 @@ QUnit.test('includes audio and video robustness', function(assert) { QUnit.test('includes audio and video content types and robustness', function(assert) { assert.deepEqual( - getSupportedConfigurations('com.widevine.alpha', { + getSupportedConfigurations({ audioContentType: 'audio/mp4; codecs="mp4a.40.2"', audioRobustness: 'SW_SECURE_CRYPTO', videoContentType: 'video/mp4; codecs="avc1.42E01E"', @@ -1296,7 +1248,7 @@ QUnit.test('includes audio and video content types and robustness', function(ass QUnit.test('includes persistentState', function(assert) { assert.deepEqual( - getSupportedConfigurations('com.widevine.alpha', { persistentState: 'optional' }), + getSupportedConfigurations({ persistentState: 'optional' }), [{ persistentState: 'optional' }], 'included persistentState' ); @@ -1304,7 +1256,7 @@ QUnit.test('includes persistentState', function(assert) { QUnit.test('uses supportedConfigurations directly if provided', function(assert) { assert.deepEqual( - getSupportedConfigurations('com.widevine.alpha', { + getSupportedConfigurations({ supportedConfigurations: [{ initDataTypes: ['cenc'], audioCapabilities: [{ diff --git a/test/plugin.test.js b/test/plugin.test.js index 6c357af..676965d 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -107,8 +107,8 @@ QUnit.test('exposes options', function(assert) { ); }); -// skip test for prefix-only Safari -if (!window.MediaKeys) { +// skip test for Safari +if (!window.WebKitMediaKeys) { QUnit.test('initializeMediaKeys standard', function(assert) { assert.expect(9); const done = assert.async(); @@ -128,7 +128,7 @@ if (!window.MediaKeys) { assert.deepEqual(sessions[0].initData, initData, 'captured initData in the session'); assert.equal( error, - 'Error: Missing url/licenseUri or getLicense in com.widevine.alpha configuration.', + 'Error: Neither URL nor getLicense function provided to get license', 'callback receives error' ); }; @@ -140,7 +140,7 @@ if (!window.MediaKeys) { assert.equal(errors, 1, 'error triggered only once'); assert.equal( this.player.error().message, - 'Missing url/licenseUri or getLicense in com.widevine.alpha configuration.', + 'Neither URL nor getLicense function provided to get license', 'error is called on player' ); this.player.error(null); @@ -281,6 +281,7 @@ QUnit.test('initializeMediaKeys ms-prefix', function(assert) { 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; @@ -289,6 +290,8 @@ QUnit.test('tech error listener is removed on dispose', function(assert) { if (!window.MSMediaKeys) { window.MSMediaKeys = noop.bind(this); } + // let this test pass on edge + videojs.browser = {IS_EDGE: false}; this.player.error = () => { called++; @@ -307,6 +310,7 @@ QUnit.test('tech error listener is removed on dispose', function(assert) { assert.equal(called, 1, 'not called after player disposal'); this.player.error = undefined; + videojs.browser = browser; window.MediaKeys = origMediaKeys; window.WebKitMediaKeys = origWebKitMediaKeys; done(); @@ -315,45 +319,6 @@ QUnit.test('tech error listener is removed on dispose', function(assert) { this.clock.tick(1); }); -QUnit.test('only registers for spec-compliant events even if legacy APIs are available', function(assert) { - const done = assert.async(1); - - const origMediaKeys = window.MediaKeys; - const origMSMediaKeys = window.MSMediaKeys; - const origWebKitMediaKeys = window.WebKitMediaKeys; - - const events = { - encrypted: 0, - msneedkey: 0, - webkitneedkey: 0 - }; - - this.player.tech_.el_ = { - addEventListener: e => events[e]++, - hasAttribute: () => false - }; - - window.MediaKeys = noop; - window.MSMediaKeys = noop; - window.WebKitMediaKeys = noop; - - this.player.eme(); - - this.player.ready(() => { - assert.equal(events.encrypted, 1, 'registers for encrypted events'); - assert.equal(events.msneedkey, 0, "doesn't register for msneedkey events"); - assert.equal(events.webkitneedkey, 0, "doesn't register for webkitneedkey events"); - - window.MediaKeys = origMediaKeys; - window.MSMediaKeys = origMSMediaKeys; - window.WebKitMediaKeys = origWebKitMediaKeys; - done(); - }); - - this.clock.tick(1); - -}); - QUnit.module('plugin guard functions', { beforeEach() { this.options = {