Skip to content

Commit

Permalink
feat(DASH): Extract PlayReady licenseServerUri from PSSH (#7898)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Jan 17, 2025
1 parent 82f7eaf commit 8fda5d8
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 10 deletions.
30 changes: 24 additions & 6 deletions lib/dash/content_protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,25 +293,43 @@ shaka.dash.ContentProtection = class {
* @return {string}
*/
static getPlayReadyLicenseUrl(element) {
const dashIfLaurlNode = shaka.util.TXml.findChildNS(
const TXml = shaka.util.TXml;
const dashIfLaurlNode = TXml.findChildNS(
element.node, shaka.dash.ContentProtection.DashIfNamespaceUri_,
'Laurl',
);
if (dashIfLaurlNode) {
const textContents = shaka.util.TXml.getTextContents(dashIfLaurlNode);
const textContents = TXml.getTextContents(dashIfLaurlNode);
if (textContents) {
return textContents;
}
}

const proNode = shaka.util.TXml.findChildNS(
const proNode = TXml.findChildNS(
element.node, 'urn:microsoft:playready', 'pro');
if (proNode) {
const textContents = TXml.getTextContents(proNode);
if (textContents) {
return shaka.drm.PlayReady.getLicenseUrl(proNode);
}
}

if (!proNode || !shaka.util.TXml.getTextContents(proNode)) {
return '';
const psshNode = TXml.findChildNS(
element.node, shaka.dash.ContentProtection.CencNamespaceUri_, 'pssh');
if (psshNode) {
const textContents = TXml.getTextContents(psshNode);
if (textContents) {
const proData = shaka.util.Pssh.getPsshData(
shaka.util.Uint8ArrayUtils.fromBase64(textContents));
const proString = shaka.util.Uint8ArrayUtils.toStandardBase64(proData);
const reBuildProNode =
TXml.parseXmlString('<pro>' + proString + '</pro>');
goog.asserts.assert(reBuildProNode, 'Must have pro node');
return shaka.drm.PlayReady.getLicenseUrl(reBuildProNode);
}
}

return shaka.drm.PlayReady.getLicenseUrl(proNode);
return '';
}

/**
Expand Down
13 changes: 9 additions & 4 deletions lib/drm/playready.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,17 @@ shaka.drm.PlayReady = class {
* @return {string}
*/
static getLicenseUrl(element) {
const headerObject = shaka.drm.PlayReady.getPlayReadyHeaderObject_(element);
if (!headerObject) {
try {
const headerObject =
shaka.drm.PlayReady.getPlayReadyHeaderObject_(element);
if (!headerObject) {
return '';
}

return shaka.drm.PlayReady.getLaurl_(headerObject);
} catch (e) {
return '';
}

return shaka.drm.PlayReady.getLaurl_(headerObject);
}

/**
Expand Down
28 changes: 28 additions & 0 deletions lib/util/pssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,34 @@ shaka.util.Pssh = class {
return psshBox;
}

/**
* Returns just the data portion of a single PSSH
*
* @param {!Uint8Array} pssh
* @return {!Uint8Array}
*/
static getPsshData(pssh) {
let offset = 8; // Box size and type fields

/** @type {!DataView} */
const view = shaka.util.BufferUtils.toDataView(pssh);

// Read version
const version = view.getUint8(offset);

// Version (1), flags (3), system ID (16)
offset += 20;

if (version > 0) {
// Key ID count (4) and All key IDs (16*count)
offset += 4 + (16 * view.getUint32(offset));
}

// Data size
offset += 4;

return shaka.util.BufferUtils.toUint8(view, offset);
}

/**
* Normalise the initData array. This is to apply browser specific
Expand Down
103 changes: 103 additions & 0 deletions test/dash/dash_parser_content_protection_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,109 @@ describe('DashParser ContentProtection', () => {
expect(actual).toBe('www.example.com');
});

it('pssh version 0', () => {
const laurl = [
'<WRMHEADER>',
' <DATA>',
' <LA_URL>www.example.com</LA_URL>',
' </DATA>',
'</WRMHEADER>',
].join('\n');
const laurlCodes = laurl.split('').map((c) => {
return c.charCodeAt();
});
const prBytes = new Uint16Array([
// pr object size (in num bytes).
// + 10 for PRO size, count, and type
laurl.length * 2 + 10, 0,
// record count
1,
// type
shaka.drm.PlayReady.PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT,
// record size (in num bytes)
laurl.length * 2,
// value
].concat(laurlCodes));
const encodedPrObject = shaka.util.Uint8ArrayUtils.toBase64(prBytes);
const data = shaka.util.Uint8ArrayUtils.fromBase64(encodedPrObject);
// PlayReady SystemID
const systemId = new Uint8Array([
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
const keyIds = new Set();
const psshVersion = 0;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
const psshBase64 = shaka.util.Uint8ArrayUtils.toBase64(pssh);
const input = {
init: null,
keyId: null,
schemeUri: '',
encryptionScheme: null,
node:
strToXml([
'<test xmlns:cenc="urn:mpeg:cenc:2013">',
' <cenc:pssh>' + psshBase64 + '</cenc:pssh>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getPlayReadyLicenseUrl(input);
expect(actual).toBe('www.example.com');
});

it('pssh version 1', () => {
const laurl = [
'<WRMHEADER>',
' <DATA>',
' <LA_URL>www.example.com</LA_URL>',
' </DATA>',
'</WRMHEADER>',
].join('\n');
const laurlCodes = laurl.split('').map((c) => {
return c.charCodeAt();
});
const prBytes = new Uint16Array([
// pr object size (in num bytes).
// + 10 for PRO size, count, and type
laurl.length * 2 + 10, 0,
// record count
1,
// type
shaka.drm.PlayReady.PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT,
// record size (in num bytes)
laurl.length * 2,
// value
].concat(laurlCodes));
const encodedPrObject = shaka.util.Uint8ArrayUtils.toBase64(prBytes);
const data = shaka.util.Uint8ArrayUtils.fromBase64(encodedPrObject);
// PlayReady SystemID
const systemId = new Uint8Array([
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
]);
const keyIds = new Set();
keyIds.add('575e49ee6270de247bb5f814a98a6b2b');
const psshVersion = 1;
const pssh =
shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
const psshBase64 = shaka.util.Uint8ArrayUtils.toBase64(pssh);
const input = {
init: null,
keyId: null,
schemeUri: '',
encryptionScheme: null,
node:
strToXml([
'<test xmlns:cenc="urn:mpeg:cenc:2013">',
' <cenc:pssh>' + psshBase64 + '</cenc:pssh>',
'</test>',
].join('\n')),
};
const actual = ContentProtection.getPlayReadyLicenseUrl(input);
expect(actual).toBe('www.example.com');
});

it('valid dashif:Laurl node', () => {
const input = {
init: null,
Expand Down

0 comments on commit 8fda5d8

Please sign in to comment.