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 encryptionScheme to shaka.extern.DrmInfo #6480

Merged
merged 1 commit into from Apr 26, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions externs/shaka/manifest.js
Expand Up @@ -181,6 +181,7 @@ shaka.extern.ServiceDescription;
/**
* @typedef {{
* keySystem: string,
* encryptionScheme: string,
* licenseServerUri: string,
* distinctiveIdentifierRequired: boolean,
* persistentStateRequired: boolean,
Expand All @@ -199,6 +200,9 @@ shaka.extern.ServiceDescription;
* @property {string} keySystem
* <i>Required.</i> <br>
* The key system, e.g., "com.widevine.alpha".
* @property {string} encryptionScheme
* <i>Required.</i> <br>
* The encryption scheme, e.g., "cenc", "cbcs", "cbcs-1-9".
* @property {string} licenseServerUri
* <i>Filled in by DRM config if missing.</i> <br>
* The license server URI.
Expand Down
40 changes: 30 additions & 10 deletions lib/dash/content_protection.js
Expand Up @@ -47,6 +47,8 @@ shaka.dash.ContentProtection = class {
// Remove any possible null value (elements may have no key ids).
keyIds.delete(null);

let encryptionScheme = 'cenc';

if (keyIds.size > 1) {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
Expand All @@ -70,6 +72,14 @@ shaka.dash.ContentProtection = class {
aes128Info = ContentProtection.parseAes128_(aes128Elements[0]);
}

const mp4ProtectionParsed = parsed.find((elem) => {
return elem.schemeUri == ContentProtection.MP4Protection_;
});

if (mp4ProtectionParsed && mp4ProtectionParsed.encryptionScheme) {
encryptionScheme = mp4ProtectionParsed.encryptionScheme;
}

// Find the default key ID and init data. Create a new array of all the
// non-CENC elements.
parsedNonCenc = parsed.filter((elem) => {
Expand All @@ -84,13 +94,14 @@ shaka.dash.ContentProtection = class {
});

if (parsedNonCenc.length) {
drmInfos = ContentProtection.convertElements_(
defaultInit, parsedNonCenc, keySystemsByURI, keyIds);
drmInfos = ContentProtection.convertElements_(defaultInit,
encryptionScheme, parsedNonCenc, keySystemsByURI, keyIds);

// If there are no drmInfos after parsing, then add a dummy entry.
// This may be removed in parseKeyIds.
if (drmInfos.length == 0) {
drmInfos = [ManifestParserUtils.createDrmInfo('', defaultInit)];
drmInfos = [ManifestParserUtils.createDrmInfo(
'', encryptionScheme, defaultInit)];
}
}
}
Expand All @@ -106,8 +117,8 @@ shaka.dash.ContentProtection = class {
// put clearkey in this list. Otherwise, it may be triggered when
// a real key system should be used instead.
if (keySystem != 'org.w3.clearkey') {
const info =
ManifestParserUtils.createDrmInfo(keySystem, defaultInit);
const info = ManifestParserUtils.createDrmInfo(
keySystem, encryptionScheme, defaultInit);
drmInfos.push(info);
}
}
Expand Down Expand Up @@ -467,13 +478,15 @@ shaka.dash.ContentProtection = class {
* Creates DrmInfo objects from the given element.
*
* @param {Array.<shaka.extern.InitDataOverride>} defaultInit
* @param {string} encryptionScheme
* @param {!Array.<shaka.dash.ContentProtection.Element>} elements
* @param {!Object.<string, string>} keySystemsByURI
* @param {!Set.<string>} keyIds
* @return {!Array.<shaka.extern.DrmInfo>}
* @private
*/
static convertElements_(defaultInit, elements, keySystemsByURI, keyIds) {
static convertElements_(defaultInit, encryptionScheme, elements,
keySystemsByURI, keyIds) {
const ContentProtection = shaka.dash.ContentProtection;
const ManifestParserUtils = shaka.util.ManifestParserUtils;
const licenseUrlParsers = ContentProtection.licenseUrlParsers_;
Expand All @@ -497,7 +510,8 @@ shaka.dash.ContentProtection = class {
}
const initData = element.init || defaultInit || proInitData ||
clearKeyInitData;
const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
const info = ManifestParserUtils.createDrmInfo(
keySystem, encryptionScheme, initData);
const licenseParser = licenseUrlParsers.get(keySystem);
if (licenseParser) {
info.licenseServerUri = licenseParser(element);
Expand Down Expand Up @@ -551,6 +565,8 @@ shaka.dash.ContentProtection = class {
const psshs = TXml.findChildrenNS(elem, NS, 'pssh')
.map(TXml.getContents);

const encryptionScheme = elem.attributes['value'];

if (!schemeUri) {
shaka.log.error('Missing required schemeIdUri attribute on',
'ContentProtection element', elem);
Expand Down Expand Up @@ -588,9 +604,10 @@ shaka.dash.ContentProtection = class {

return {
node: elem,
schemeUri: schemeUri,
keyId: keyId,
schemeUri,
keyId,
init: (init.length > 0 ? init : null),
encryptionScheme,
};
}

Expand Down Expand Up @@ -748,7 +765,8 @@ shaka.dash.ContentProtection.Aes128Info;
* node: !shaka.extern.xml.Node,
* schemeUri: string,
* keyId: ?string,
* init: Array.<shaka.extern.InitDataOverride>
* init: Array.<shaka.extern.InitDataOverride>,
* encryptionScheme: ?string
* }}
*
* @description
Expand All @@ -763,6 +781,8 @@ shaka.dash.ContentProtection.Aes128Info;
* @property {Array.<shaka.extern.InitDataOverride>} init
* The init data, if present. If there is no init data, it will be null. If
* this is non-null, there is at least one element.
* @property {?string} encryptionScheme
* The encryption scheme, if present.
*/
shaka.dash.ContentProtection.Element;

Expand Down
24 changes: 20 additions & 4 deletions lib/hls/hls_parser.js
Expand Up @@ -4173,7 +4173,7 @@ shaka.hls.HlsParser = class {
* with the correct keySystem and initDataType
*/
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.apple.fps', [
'com.apple.fps', /* encryptionScheme= */ 'cbcs-1-9', [
{initDataType: 'sinf', initData: new Uint8Array(0), keyId: null},
]);

Expand All @@ -4194,13 +4194,18 @@ shaka.hls.HlsParser = class {
return null;
}

let encryptionScheme = 'cenc';
if (method == 'SAMPLE-AES') {
encryptionScheme = 'cbcs';
}

const uri = drmTag.getRequiredAttrValue('URI');
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);

// The data encoded in the URI is a PSSH box to be used as init data.
const pssh = shaka.util.BufferUtils.toUint8(parsedData.data);
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.widevine.alpha', [
'com.widevine.alpha', encryptionScheme, [
{initDataType: 'cenc', initData: pssh},
]);

Expand Down Expand Up @@ -4232,6 +4237,11 @@ shaka.hls.HlsParser = class {
return null;
}

let encryptionScheme = 'cenc';
if (method == 'SAMPLE-AES') {
encryptionScheme = 'cbcs';
}

const uri = drmTag.getRequiredAttrValue('URI');
const parsedData = shaka.net.DataUriPlugin.parseRaw(uri);

Expand All @@ -4247,7 +4257,7 @@ shaka.hls.HlsParser = class {
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
'com.microsoft.playready', [
'com.microsoft.playready', encryptionScheme, [
{initDataType: 'cenc', initData: pssh},
]);

Expand Down Expand Up @@ -4350,7 +4360,13 @@ shaka.hls.HlsParser = class {
const clearkeys = new Map();
clearkeys.set(keyId, key);

return shaka.util.ManifestParserUtils.createDrmInfoFromClearKeys(clearkeys);
let encryptionScheme = 'cenc';
if (method == 'SAMPLE-AES') {
encryptionScheme = 'cbcs';
}

return shaka.util.ManifestParserUtils.createDrmInfoFromClearKeys(
clearkeys, encryptionScheme);
}
};

Expand Down
30 changes: 27 additions & 3 deletions lib/media/drm_engine.js
Expand Up @@ -2168,6 +2168,9 @@ shaka.media.DrmEngine = class {
* @private
*/
createDrmInfoByInfos_(keySystem, drmInfos) {
/** @type {!Array.<string>} */
const encryptionSchemes = [];

/** @type {!Array.<string>} */
const licenseServers = [];

Expand All @@ -2184,9 +2187,14 @@ shaka.media.DrmEngine = class {
const keyIds = new Set();

shaka.media.DrmEngine.processDrmInfos_(
drmInfos, licenseServers, serverCerts,
drmInfos, encryptionSchemes, licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds);

if (encryptionSchemes.length > 1) {
shaka.log.warning('Multiple unique encryption schemes found! ' +
'Only the first will be used.');
}

if (serverCerts.length > 1) {
shaka.log.warning('Multiple unique server certificates found! ' +
'Only the first will be used.');
Expand All @@ -2208,6 +2216,7 @@ shaka.media.DrmEngine = class {
/** @type {shaka.extern.DrmInfo} */
const res = {
keySystem,
encryptionScheme: encryptionSchemes[0],
licenseServerUri: licenseServers[0],
distinctiveIdentifierRequired: drmInfos[0].distinctiveIdentifierRequired,
persistentStateRequired: drmInfos[0].persistentStateRequired,
Expand Down Expand Up @@ -2244,6 +2253,9 @@ shaka.media.DrmEngine = class {
* @private
*/
static createDrmInfoByConfigs_(keySystem, config) {
/** @type {!Array.<string>} */
const encryptionSchemes = [];

/** @type {!Array.<string>} */
const licenseServers = [];

Expand All @@ -2261,9 +2273,14 @@ shaka.media.DrmEngine = class {

// TODO: refactor, don't stick drmInfos onto MediaKeySystemConfiguration
shaka.media.DrmEngine.processDrmInfos_(
config['drmInfos'], licenseServers, serverCerts,
config['drmInfos'], encryptionSchemes, licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds);

if (encryptionSchemes.length > 1) {
shaka.log.warning('Multiple unique encryption schemes found! ' +
'Only the first will be used.');
}

if (serverCerts.length > 1) {
shaka.log.warning('Multiple unique server certificates found! ' +
'Only the first will be used.');
Expand All @@ -2288,6 +2305,7 @@ shaka.media.DrmEngine = class {
const distinctiveIdentifier = config.distinctiveIdentifier;
return {
keySystem,
encryptionScheme: encryptionSchemes[0],
licenseServerUri: licenseServers[0],
distinctiveIdentifierRequired: (distinctiveIdentifier == 'required'),
persistentStateRequired: (config.persistentState == 'required'),
Expand All @@ -2307,14 +2325,15 @@ shaka.media.DrmEngine = class {
*
* @param {!Array.<shaka.extern.DrmInfo>} drmInfos
* @param {!Array.<string>} licenseServers
* @param {!Array.<string>} encryptionSchemes
* @param {!Array.<!Uint8Array>} serverCerts
* @param {!Array.<string>} serverCertificateUris
* @param {!Array.<!shaka.extern.InitDataOverride>} initDatas
* @param {!Set.<string>} keyIds
* @private
*/
static processDrmInfos_(
drmInfos, licenseServers, serverCerts,
drmInfos, encryptionSchemes, licenseServers, serverCerts,
serverCertificateUris, initDatas, keyIds) {
/** @type {function(shaka.extern.InitDataOverride,
* shaka.extern.InitDataOverride):boolean} */
Expand All @@ -2332,6 +2351,11 @@ shaka.media.DrmEngine = class {
const clearKeyLicenseServers = [];

for (const drmInfo of drmInfos) {
// Build an array of unique encryption schemes.
if (!encryptionSchemes.includes(drmInfo.encryptionScheme)) {
encryptionSchemes.push(drmInfo.encryptionScheme);
}

// Build an array of unique license servers.
if (drmInfo.keySystem == 'org.w3.clearkey' &&
drmInfo.licenseServerUri.startsWith(clearkeyDataStart)) {
Expand Down
3 changes: 2 additions & 1 deletion lib/mss/content_protection.js
Expand Up @@ -296,7 +296,8 @@ shaka.mss.ContentProtection = class {
const initData = ContentProtection.getInitDataFromPro_(
element, systemID, KID);

const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
const info = ManifestParserUtils.createDrmInfo(
keySystem, /* encryptionScheme= */ 'cenc', initData);
if (KID) {
info.keyIds.add(KID);
}
Expand Down
10 changes: 7 additions & 3 deletions lib/util/manifest_parser_utils.js
Expand Up @@ -59,12 +59,14 @@ shaka.util.ManifestParserUtils = class {
* Creates a DrmInfo object from the given info.
*
* @param {string} keySystem
* @param {string} encryptionScheme
* @param {Array.<shaka.extern.InitDataOverride>} initData
* @return {shaka.extern.DrmInfo}
*/
static createDrmInfo(keySystem, initData) {
static createDrmInfo(keySystem, encryptionScheme, initData) {
return {
keySystem: keySystem,
keySystem,
encryptionScheme,
licenseServerUri: '',
distinctiveIdentifierRequired: false,
persistentStateRequired: false,
Expand All @@ -82,9 +84,10 @@ shaka.util.ManifestParserUtils = class {
* Creates a DrmInfo object from ClearKeys.
*
* @param {!Map.<string, string>} clearKeys
* @param {string=} encryptionScheme
* @return {shaka.extern.DrmInfo}
*/
static createDrmInfoFromClearKeys(clearKeys) {
static createDrmInfoFromClearKeys(clearKeys, encryptionScheme = 'cenc') {
const StringUtils = shaka.util.StringUtils;
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
const keys = [];
Expand Down Expand Up @@ -126,6 +129,7 @@ shaka.util.ManifestParserUtils = class {

return {
keySystem: 'org.w3.clearkey',
encryptionScheme,
licenseServerUri: 'data:application/json;base64,' + window.btoa(license),
distinctiveIdentifierRequired: false,
persistentStateRequired: false,
Expand Down