From 502c8ea468217f2e50c0b73351a95ea88f500f52 Mon Sep 17 00:00:00 2001 From: Garrett Singer Date: Wed, 12 Feb 2020 15:58:09 -0500 Subject: [PATCH] feat: support setting robustness and supportedConfigurations (#100) * feat: support setting robustness level * Add support for supportedConfigurations in requestMediaKeySystemAccess * Add note to README about supportedConfigurations overriding individual options Co-authored-by: Declan Rek --- README.md | 31 ++++++++++++++ src/eme.js | 67 ++++++++++++++++++++++--------- test/eme.test.js | 102 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 181 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9f8f53e..17d470a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Maintenance Status: Stable - [Other DRM Systems](#other-drm-systems) - [Get License By URL](#get-license-by-url) - [Get License By Function](#get-license-by-function) + - [MediaKeySystemConfiguration and supportedConfigurations](#mediakeysystemconfiguration-and-supportedconfigurations) - [Get License Errors](#get-license-errors) - [API](#api) - [Available Options](#available-options) @@ -279,6 +280,34 @@ Below is an example of using one of these DRM systems and custom `getLicense()` } ``` +### MediaKeySystemConfiguration and supportedConfigurations + +In addition to `audioContentType` and `videoContentType` posted above, it is possible to directly provide the `supportedConfigurations` array to use for the `requestMediaKeySystemAccess` call. This allows for the entire range of options specified by the [MediaKeySystemConfiguration] object. + +Note that if `supportedConfigurations` is provided, it will override `audioContentType`, `videoContentType`, `audioRobustness`, and `videoRobustness`. + +Example: + +```js +{ + keySystems: { + 'org.w3.clearkey': { + supportedConfigurations: [{ + videoCapabilities: [{ + contentType: 'video/webm; codecs="vp9"', + robustness: 'SW_SECURE_CRYPTO' + }], + audioCapabilities: [{ + contentType: 'audio/webm; codecs="vorbis"', + robustness: 'SW_SECURE_CRYPTO' + }] + }], + 'org.w3.clearkey': '' + } + } +} +``` + ### Get License Errors The default `getLicense()` functions pass an error to the callback if the license request returns a 4xx or 5xx response code. Depending on how the license server is configured, it is possible in some cases that a valid license could still be returned even if the response code is in that range. If you wish not to pass an error for 4xx and 5xx response codes, you may pass your own `getLicense()` function with the `keySystems` as described above. @@ -526,3 +555,5 @@ This event is triggered directly from the underlying `keystatuseschange` event, ## License Apache License, Version 2.0. [View the license file](LICENSE) + +[MediaKeySystemConfiguration]: https://www.w3.org/TR/encrypted-media/#dom-mediakeysystemconfiguration diff --git a/src/eme.js b/src/eme.js index df8765b..92614e3 100644 --- a/src/eme.js +++ b/src/eme.js @@ -3,6 +3,50 @@ import { requestPlayreadyLicense } from './playready'; import window from 'global/window'; import {mergeAndRemoveNull} from './utils'; +/** + * Returns an array of MediaKeySystemConfigurationObjects provided in the keySystem + * options. + * + * @see {@link https://www.w3.org/TR/encrypted-media/#dom-mediakeysystemconfiguration|MediaKeySystemConfigurationObject} + * + * @param {Object} keySystemOptions + * Options passed into videojs-contrib-eme for a specific keySystem + * @return {Object[]} + * Array of MediaKeySystemConfigurationObjects + */ +export const getSupportedConfigurations = (keySystemOptions) => { + if (keySystemOptions.supportedConfigurations) { + return keySystemOptions.supportedConfigurations; + } + + // TODO use initDataTypes when appropriate + const supportedConfiguration = {}; + const audioContentType = keySystemOptions.audioContentType; + const audioRobustness = keySystemOptions.audioRobustness; + const videoContentType = keySystemOptions.videoContentType; + const videoRobustness = keySystemOptions.videoRobustness; + + if (audioContentType || audioRobustness) { + supportedConfiguration.audioCapabilities = [ + Object.assign({}, + (audioContentType ? { contentType: audioContentType } : {}), + (audioRobustness ? { robustness: audioRobustness } : {}) + ) + ]; + } + + if (videoContentType || videoRobustness) { + supportedConfiguration.videoCapabilities = [ + Object.assign({}, + (videoContentType ? { contentType: videoContentType } : {}), + (videoRobustness ? { robustness: videoRobustness } : {}) + ) + ]; + } + + return [supportedConfiguration]; +}; + export const getSupportedKeySystem = (keySystems) => { // As this happens after the src is set on the video, we rely only on the set src (we // do not change src based on capabilities of the browser in this plugin). @@ -10,27 +54,14 @@ export const getSupportedKeySystem = (keySystems) => { let promise; Object.keys(keySystems).forEach((keySystem) => { - // TODO use initDataTypes when appropriate - const systemOptions = {}; - const audioContentType = keySystems[keySystem].audioContentType; - const videoContentType = keySystems[keySystem].videoContentType; - - if (audioContentType) { - systemOptions.audioCapabilities = [{ - contentType: audioContentType - }]; - } - if (videoContentType) { - systemOptions.videoCapabilities = [{ - contentType: videoContentType - }]; - } + const supportedConfigurations = getSupportedConfigurations(keySystems[keySystem]); if (!promise) { - promise = window.navigator.requestMediaKeySystemAccess(keySystem, [systemOptions]); + promise = + window.navigator.requestMediaKeySystemAccess(keySystem, supportedConfigurations); } else { - promise = promise.catch( - (e) => window.navigator.requestMediaKeySystemAccess(keySystem, [systemOptions])); + promise = promise.catch((e) => + window.navigator.requestMediaKeySystemAccess(keySystem, supportedConfigurations)); } }); diff --git a/test/eme.test.js b/test/eme.test.js index ecc80ee..ab31d78 100644 --- a/test/eme.test.js +++ b/test/eme.test.js @@ -6,7 +6,8 @@ import { makeNewRequest, getSupportedKeySystem, addSession, - addPendingSessions + addPendingSessions, + getSupportedConfigurations } from '../src/eme'; import sinon from 'sinon'; @@ -1043,3 +1044,102 @@ QUnit.test('addPendingSessions reuses saved options', function(assert) { done(); }); }); + +QUnit.module('videojs-contrib-eme getSupportedConfigurations'); + +QUnit.test('includes audio and video content types', function(assert) { + assert.deepEqual( + getSupportedConfigurations({ + audioContentType: 'audio/mp4; codecs="mp4a.40.2"', + videoContentType: 'video/mp4; codecs="avc1.42E01E"' + }), + [{ + audioCapabilities: [{ + contentType: 'audio/mp4; codecs="mp4a.40.2"' + }], + videoCapabilities: [{ + contentType: 'video/mp4; codecs="avc1.42E01E"' + }] + }], + 'included audio and video content types' + ); +}); + +QUnit.test('includes audio and video robustness', function(assert) { + assert.deepEqual( + getSupportedConfigurations({ + audioRobustness: 'SW_SECURE_CRYPTO', + videoRobustness: 'SW_SECURE_CRYPTO' + }), + [{ + audioCapabilities: [{ + robustness: 'SW_SECURE_CRYPTO' + }], + videoCapabilities: [{ + robustness: 'SW_SECURE_CRYPTO' + }] + }], + 'included audio and video robustness' + ); +}); + +QUnit.test('includes audio and video content types and robustness', function(assert) { + assert.deepEqual( + getSupportedConfigurations({ + audioContentType: 'audio/mp4; codecs="mp4a.40.2"', + audioRobustness: 'SW_SECURE_CRYPTO', + videoContentType: 'video/mp4; codecs="avc1.42E01E"', + videoRobustness: 'SW_SECURE_CRYPTO' + }), + [{ + audioCapabilities: [{ + contentType: 'audio/mp4; codecs="mp4a.40.2"', + robustness: 'SW_SECURE_CRYPTO' + }], + videoCapabilities: [{ + contentType: 'video/mp4; codecs="avc1.42E01E"', + robustness: 'SW_SECURE_CRYPTO' + }] + }], + 'included audio and video content types and robustness' + ); +}); + +QUnit.test('uses supportedConfigurations directly if provided', function(assert) { + assert.deepEqual( + getSupportedConfigurations({ + supportedConfigurations: [{ + initDataTypes: ['cenc'], + audioCapabilities: [{ + contentType: 'audio/mp4; codecs="mp4a.40.2"', + robustness: 'SW_SECURE_CRYPTO', + extraOption: 'audio-extra' + }], + videoCapabilities: [{ + contentType: 'video/mp4; codecs="avc1.42E01E"', + robustness: 'SW_SECURE_CRYPTO', + extraOption: 'video-extra' + }] + }], + // should not be used + audioContentType: 'audio/mp4; codecs="mp4a.40.5"', + audioRobustness: 'HW_SECURE_CRYPTO', + videoContentType: 'video/mp4; codecs="avc1.42001e"', + videoRobustness: 'HW_SECURE_CRYPTO' + }), + [{ + initDataTypes: ['cenc'], + audioCapabilities: [{ + contentType: 'audio/mp4; codecs="mp4a.40.2"', + robustness: 'SW_SECURE_CRYPTO', + extraOption: 'audio-extra' + }], + videoCapabilities: [{ + contentType: 'video/mp4; codecs="avc1.42E01E"', + robustness: 'SW_SECURE_CRYPTO', + extraOption: 'video-extra' + }] + }], + 'used supportedConfigurations directly' + ); +});