Skip to content

Commit

Permalink
feat: Add support for probing encryption scheme support (#6506)
Browse files Browse the repository at this point in the history
This will add encryption schemes to the DRM support report generated by
probeSupport() and support.html.

Related to shaka-project/eme-encryption-scheme-polyfill#62, PR #6484,
and issue #1419.

---------

Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
  • Loading branch information
joeyparrish and avelad committed May 6, 2024
1 parent bfa9b3c commit 2dea350
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 46 deletions.
7 changes: 6 additions & 1 deletion externs/shaka/player.js
Expand Up @@ -436,11 +436,16 @@ shaka.extern.Restrictions;

/**
* @typedef {{
* persistentState: boolean
* persistentState: boolean,
* encryptionSchemes: !Array<string>
* }}
*
* @property {boolean} persistentState
* Whether this key system supports persistent state.
* @property {!Array<string|null>} encryptionSchemes
* An array of encryption schemes that are reported to work, through either
* EME or MCap APIs. An empty array indicates that encryptionScheme queries
* are not supported. This should not happen if our polyfills are installed.
* @exportDoc
*/
shaka.extern.DrmSupportType;
Expand Down
159 changes: 118 additions & 41 deletions lib/media/drm_engine.js
Expand Up @@ -21,6 +21,7 @@ goog.require('shaka.util.Lazy');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MapUtils');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.ObjectUtils');
goog.require('shaka.util.Platform');
goog.require('shaka.util.Pssh');
goog.require('shaka.util.PublicPromise');
Expand Down Expand Up @@ -1810,65 +1811,141 @@ shaka.media.DrmEngine = class {
{contentType: 'video/webm; codecs="vp8"'},
];

