Skip to content

Commit

Permalink
feat: Add support for probing encryption scheme support
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 shaka-project#6484,
and issue shaka-project#1419.
  • Loading branch information
joeyparrish committed Apr 29, 2024
1 parent 0873d1e commit 76e3edc
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 36 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
148 changes: 113 additions & 35 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,23 +1811,72 @@ 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'],
};

// 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 {
// Our Polyfill will reject anything apart com.apple.fps key systems.
// It seems the Safari modern EME API will allow to request a
Expand All @@ -1838,37 +1888,65 @@ shaka.media.DrmEngine = class {
throw new Error('Unsupported keySystem');
}

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.
};

// 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;
}
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.
};

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);
// 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) {
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

0 comments on commit 76e3edc

Please sign in to comment.