Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for probing encryption scheme support #6506

Merged
merged 4 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion externs/shaka/player.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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