const basicConfig = {
initDataTypes: ['cenc'],
const basicConfigTemplate = {
videoCapabilities: basicVideoCapabilities,
};
const offlineConfig = {
videoCapabilities: basicVideoCapabilities,
persistentState: 'required',
sessionTypes: ['persistent-license'],
initDataTypes: ['cenc', 'sinf', 'skd', 'keyids'],
};

// Try the offline config first, then fall back to the basic config.
const configs = [offlineConfig, basicConfig];
const testEncryptionSchemes = [
null,
'cenc',
'cbcs',
'cbcs-1-9',
];

/** @type {!Map.<string, ?shaka.extern.DrmSupportType>} */
const support = new Map();

const testSystem = async (keySystem) => {
/**
* @param {string} keySystem
* @param {MediaKeySystemAccess} access
* @return {!Promise}
*/
const processMediaKeySystemAccess = async (keySystem, access) => {
try {
await access.createMediaKeys();
} catch (error) {
// In some cases, we can get a successful access object but fail to
// create a MediaKeys instance. When this happens, don't update the
// support structure. If a previous test succeeded, we won't overwrite
// any of the results.
return;
}

// If sessionTypes is missing, assume no support for persistent-license.
const sessionTypes = access.getConfiguration().sessionTypes;
let persistentState = sessionTypes ?
sessionTypes.includes('persistent-license') : false;

// Tizen 3.0 doesn't support persistent licenses, but reports that it
// does. It doesn't fail until you call update() with a license
// response, which is way too late.
// This is a work-around for #894.
if (shaka.util.Platform.isTizen3()) {
persistentState = false;
}

const videoCapabilities = access.getConfiguration().videoCapabilities;

let supportValue = {persistentState, encryptionSchemes: []};
if (support.has(keySystem) && support.get(keySystem)) {
// Update the existing non-null value.
supportValue = support.get(keySystem);
} else {
// Set a new one.
support.set(keySystem, supportValue);
}

// If the returned config doesn't mention encryptionScheme, the field
// is not supported. If installed, our polyfills should make sure this
// doesn't happen.
const returnedScheme = videoCapabilities[0].encryptionScheme;
if (returnedScheme &&
!supportValue.encryptionSchemes.includes(returnedScheme)) {
supportValue.encryptionSchemes.push(returnedScheme);
}
};

const testSystemEme = async (keySystem, encryptionScheme) => {
try {
const basicConfig =
shaka.util.ObjectUtils.cloneObject(basicConfigTemplate);
for (const cap of basicConfig.videoCapabilities) {
cap.encryptionScheme = encryptionScheme;
}

const offlineConfig = shaka.util.ObjectUtils.cloneObject(basicConfig);
offlineConfig.persistentState = 'required';
offlineConfig.sessionTypes = ['persistent-license'];

const configs = [offlineConfig, basicConfig];

const access = await navigator.requestMediaKeySystemAccess(
keySystem, configs);
await processMediaKeySystemAccess(keySystem, access);
} catch (error) {} // Ignore errors.
};

const testSystemMcap = async (keySystem, encryptionScheme) => {
try {
const decodingConfig = {
type: 'media-source',
video: {
contentType: 'video/mp4; codecs="avc1.42E01E"',
width: 640,
height: 480,
bitrate: 1,
framerate: 1,
},
keySystemConfiguration: {
keySystem,
video: {
encryptionScheme,
},
},
};

const decodingInfo =
await navigator.mediaCapabilities.decodingInfo(decodingConfig);

const access = decodingInfo.keySystemAccess;
await processMediaKeySystemAccess(keySystem, access);
} catch (error) {} // Ignore errors.
};

// Initialize the support structure for each key system.
for (const keySystem of testKeySystems) {
support.set(keySystem, null);
}

// Test each key system and encryption scheme.
const tests = [];
for (const encryptionScheme of testEncryptionSchemes) {
for (const keySystem of testKeySystems) {
// Our Polyfill will reject anything apart com.apple.fps key systems.
// It seems the Safari modern EME API will allow to request a
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
// session but playback will never start
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
if (keySystem === 'org.w3.clearkey' &&
shaka.util.Platform.isSafari()) {
throw new Error('Unsupported keySystem');
}

const access = await navigator.requestMediaKeySystemAccess(
keySystem, configs);

// Edge doesn't return supported session types, but current versions
// do not support persistent-license. If sessionTypes is missing,
// assume no support for persistent-license.
// TODO: Polyfill Edge to return known supported session types.
// Edge bug: https://bit.ly/2IeKzho
const sessionTypes = access.getConfiguration().sessionTypes;
let persistentState = sessionTypes ?
sessionTypes.includes('persistent-license') : false;

// Tizen 3.0 doesn't support persistent licenses, but reports that it
// does. It doesn't fail until you call update() with a license
// response, which is way too late.
// This is a work-around for #894.
if (shaka.util.Platform.isTizen3()) {
persistentState = false;
continue;
}

support.set(keySystem, {persistentState: persistentState});
await access.createMediaKeys();
} catch (e) {
// Either the request failed or createMediaKeys failed.
// Either way, write null to the support object.
support.set(keySystem, null);
tests.push(testSystemEme(keySystem, encryptionScheme));
tests.push(testSystemMcap(keySystem, encryptionScheme));
}
};
}

// Test each key system.
const tests = testKeySystems.map((keySystem) => testSystem(keySystem));
await Promise.all(tests);
return shaka.util.MapUtils.asObject(support);
}
Expand Down
7 changes: 3 additions & 4 deletions test/hls/hls_parser_integration.js
Expand Up @@ -26,12 +26,11 @@ describe('HlsParser', () => {
let waiter;

function checkClearKeySupport() {
// Some versions of Tizen doesn't support CBCS, so omit it for now.
// See: https://github.com/shaka-project/shaka-player/issues/1419
if (shaka.util.Platform.isTizen()) {
const clearKeySupport = support['org.w3.clearkey'];
if (!clearKeySupport) {
return false;
}
return support['org.w3.clearkey'];
return clearKeySupport.encryptionSchemes.includes('cbcs');
}

beforeAll(async () => {
Expand Down

0 comments on commit 2dea350

Please sign in to comment.