Skip to content

Commit

Permalink
feat: Add encryptionScheme to shaka.extern.DrmInfo (#6480)
Browse files Browse the repository at this point in the history
Related to #1419
  • Loading branch information
avelad committed Apr 26, 2024
1 parent 95c6a7d commit c6c39df
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 22 deletions.
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

0 comments on commit c6c39df

Please sign in to comment.