Skip to content

Commit

Permalink
feat: Add CDM detection module (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-barstow committed Jun 14, 2023
1 parent 579df4c commit 33dfe13
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
67 changes: 67 additions & 0 deletions src/cdm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import window from 'global/window';

const genericConfig = [{
initDataTypes: ['cenc'],
audioCapabilities: [{
contentType: 'audio/mp4;codecs="mp4a.40.2"'
}],
videoCapabilities: [{
contentType: 'video/mp4;codecs="avc1.42E01E"'
}]
}];

const keySystems = [
// Fairplay
// Requires different config than other CDMs
{
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.
export const detectSupportedCDMs = () => {
const Promise = window.Promise;
const results = {
fairplay: Boolean(window.WebKitMediaKeys),
playready: false,
widevine: false,
clearkey: false
};

if (!window.MediaKeys || !window.navigator.requestMediaKeySystemAccess) {
return Promise.resolve(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);

return results;
});
};
2 changes: 2 additions & 0 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
default as msPrefixed,
PLAYREADY_KEY_SYSTEM
} from './ms-prefixed';
import {detectSupportedCDMs } from './cdm.js';
import { arrayBuffersEqual, arrayBufferFrom, merge } from './utils';
import {version as VERSION} from '../package.json';

Expand Down Expand Up @@ -402,6 +403,7 @@ const eme = function(options = {}) {
}
}
},
detectSupportedCDMs,
options
};
};
Expand Down
77 changes: 77 additions & 0 deletions test/cdm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import QUnit from 'qunit';
import window from 'global/window';
import videojs from 'video.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');

QUnit.test('detectSupportedCDMs() returns a Promise', function(assert) {
const promise = detectSupportedCDMs();

assert.ok(promise.then);
});

// 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 promise = detectSupportedCDMs();

promise.then((result) => {
// 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 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');
}

if (videojs.browser.IS_ANY_SAFARI) {
assert.deepEqual(result, {
fairplay: true,
clearkey: true,
playready: false,
widevine: false
}, 'fairplay support reported in Safari');
}

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: true,
clearkey: true
}, 'widevine and playready support reported in Windows Chromium Edge');
}

done();
}).catch(done);
});
8 changes: 8 additions & 0 deletions test/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ QUnit.test('exposes options', function(assert) {
);
});

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.detectSupportedCDMs, 'detectSupportedCDMs is available after initialization');
});

// skip test for prefix-only Safari
if (!window.MediaKeys) {
QUnit.test('initializeMediaKeys standard', function(assert) {
Expand Down

0 comments on commit 33dfe13

Please sign in to comment.