-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Add ms:laurl and mspr:pro parsing #1010
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -250,6 +250,179 @@ shaka.dash.ContentProtection.parseFromRepresentation = function( | |
return repContext.defaultKeyId || context.defaultKeyId; | ||
}; | ||
|
||
/** | ||
* Gets a Widevine license URL from a content protection element | ||
* containing a custom `ms:laurl` element | ||
* | ||
* @param {shaka.dash.ContentProtection.Element} element | ||
* @return {string} | ||
* @private | ||
*/ | ||
shaka.dash.ContentProtection.getWidevineLicenseUrl_ = function(element) { | ||
var mslaurlNode = shaka.util.XmlUtils.findChild(element.node, 'ms:laurl'); | ||
|
||
if (mslaurlNode) { | ||
return mslaurlNode.getAttribute('licenseUrl') || ''; | ||
} | ||
|
||
return ''; | ||
}; | ||
|
||
/** | ||
* @typedef {{ | ||
* type: number, | ||
* length: number, | ||
* value: string | ||
* }} | ||
* | ||
* @description | ||
* The parsed result of a PlayReady object record. | ||
* | ||
* @property {number} type | ||
* Type of data stored in the record. | ||
* @property {number} length | ||
* Size of the record in bytes. | ||
* @property {string} value | ||
* Record content. | ||
*/ | ||
shaka.dash.ContentProtection.PlayReadyRecord; | ||
|
||
/** | ||
* @typedef {{ | ||
* length: number, | ||
* recordCount: number, | ||
* records: Array.<shaka.dash.ContentProtection.PlayReadyRecord>, | ||
* }} | ||
* | ||
* @description | ||
* The parsed result of a PlayReady object. | ||
* | ||
* @property {number} length | ||
* Size of the PlayReady header object in bytes. | ||
* @property {number} recordCount | ||
* Number of records in the PlayReady object. | ||
* @property {Array.<shaka.dash.ContentProtection.PlayReadyRecord>} records | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For object types like |
||
* Contains a variable number of records that contain license acquisition information. | ||
*/ | ||
shaka.dash.ContentProtection.PlayReadyHeaderObject; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You never use the |
||
|
||
/** | ||
* A map of PlayReady record types to description. | ||
* | ||
* @const {!Object.<number, string>} | ||
* @private | ||
*/ | ||
var PLAYREADY_RECORD_TYPES = { | ||
0x0001 : 'RIGHTS_MANAGEMENT', | ||
0x0002 : 'RESERVED', | ||
0x0003 : 'EMBEDDED_LICENSE' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to reverse this enum: /** @enum {number} */
var PLAYREADY_RECORD_TYPES = {
RIGHTS_MANAGEMENT: 0x001,
RESERVED: 0x002,
EMBEDDED_LICENSE: 0x003
}; If you look at my suggestion below, it allows us to use the compiler to check we don't misspell the name. Your enum here just converts a magic number to a magic string. Also, our compiler will remove the enum completely, so it avoids an object lookup at runtime. |
||
}; | ||
|
||
/** | ||
* @param {Uint16Array} recordData | ||
* @param {number} recordCount | ||
* @return {Array.<shaka.dash.ContentProtection.PlayReadyRecord>} | ||
* @private | ||
*/ | ||
shaka.dash.ContentProtection.parseRecords_ = function(recordData, recordCount) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about renaming this so it is clear it is a 'mspr:pro' record? Something like |
||
var head = 0; | ||
var records = []; | ||
for (var i = 0; i < recordCount; i++) { | ||
var recordRaw = recordData.subarray(head); | ||
|
||
var type = recordRaw[0]; | ||
var length = recordRaw[1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too, there are unchecked indexes. Can we add some checks to insure that the right amount of data is present before indexing? |
||
var offset = 2; | ||
var charCount = length / 2; | ||
var end = charCount + offset; | ||
|
||
// subarray end is exclusive | ||
var rawValue = recordRaw.subarray(offset, end); | ||
var value = String.fromCharCode.apply(null, rawValue); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use shaka.util.StringUtils.fromUTF8 to parse UTF-8 correctly and avoid a possible stack overflow with large strings. |
||
|
||
records.push({ | ||
type: type, | ||
length: length, | ||
value: value | ||
}); | ||
|
||
head = end; | ||
} | ||
|
||
return records; | ||
}; | ||
|
||
/** | ||
* Based off getLicenseServerURLFromInitData from dash.js | ||
* https://github.com/Dash-Industry-Forum/dash.js | ||
* | ||
* @param {Uint16Array} bytes | ||
* @return {shaka.dash.ContentProtection.PlayReadyHeaderObject} | ||
* @private | ||
*/ | ||
shaka.dash.ContentProtection.parsePro_ = function(bytes) { | ||
var length = bytes[0] | bytes[1]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you forget a shift here? |
||
var recordCount = bytes[2]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a guarantee that there will be at least 3 elements in bytes? |
||
|
||
var recordData = bytes.subarray(3); | ||
var records = shaka.dash.ContentProtection.parseRecords_(recordData, recordCount); | ||
|
||
return { | ||
length: length, | ||
recordCount: recordCount, | ||
records: records | ||
}; | ||
}; | ||
|
||
/** | ||
* @param {!Element} xml | ||
* @return {string} | ||
* @private | ||
*/ | ||
shaka.dash.ContentProtection.getLaurl_ = function(xml) { | ||
var laurlNode = xml.getElementsByTagName('LA_URL')[0]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a guarantee that there will be an element with the given tag name? |
||
if (laurlNode) { | ||
var laurl = laurlNode.childNodes[0].nodeValue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a guarantee that there will be a child node? |
||
|
||
if (laurl) { | ||
return laurl; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you reduce this down to:
|
||
|
||
return ''; | ||
}; | ||
|
||
/** | ||
* Gets a PlayReady license URL from a content protection element | ||
* containing a PlayReady Header Object | ||
* | ||
* @param {shaka.dash.ContentProtection.Element} element | ||
* @return {string} | ||
* @private | ||
*/ | ||
shaka.dash.ContentProtection.getPlayReadyLicenseServerURL_ = function(element) { | ||
var proNode = shaka.util.XmlUtils.findChild(element.node, 'mspr:pro'); | ||
|
||
if (!proNode) { | ||
return ''; | ||
} | ||
|
||
var bytes = shaka.util.Uint8ArrayUtils.fromBase64(proNode.textContent); | ||
var parsedPro = shaka.dash.ContentProtection.parsePro_(new Uint16Array(bytes.buffer)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I remember correctly, Uint16Array uses the host byte order, whereas PlayReady uses little-endian. You may need to use DataView to read the data with the correct byte order. |
||
|
||
var records = parsedPro.records; | ||
var parser = new DOMParser(); | ||
for (var i = 0; i < records.length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer it if we could avoid indexed array iteration. Can we do something like:
|
||
var record = records[i]; | ||
if (PLAYREADY_RECORD_TYPES[record.type] === 'RIGHTS_MANAGEMENT') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (record.type == PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT) |
||
var xml = parser.parseFromString(record.value, 'application/xml').documentElement; | ||
|
||
return shaka.dash.ContentProtection.getLaurl_(xml); | ||
} | ||
} | ||
|
||
return ''; | ||
}; | ||
|
||
/** | ||
* Creates DrmInfo objects from the given element. | ||
|
@@ -276,7 +449,19 @@ shaka.dash.ContentProtection.convertElements_ = function( | |
goog.asserts.assert(!element.init || element.init.length, | ||
'Init data must be null or non-empty.'); | ||
var initData = element.init || defaultInit; | ||
return [ManifestParserUtils.createDrmInfo(keySystem, initData)]; | ||
var drmInfo = ManifestParserUtils.createDrmInfo(keySystem, initData); | ||
|
||
// extract Widevine license URL | ||
if (keySystem === 'com.widevine.alpha') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make it easier for us to add support for other schemes, could we structure this like:
|
||
drmInfo.licenseServerUri = shaka.dash.ContentProtection.getWidevineLicenseUrl_(element); | ||
} | ||
|
||
// extract PlayReady license URL from PlayReady header Object | ||
if (keySystem === 'com.microsoft.playready') { | ||
drmInfo.licenseServerUri = shaka.dash.ContentProtection.getPlayReadyLicenseServerURL_(element); | ||
} | ||
|
||
return [drmInfo]; | ||
} else { | ||
goog.asserts.assert( | ||
callback, 'ContentProtection callback is required'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I am mistaken, 'ms:laurl' is a Microsoft specific element. I don't believe Widevine has a standard way of embedding URLs in the manifest.