Skip to content

Commit

Permalink
chore: Add PlayReady utils
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad committed Dec 20, 2024
1 parent 6616ff2 commit 0216860
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 363 deletions.
2 changes: 2 additions & 0 deletions build/types/core
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
+../../lib/deprecate/enforcer.js
+../../lib/deprecate/version.js

+../../lib/drm/playready.js

+../../lib/media/adaptation_set.js
+../../lib/media/adaptation_set_criteria.js
+../../lib/media/buffering_observer.js
Expand Down
152 changes: 2 additions & 150 deletions lib/dash/content_protection.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ goog.provide('shaka.dash.ContentProtection');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.BufferUtils');
goog.require('shaka.drm.PlayReady');
goog.require('shaka.util.Error');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.Platform');
Expand Down Expand Up @@ -285,107 +285,6 @@ shaka.dash.ContentProtection = class {
return '';
}

/**
* Parses an Array buffer starting at byteOffset for PlayReady Object Records.
* Each PRO Record is preceded by its PlayReady Record type and length in
* bytes.
*
* PlayReady Object Record format: https://goo.gl/FTcu46
*
* @param {!DataView} view
* @param {number} byteOffset
* @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
* @private
*/
static parseMsProRecords_(view, byteOffset) {
const records = [];

while (byteOffset < view.byteLength - 1) {
const type = view.getUint16(byteOffset, true);
byteOffset += 2;

const byteLength = view.getUint16(byteOffset, true);
byteOffset += 2;

if ((byteLength & 1) != 0 || byteLength + byteOffset > view.byteLength) {
shaka.log.warning('Malformed MS PRO object');
return [];
}

const recordValue = shaka.util.BufferUtils.toUint8(
view, byteOffset, byteLength);
records.push({
type: type,
value: recordValue,
});

byteOffset += byteLength;
}

return records;
}

/**
* Parses a buffer for PlayReady Objects. The data
* should contain a 32-bit integer indicating the length of
* the PRO in bytes. Following that, a 16-bit integer for
* the number of PlayReady Object Records in the PRO. Lastly,
* a byte array of the PRO Records themselves.
*
* PlayReady Object format: https://goo.gl/W8yAN4
*
* @param {BufferSource} data
* @return {!Array.<shaka.dash.ContentProtection.PlayReadyRecord>}
* @private
*/
static parseMsPro_(data) {
let byteOffset = 0;
const view = shaka.util.BufferUtils.toDataView(data);

// First 4 bytes is the PRO length (DWORD)
const byteLength = view.getUint32(byteOffset, /* littleEndian= */ true);
byteOffset += 4;

if (byteLength != data.byteLength) {
// Malformed PRO
shaka.log.warning('PlayReady Object with invalid length encountered.');
return [];
}

// Skip PRO Record count (WORD)
byteOffset += 2;

// Rest of the data contains the PRO Records
const ContentProtection = shaka.dash.ContentProtection;
return ContentProtection.parseMsProRecords_(view, byteOffset);
}

/**
* PlayReady Header format: https://goo.gl/dBzxNA
*
* @param {!shaka.extern.xml.Node} xml
* @return {string}
* @private
*/
static getLaurl_(xml) {
const TXml = shaka.util.TXml;
// LA_URL element is optional and no more than one is
// allowed inside the DATA element. Only absolute URLs are allowed.
// If the LA_URL element exists, it must not be empty.
for (const elem of TXml.getElementsByTagName(xml, 'DATA')) {
if (elem.children) {
for (const child of elem.children) {
if (child.tagName == 'LA_URL') {
return /** @type{string} */(shaka.util.TXml.getTextContents(child));
}
}
}
}

// Not found
return '';
}

/**
* Gets a PlayReady license URL from a content protection element
* containing a PlayReady Header Object
Expand All @@ -412,28 +311,7 @@ shaka.dash.ContentProtection = class {
return '';
}

const ContentProtection = shaka.dash.ContentProtection;
const PLAYREADY_RECORD_TYPES = ContentProtection.PLAYREADY_RECORD_TYPES;

const textContent =
/** @type {string} */ (shaka.util.TXml.getTextContents(proNode));
const bytes = shaka.util.Uint8ArrayUtils.fromBase64(textContent);
const records = ContentProtection.parseMsPro_(bytes);
const record = records.filter((record) => {
return record.type === PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT;
})[0];

if (!record) {
return '';
}

const xml = shaka.util.StringUtils.fromUTF16(record.value, true);
const rootElement = shaka.util.TXml.parseXmlString(xml, 'WRMHEADER');
if (!rootElement) {
return '';
}

return ContentProtection.getLaurl_(rootElement);
return shaka.drm.PlayReady.getLicenseUrl(proNode);
}

/**
Expand Down Expand Up @@ -715,32 +593,6 @@ shaka.dash.ContentProtection = class {
}
};

/**
* @typedef {{
* type: number,
* value: !Uint8Array
* }}
*
* @description
* The parsed result of a PlayReady object record.
*
* @property {number} type
* Type of data stored in the record.
* @property {!Uint8Array} value
* Record content.
*/
shaka.dash.ContentProtection.PlayReadyRecord;

/**
* Enum for PlayReady record types.
* @enum {number}
*/
shaka.dash.ContentProtection.PLAYREADY_RECORD_TYPES = {
RIGHTS_MANAGEMENT: 0x001,
RESERVED: 0x002,
EMBEDDED_LICENSE: 0x003,
};

/**
* @typedef {{
* defaultKeyId: ?string,
Expand Down
Loading

0 comments on commit 0216860

Please sign in to comment.