From 49762c671b5e3436e80e26bf755b9067d32f2c19 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Thu, 30 Jan 2020 14:20:51 -0500 Subject: [PATCH 1/9] add cdm detection module --- src/cdm.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/cdm.js diff --git a/src/cdm.js b/src/cdm.js new file mode 100644 index 0000000..2a5ccb4 --- /dev/null +++ b/src/cdm.js @@ -0,0 +1,77 @@ +import window from 'global/window'; +import videojs from 'video.js'; + +const keySystems = { + fairplay: 'com.apple.fairplay', + playready: 'com.microsoft.playready', + widevine: 'com.widevine.alpha', + clearkey: 'org.w3.clearkey' +}; + +// Use a combination of API feature and user agent detection to provide an initial +// best guess as to which CDMs are supported +const bestGuessSupport = { + fairplay: !!window.WebKitMediaKeys, + playready: !!(window.MSMediaKeys && videojs.browser.IE_VERSION) || + !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess && videojs.browser.IS_EDGE), + widevine: !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess) && + (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX), + clearkey: !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess) && + (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX) +}; + +let latestSupportResults = bestGuessSupport; + +// Synchronously return the latest or best guess list of supported CDMs +export const getSupportedCDM = () => { + return latestSupportResults; +}; + +// Asynchronously detect the list of supported CDMs +export const detectCDMSupport = () => { + const results = { + fairplay: false, + playready: false, + widevine: false, + clearkey: false + }; + + if (window.WebKitMediaKeys) { + results.fairplay = true; + } + + if (window.MSMediaKeys && window.MSMediaKeys.isTypeSupported(keySystems.playready)) { + results.playready = true; + } + + // Note: `requestMediaKeySystemAccess` is undefined in Chrome unless over https + if (window.MediaKeys && window.navigator.requestMediaKeySystemAccess) { + const defaultConfig = [{ + initDataTypes: [], + audioCapabilities: [{ + contentType: 'audio/mp4;codecs="mp4a.40.2"' + }], + videoCapabilities: [{ + contentType: 'video/mp4;codecs="avc1.42E01E"' + }] + }]; + + return Promise.all([ + window.navigator.requestMediaKeySystemAccess(keySystems.widevine, defaultConfig).catch(() => {}), + window.navigator.requestMediaKeySystemAccess(keySystems.playready, defaultConfig).catch(() => {}), + window.navigator.requestMediaKeySystemAccess(keySystems.clearkey, defaultConfig).catch(() => {}) + ]).then(([widevine, playready, clearkey]) => { + results.widevine = !!widevine; + results.playready = !!playready; + results.clearkey = !!clearkey; + + // Update the guesses now that we have more definitive answers + latestSupportResults = results; + return results; + }); + } + // Update the guesses now that we have more definitive answers + latestSupportResults = results; + return Promise.resolve(results); + +}; From 7d27724588871d9f636e34b61becb1c1fd129771 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Fri, 31 Jan 2020 15:25:53 -0500 Subject: [PATCH 2/9] allow promise polyfill for detect func, expose on player.eme, add tests --- src/cdm.js | 35 ++++++++++++--------- src/plugin.js | 7 ++++- test/cdm.test.js | 74 +++++++++++++++++++++++++++++++++++++++++++++ test/plugin.test.js | 10 ++++++ 4 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 test/cdm.test.js diff --git a/src/cdm.js b/src/cdm.js index 2a5ccb4..fab6a22 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -9,7 +9,7 @@ const keySystems = { }; // Use a combination of API feature and user agent detection to provide an initial -// best guess as to which CDMs are supported +// best guess as to which CDMs are supported. const bestGuessSupport = { fairplay: !!window.WebKitMediaKeys, playready: !!(window.MSMediaKeys && videojs.browser.IE_VERSION) || @@ -22,13 +22,18 @@ const bestGuessSupport = { let latestSupportResults = bestGuessSupport; -// Synchronously return the latest or best guess list of supported CDMs -export const getSupportedCDM = () => { +// Synchronously return the latest list of supported CDMs returned by detectCDMSupport(). +// If none is available, return the best guess +export const getSupportedCDMs = () => { return latestSupportResults; }; -// Asynchronously detect the list of supported CDMs -export const detectCDMSupport = () => { +// Asynchronously detect the list of supported CDMs by requesting key system access +// when possible, otherwise rely on browser-specific EME API feature detection. This +// is curried to allow passing a promise polyfill from the player options when the +// plugin is initialized. The polyfill is necessary to ensure the function behaves +// consistently between IE (which lacks native promise support) and other browsers +export const createDetectSupportedCDMsFunc = (promise = window.Promise) => () => { const results = { fairplay: false, playready: false, @@ -44,9 +49,8 @@ export const detectCDMSupport = () => { results.playready = true; } - // Note: `requestMediaKeySystemAccess` is undefined in Chrome unless over https if (window.MediaKeys && window.navigator.requestMediaKeySystemAccess) { - const defaultConfig = [{ + const validConfig = [{ initDataTypes: [], audioCapabilities: [{ contentType: 'audio/mp4;codecs="mp4a.40.2"' @@ -56,22 +60,23 @@ export const detectCDMSupport = () => { }] }]; - return Promise.all([ - window.navigator.requestMediaKeySystemAccess(keySystems.widevine, defaultConfig).catch(() => {}), - window.navigator.requestMediaKeySystemAccess(keySystems.playready, defaultConfig).catch(() => {}), - window.navigator.requestMediaKeySystemAccess(keySystems.clearkey, defaultConfig).catch(() => {}) + // Currently, Safari doesn't support requestMediaKeySystemAccess() so Fairplay + // is excluded from the checks here + return promise.all([ + window.navigator.requestMediaKeySystemAccess(keySystems.widevine, validConfig).catch(() => {}), + window.navigator.requestMediaKeySystemAccess(keySystems.playready, validConfig).catch(() => {}), + window.navigator.requestMediaKeySystemAccess(keySystems.clearkey, validConfig).catch(() => {}) ]).then(([widevine, playready, clearkey]) => { results.widevine = !!widevine; results.playready = !!playready; results.clearkey = !!clearkey; - - // Update the guesses now that we have more definitive answers latestSupportResults = results; + return results; }); } - // Update the guesses now that we have more definitive answers + latestSupportResults = results; - return Promise.resolve(results); + return promise.resolve(results); }; diff --git a/src/plugin.js b/src/plugin.js index a12c997..ac1cb82 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -9,6 +9,7 @@ import { default as msPrefixed, PLAYREADY_KEY_SYSTEM } from './ms-prefixed'; +import { getSupportedCDMs, createDetectSupportedCDMsFunc } from './cdm.js'; import { arrayBuffersEqual, arrayBufferFrom } from './utils'; export const hasSession = (sessions, initData) => { @@ -294,7 +295,7 @@ const eme = function(options = {}) { * @param {Object} [emeOptions={}] * An object of eme plugin options. * @param {Function} [callback=function(){}] - * @param {Boolean} [suppressErrorIfPossible=false] + * @param {boolean} [suppressErrorIfPossible=false] */ initializeMediaKeys(emeOptions = {}, callback = function() {}, suppressErrorIfPossible = false) { // TODO: this should be refactored and renamed to be less tied @@ -351,6 +352,10 @@ const eme = function(options = {}) { } } }, + // Pass a promise polyfill from the player options for IE support. If none + // exists, native Promises will be used and the function won't be supported in IE + detectSupportedCDMs: createDetectSupportedCDMsFunc(player.options().Promise), + getSupportedCDMs, options }; }; diff --git a/test/cdm.test.js b/test/cdm.test.js new file mode 100644 index 0000000..0c91271 --- /dev/null +++ b/test/cdm.test.js @@ -0,0 +1,74 @@ +import QUnit from 'qunit'; +import videojs from 'video.js'; +import { getSupportedCDMs, createDetectSupportedCDMsFunc } from '../src/cdm.js'; + +QUnit.module('videojs-contrib-eme CDM Module'); + +QUnit.test('detectSupportedCDMs() returns a Promise', function(assert) { + const detectSupportedCDMs = createDetectSupportedCDMsFunc(); + const promise = detectSupportedCDMs(); + + assert.ok(promise.then); +}); + +QUnit.test('getSupportedCDMs() returns an object with correct properties', function(assert) { + const cdmResults = getSupportedCDMs(); + const cdmNames = Object.keys(cdmResults); + + assert.equal(cdmNames.length, 4, 'object contains correct number of properties'); + assert.equal(cdmNames.includes('fairplay'), true, 'object contains fairplay property'); + assert.equal(cdmNames.includes('playready'), true, 'object contains playready property'); + assert.equal(cdmNames.includes('widevine'), true, 'object contains widevine property'); + assert.equal(cdmNames.includes('clearkey'), true, 'object contains clearkey property'); +}); + +// NOTE: This test is not future-proof. It verifies that the CDM detect function +// works as expected given browser's *current* CDM support. If that support changes, +// this test may need updating. +QUnit.test('detectSupportedCDMs() promise resolves correctly on different browsers', function(assert) { + const done = assert.async(); + const detectSupportedCDMs = createDetectSupportedCDMsFunc(); + const promise = detectSupportedCDMs(); + + promise.then((result) => { + if (videojs.browser.IS_FIREFOX) { + assert.deepEqual(result, { + fairplay: false, + playready: false, + widevine: true, + clearkey: true + }, 'widevine and clearkey support reported in Firefox'); + } + + if (videojs.browser.IS_CHROME) { + // Currently, CDM support should be the same in Chrome and Firefox, but + // Widevine doesn't work in headless Chrome, so for now we just check + // that clearkey: true. When the bug is fixed, this block can be combined + // with the above Firefox block since the behavior should be the same + // https://bugs.chromium.org/p/chromium/issues/detail?id=788662 + assert.equal(result.fairplay, false, 'fairplay not supported in Chrome'); + assert.equal(result.playready, false, 'playready not supported in Chrome'); + assert.equal(result.clearkey, true, 'clearkey is supported in Chrome'); + } + + if (videojs.browser.IS_ANY_SAFARI) { + assert.deepEqual(result, { + fairplay: true, + playready: false, + widevine: false, + clearkey: false + }, 'fairplay support reported in Safari'); + } + + if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) { + assert.deepEqual(result, { + fairplay: false, + playready: true, + widevine: false, + clearkey: false + }, 'playready support reported in IE/Edge'); + } + + done(); + }).catch(done); +}); diff --git a/test/plugin.test.js b/test/plugin.test.js index 0fd20bd..815c27b 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -98,6 +98,16 @@ QUnit.test('exposes options', function(assert) { 'exposes publisherId'); }); +QUnit.test('exposes getSupportedCDMs() and detectSupportedCDMs()', function(assert) { + assert.notOk(this.player.eme.getSupportedCDMs, 'getSupportedCDMs is unavailable at start'); + assert.notOk(this.player.eme.detectSupportedCDMs, 'detectSupportedCDMs is unavailable at start'); + + this.player.eme(); + + assert.ok(this.player.eme.getSupportedCDMs, 'getSupportedCDMs is available after initialization'); + assert.ok(this.player.eme.detectSupportedCDMs, 'detectSupportedCDMs is available after initialization'); +}); + // skip test for Safari if (!window.WebKitMediaKeys) { QUnit.test('initializeMediaKeys standard', function(assert) { From 81748c766c642047e8e1ab7401facaba02c96679 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Mon, 5 Jun 2023 19:09:25 -0400 Subject: [PATCH 3/9] update logic --- src/cdm.js | 120 +++++++++++++++++++++++++++-------------------- src/plugin.js | 6 +-- test/cdm.test.js | 25 ++++++---- 3 files changed, 86 insertions(+), 65 deletions(-) diff --git a/src/cdm.js b/src/cdm.js index fab6a22..f29609e 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -1,23 +1,22 @@ import window from 'global/window'; import videojs from 'video.js'; -const keySystems = { - fairplay: 'com.apple.fairplay', - playready: 'com.microsoft.playready', - widevine: 'com.widevine.alpha', - clearkey: 'org.w3.clearkey' -}; +// `IS_CHROMIUM` and `IS_WINDOWS` are newer Video.js features, so add fallback just in case +export const IS_CHROMIUM = videojs.browser.IS_CHROMIUM || (/Chrome|CriOS/i).test(window.navigator.userAgent); +export const IS_WINDOWS = videojs.browser.IS_WINDOWS || (/Windows/i).test(window.navigator.userAgent); // Use a combination of API feature and user agent detection to provide an initial // best guess as to which CDMs are supported. +const hasMediaKeys = Boolean(window.MediaKeys && window.navigator.requestMediaKeySystemAccess); +const isChromeOrFirefox = videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX; +const isChromiumEdge = videojs.browser.IS_EDGE && IS_CHROMIUM; +const isAnyEdge = videojs.browser.IS_EDGE; + const bestGuessSupport = { - fairplay: !!window.WebKitMediaKeys, - playready: !!(window.MSMediaKeys && videojs.browser.IE_VERSION) || - !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess && videojs.browser.IS_EDGE), - widevine: !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess) && - (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX), - clearkey: !!(window.MediaKeys && window.navigator.requestMediaKeySystemAccess) && - (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX) + fairplay: Boolean(window.WebKitMediaKeys) || (hasMediaKeys && videojs.browser.IS_ANY_SAFARI), + playready: hasMediaKeys && (isAnyEdge && (!IS_CHROMIUM || IS_WINDOWS)), + widevine: hasMediaKeys && (isChromeOrFirefox || isChromiumEdge), + clearkey: hasMediaKeys && (isChromeOrFirefox || isChromiumEdge) }; let latestSupportResults = bestGuessSupport; @@ -28,55 +27,72 @@ export const getSupportedCDMs = () => { return latestSupportResults; }; +const genericConfig = [{ + initDataTypes: ['cenc'], + audioCapabilities: [{ + contentType: 'audio/mp4;codecs="mp4a.40.2"' + }], + videoCapabilities: [{ + contentType: 'video/mp4;codecs="avc1.42E01E"' + }] +}]; + +const keySystems = [ + // Fairplay + // Needs a different config than the others + { + keySystem: 'com.apple.fps', + supportedConfig: [{ + initDataTypes: ['sinf'], + videoCapabilities: [{ + contentType: 'video/mp4' + }] + }] + }, + // Playready + { + keySystem: 'com.microsoft.playready.recommendation', + supportedConfig: genericConfig + }, + // Widevine + { + keySystem: 'com.widevine.alpha', + supportedConfig: genericConfig + }, + // Clear + { + keySystem: 'org.w3.clearkey', + supportedConfig: genericConfig + } +]; + // Asynchronously detect the list of supported CDMs by requesting key system access -// when possible, otherwise rely on browser-specific EME API feature detection. This -// is curried to allow passing a promise polyfill from the player options when the -// plugin is initialized. The polyfill is necessary to ensure the function behaves -// consistently between IE (which lacks native promise support) and other browsers -export const createDetectSupportedCDMsFunc = (promise = window.Promise) => () => { +// when possible, otherwise rely on browser-specific EME API feature detection. +export const detectSupportedCDMs = () => { + const Promise = window.Promise; const results = { - fairplay: false, + fairplay: Boolean(window.WebKitMediaKeys), playready: false, widevine: false, clearkey: false }; - if (window.WebKitMediaKeys) { - results.fairplay = true; - } + if (!window.MediaKeys || !window.navigator.requestMediaKeySystemAccess) { + latestSupportResults = results; - if (window.MSMediaKeys && window.MSMediaKeys.isTypeSupported(keySystems.playready)) { - results.playready = true; + return Promise.resolve(results); } - if (window.MediaKeys && window.navigator.requestMediaKeySystemAccess) { - const validConfig = [{ - initDataTypes: [], - audioCapabilities: [{ - contentType: 'audio/mp4;codecs="mp4a.40.2"' - }], - videoCapabilities: [{ - contentType: 'video/mp4;codecs="avc1.42E01E"' - }] - }]; - - // Currently, Safari doesn't support requestMediaKeySystemAccess() so Fairplay - // is excluded from the checks here - return promise.all([ - window.navigator.requestMediaKeySystemAccess(keySystems.widevine, validConfig).catch(() => {}), - window.navigator.requestMediaKeySystemAccess(keySystems.playready, validConfig).catch(() => {}), - window.navigator.requestMediaKeySystemAccess(keySystems.clearkey, validConfig).catch(() => {}) - ]).then(([widevine, playready, clearkey]) => { - results.widevine = !!widevine; - results.playready = !!playready; - results.clearkey = !!clearkey; - latestSupportResults = results; - - return results; - }); - } + return Promise.all(keySystems.map(({keySystem, supportedConfig}) => { + return window.navigator.requestMediaKeySystemAccess(keySystem, supportedConfig).catch(() => {}); + })).then(([fairplay, playready, widevine, clearkey]) => { + results.fairplay = Boolean(fairplay); + results.playready = Boolean(playready); + results.widevine = Boolean(widevine); + results.clearkey = Boolean(clearkey); - latestSupportResults = results; + latestSupportResults = results; - return promise.resolve(results); + return results; + }); }; diff --git a/src/plugin.js b/src/plugin.js index 798317b..032ef91 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -9,7 +9,7 @@ import { default as msPrefixed, PLAYREADY_KEY_SYSTEM } from './ms-prefixed'; -import { getSupportedCDMs, createDetectSupportedCDMsFunc } from './cdm.js'; +import {getSupportedCDMs, detectSupportedCDMs } from './cdm.js'; import { arrayBuffersEqual, arrayBufferFrom, merge } from './utils'; import {version as VERSION} from '../package.json'; @@ -403,9 +403,7 @@ const eme = function(options = {}) { } } }, - // Pass a promise polyfill from the player options for IE support. If none - // exists, native Promises will be used and the function won't be supported in IE - detectSupportedCDMs: createDetectSupportedCDMsFunc(player.options().Promise), + detectSupportedCDMs, getSupportedCDMs, options }; diff --git a/test/cdm.test.js b/test/cdm.test.js index 0c91271..1986fd5 100644 --- a/test/cdm.test.js +++ b/test/cdm.test.js @@ -1,11 +1,10 @@ import QUnit from 'qunit'; import videojs from 'video.js'; -import { getSupportedCDMs, createDetectSupportedCDMsFunc } from '../src/cdm.js'; +import {IS_CHROMIUM, IS_WINDOWS, getSupportedCDMs, detectSupportedCDMs } from '../src/cdm.js'; QUnit.module('videojs-contrib-eme CDM Module'); QUnit.test('detectSupportedCDMs() returns a Promise', function(assert) { - const detectSupportedCDMs = createDetectSupportedCDMsFunc(); const promise = detectSupportedCDMs(); assert.ok(promise.then); @@ -27,7 +26,6 @@ QUnit.test('getSupportedCDMs() returns an object with correct properties', funct // this test may need updating. QUnit.test('detectSupportedCDMs() promise resolves correctly on different browsers', function(assert) { const done = assert.async(); - const detectSupportedCDMs = createDetectSupportedCDMsFunc(); const promise = detectSupportedCDMs(); promise.then((result) => { @@ -54,19 +52,28 @@ QUnit.test('detectSupportedCDMs() promise resolves correctly on different browse if (videojs.browser.IS_ANY_SAFARI) { assert.deepEqual(result, { fairplay: true, + clearkey: true, playready: false, - widevine: false, - clearkey: false + widevine: false }, 'fairplay support reported in Safari'); } - if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) { + if (videojs.browser.IS_EDGE && IS_CHROMIUM && !IS_WINDOWS) { + assert.deepEqual(result, { + fairplay: false, + playready: false, + widevine: true, + clearkey: true + }, 'widevine support reported in non-Windows Chromium Edge'); + } + + if (videojs.browser.IS_EDGE && IS_CHROMIUM && IS_WINDOWS) { assert.deepEqual(result, { fairplay: false, playready: true, - widevine: false, - clearkey: false - }, 'playready support reported in IE/Edge'); + widevine: true, + clearkey: true + }, 'widevine and playready support reported in Windows Chromium Edge'); } done(); From f0f677f39fa54ae964bf64efadd73d2d7da03152 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 6 Jun 2023 13:29:45 -0400 Subject: [PATCH 4/9] fix test --- test/cdm.test.js | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/cdm.test.js b/test/cdm.test.js index 1986fd5..e76751f 100644 --- a/test/cdm.test.js +++ b/test/cdm.test.js @@ -29,24 +29,16 @@ QUnit.test('detectSupportedCDMs() promise resolves correctly on different browse const promise = detectSupportedCDMs(); promise.then((result) => { - if (videojs.browser.IS_FIREFOX) { - assert.deepEqual(result, { - fairplay: false, - playready: false, - widevine: true, - clearkey: true - }, 'widevine and clearkey support reported in Firefox'); - } + // Currently, widevine doesn't work in headless Chrome and requires a plugin in Ubuntu Firefox, so + // we can't verify widevine support in the remote Video.js test environment. However, it can be verified + // if testing locally. Headless Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=788662 + if (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX) { + assert.equal(result.fairplay, false, 'fairplay not supported in Chrome and FF'); + assert.equal(result.playready, false, 'playready not supported in Chrome and FF'); + assert.equal(result.clearkey, true, 'clearkey is supported in Chrome and FF'); - if (videojs.browser.IS_CHROME) { - // Currently, CDM support should be the same in Chrome and Firefox, but - // Widevine doesn't work in headless Chrome, so for now we just check - // that clearkey: true. When the bug is fixed, this block can be combined - // with the above Firefox block since the behavior should be the same - // https://bugs.chromium.org/p/chromium/issues/detail?id=788662 - assert.equal(result.fairplay, false, 'fairplay not supported in Chrome'); - assert.equal(result.playready, false, 'playready not supported in Chrome'); - assert.equal(result.clearkey, true, 'clearkey is supported in Chrome'); + // Uncomment if testing locally + // assert.equal(result.widevine, true, 'widevine is supported in Chrome and FF'); } if (videojs.browser.IS_ANY_SAFARI) { From bf1b1623d1ee6f560141fe4e6300d55dceecdd1b Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 6 Jun 2023 13:50:12 -0400 Subject: [PATCH 5/9] add temporary debug log --- src/cdm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cdm.js b/src/cdm.js index f29609e..a3cdf90 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -78,6 +78,8 @@ export const detectSupportedCDMs = () => { }; if (!window.MediaKeys || !window.navigator.requestMediaKeySystemAccess) { + console.log('****** DEBUG:', 'EME not supported'); // eslint-disable-line + latestSupportResults = results; return Promise.resolve(results); From 7d495c0f5a9e71a8bdd1d52b37a921c8b12dcaa9 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 6 Jun 2023 14:02:51 -0400 Subject: [PATCH 6/9] temporarily change initDataTypes --- src/cdm.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cdm.js b/src/cdm.js index a3cdf90..f6e5ed3 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -28,7 +28,7 @@ export const getSupportedCDMs = () => { }; const genericConfig = [{ - initDataTypes: ['cenc'], + initDataTypes: [], audioCapabilities: [{ contentType: 'audio/mp4;codecs="mp4a.40.2"' }], @@ -78,8 +78,6 @@ export const detectSupportedCDMs = () => { }; if (!window.MediaKeys || !window.navigator.requestMediaKeySystemAccess) { - console.log('****** DEBUG:', 'EME not supported'); // eslint-disable-line - latestSupportResults = results; return Promise.resolve(results); From 5680afb7fb72112c9d18e7afcfbe2b91c1383c2b Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 6 Jun 2023 14:46:16 -0400 Subject: [PATCH 7/9] switch initDataTypes back --- src/cdm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdm.js b/src/cdm.js index f6e5ed3..f29609e 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -28,7 +28,7 @@ export const getSupportedCDMs = () => { }; const genericConfig = [{ - initDataTypes: [], + initDataTypes: ['cenc'], audioCapabilities: [{ contentType: 'audio/mp4;codecs="mp4a.40.2"' }], From 0173a16ba6cf05b04c554266b6db916ae4395c13 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Tue, 6 Jun 2023 15:01:22 -0400 Subject: [PATCH 8/9] fix tests --- test/cdm.test.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/test/cdm.test.js b/test/cdm.test.js index e76751f..7d5ea02 100644 --- a/test/cdm.test.js +++ b/test/cdm.test.js @@ -29,15 +29,25 @@ QUnit.test('detectSupportedCDMs() promise resolves correctly on different browse const promise = detectSupportedCDMs(); promise.then((result) => { - // Currently, widevine doesn't work in headless Chrome and requires a plugin in Ubuntu Firefox, so - // we can't verify widevine support in the remote Video.js test environment. However, it can be verified - // if testing locally. Headless Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=788662 - if (videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX) { - assert.equal(result.fairplay, false, 'fairplay not supported in Chrome and FF'); - assert.equal(result.playready, false, 'playready not supported in Chrome and FF'); - assert.equal(result.clearkey, true, 'clearkey is supported in Chrome and FF'); + // Currently, widevine and clearkey don't work in headless Chrome, so we can't verify cdm support in + // the remote Video.js test environment. However, it can be verified if testing locally in a real browser. + // Headless Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=788662 + if (videojs.browser.IS_CHROME) { + assert.equal(result.fairplay, false, 'fairplay not supported in Chrome'); + assert.equal(result.playready, false, 'playready not supported in Chrome'); - // Uncomment if testing locally + // Uncomment if testing locally in actual browser + // assert.equal(result.clearkey, true, 'clearkey is supported in Chrome'); + // assert.equal(result.widevine, true, 'widevine is supported in Chrome'); + } + + // Widevine requires a plugin in Ubuntu Firefox so it also does not work in the remote Video.js test environment + if (videojs.browser.IS_FIREFOX) { + assert.equal(result.fairplay, false, 'fairplay not supported in FF'); + assert.equal(result.playready, false, 'playready not supported in FF'); + assert.equal(result.clearkey, true, 'clearkey is supported in FF'); + + // Uncomment if testing locally in actual browser // assert.equal(result.widevine, true, 'widevine is supported in Chrome and FF'); } From 7ac3e30c045cf7b9689f325594adffd4e7d32871 Mon Sep 17 00:00:00 2001 From: Alex Barstow Date: Wed, 14 Jun 2023 13:06:33 -0400 Subject: [PATCH 9/9] remove getSupportedCDMs, update tests, add docs --- README.md | 17 +++++++++++++++++ src/cdm.js | 33 +-------------------------------- src/plugin.js | 3 +-- test/cdm.test.js | 18 ++++++------------ test/plugin.test.js | 4 +--- 5 files changed, 26 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 6f567c3..5d38f18 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Maintenance Status: Stable - [Header Hierarchy and Removal](#header-hierarchy-and-removal) - [`emeOptions`](#emeoptions) - [`initializeMediaKeys()`](#initializemediakeys) + - [`detectSupportedCDMs()`](#detectsupportedcdms) - [Events](#events) - [`licenserequestattempted`](#licenserequestattempted) - [`keystatuschange`](#keystatuschange) @@ -558,6 +559,22 @@ player.eme.initializeMediaKeys(emeOptions, emeCallback, suppressErrorsIfPossible When `suppressErrorsIfPossible` is set to `false` (the default) and an error occurs, the error handler will be invoked after the callback finishes and `error()` will be called on the player. When set to `true` and an error occurs, the error handler will not be invoked with the exception of `mskeyerror` errors in IE11 since they cannot be suppressed asynchronously. +### `detectSupportedCDMs()` + +`player.eme.detectSupportedCDMs()` is used to asynchronously detect and return a list of supported Content Decryption Modules (CDMs) in the current browser. It uses the EME API to request access to each key system and determine its availability. This function checks for the support of the following key systems: FairPlay, PlayReady, Widevine, and ClearKey. + +Please use this function sparingly, as side-effects (namely calling `navigator.requestMediaKeySystemAccess()`) can have user-visible effects, such as prompting for system resource permissions, which could be disruptive if invoked at inappropriate times. See [requestMediaKeySystemAccess()](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess) documentation for more information. + +```js +player.eme.detectSupportedCDMs() + .then(supportedCDMs => { + // Sample output: {fairplay: false, playready: false, widevine: true, clearkey: true} + console.log(supportedCDMs); + }); +``` + +_________________________________________________________ + ### Events There are some events that are specific to this plugin. diff --git a/src/cdm.js b/src/cdm.js index f29609e..5abf0dd 100644 --- a/src/cdm.js +++ b/src/cdm.js @@ -1,31 +1,4 @@ import window from 'global/window'; -import videojs from 'video.js'; - -// `IS_CHROMIUM` and `IS_WINDOWS` are newer Video.js features, so add fallback just in case -export const IS_CHROMIUM = videojs.browser.IS_CHROMIUM || (/Chrome|CriOS/i).test(window.navigator.userAgent); -export const IS_WINDOWS = videojs.browser.IS_WINDOWS || (/Windows/i).test(window.navigator.userAgent); - -// Use a combination of API feature and user agent detection to provide an initial -// best guess as to which CDMs are supported. -const hasMediaKeys = Boolean(window.MediaKeys && window.navigator.requestMediaKeySystemAccess); -const isChromeOrFirefox = videojs.browser.IS_CHROME || videojs.browser.IS_FIREFOX; -const isChromiumEdge = videojs.browser.IS_EDGE && IS_CHROMIUM; -const isAnyEdge = videojs.browser.IS_EDGE; - -const bestGuessSupport = { - fairplay: Boolean(window.WebKitMediaKeys) || (hasMediaKeys && videojs.browser.IS_ANY_SAFARI), - playready: hasMediaKeys && (isAnyEdge && (!IS_CHROMIUM || IS_WINDOWS)), - widevine: hasMediaKeys && (isChromeOrFirefox || isChromiumEdge), - clearkey: hasMediaKeys && (isChromeOrFirefox || isChromiumEdge) -}; - -let latestSupportResults = bestGuessSupport; - -// Synchronously return the latest list of supported CDMs returned by detectCDMSupport(). -// If none is available, return the best guess -export const getSupportedCDMs = () => { - return latestSupportResults; -}; const genericConfig = [{ initDataTypes: ['cenc'], @@ -39,7 +12,7 @@ const genericConfig = [{ const keySystems = [ // Fairplay - // Needs a different config than the others + // Requires different config than other CDMs { keySystem: 'com.apple.fps', supportedConfig: [{ @@ -78,8 +51,6 @@ export const detectSupportedCDMs = () => { }; if (!window.MediaKeys || !window.navigator.requestMediaKeySystemAccess) { - latestSupportResults = results; - return Promise.resolve(results); } @@ -91,8 +62,6 @@ export const detectSupportedCDMs = () => { results.widevine = Boolean(widevine); results.clearkey = Boolean(clearkey); - latestSupportResults = results; - return results; }); }; diff --git a/src/plugin.js b/src/plugin.js index 032ef91..d1182fc 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -9,7 +9,7 @@ import { default as msPrefixed, PLAYREADY_KEY_SYSTEM } from './ms-prefixed'; -import {getSupportedCDMs, detectSupportedCDMs } from './cdm.js'; +import {detectSupportedCDMs } from './cdm.js'; import { arrayBuffersEqual, arrayBufferFrom, merge } from './utils'; import {version as VERSION} from '../package.json'; @@ -404,7 +404,6 @@ const eme = function(options = {}) { } }, detectSupportedCDMs, - getSupportedCDMs, options }; }; diff --git a/test/cdm.test.js b/test/cdm.test.js index 7d5ea02..b94afd9 100644 --- a/test/cdm.test.js +++ b/test/cdm.test.js @@ -1,6 +1,11 @@ import QUnit from 'qunit'; +import window from 'global/window'; import videojs from 'video.js'; -import {IS_CHROMIUM, IS_WINDOWS, getSupportedCDMs, detectSupportedCDMs } from '../src/cdm.js'; +import {detectSupportedCDMs } from '../src/cdm.js'; + +// `IS_CHROMIUM` and `IS_WINDOWS` are newer Video.js features, so add fallback just in case +const IS_CHROMIUM = videojs.browser.IS_CHROMIUM || (/Chrome|CriOS/i).test(window.navigator.userAgent); +const IS_WINDOWS = videojs.browser.IS_WINDOWS || (/Windows/i).test(window.navigator.userAgent); QUnit.module('videojs-contrib-eme CDM Module'); @@ -10,17 +15,6 @@ QUnit.test('detectSupportedCDMs() returns a Promise', function(assert) { assert.ok(promise.then); }); -QUnit.test('getSupportedCDMs() returns an object with correct properties', function(assert) { - const cdmResults = getSupportedCDMs(); - const cdmNames = Object.keys(cdmResults); - - assert.equal(cdmNames.length, 4, 'object contains correct number of properties'); - assert.equal(cdmNames.includes('fairplay'), true, 'object contains fairplay property'); - assert.equal(cdmNames.includes('playready'), true, 'object contains playready property'); - assert.equal(cdmNames.includes('widevine'), true, 'object contains widevine property'); - assert.equal(cdmNames.includes('clearkey'), true, 'object contains clearkey property'); -}); - // NOTE: This test is not future-proof. It verifies that the CDM detect function // works as expected given browser's *current* CDM support. If that support changes, // this test may need updating. diff --git a/test/plugin.test.js b/test/plugin.test.js index ccff6e3..838c88c 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -107,13 +107,11 @@ QUnit.test('exposes options', function(assert) { ); }); -QUnit.test('exposes getSupportedCDMs() and detectSupportedCDMs()', function(assert) { - assert.notOk(this.player.eme.getSupportedCDMs, 'getSupportedCDMs is unavailable at start'); +QUnit.test('exposes detectSupportedCDMs()', function(assert) { assert.notOk(this.player.eme.detectSupportedCDMs, 'detectSupportedCDMs is unavailable at start'); this.player.eme(); - assert.ok(this.player.eme.getSupportedCDMs, 'getSupportedCDMs is available after initialization'); assert.ok(this.player.eme.detectSupportedCDMs, 'detectSupportedCDMs is available after initialization'); });