mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
15c60221fe
Refactors PlayReady license URL extraction to operate directly on PRO bytes instead of performing a base64/XML roundtrip. Previously, getLicenseUrlFromPssh converted PRO bytes to base64, wrapped them in a synthetic XML node, parsed them, and then decoded them back to bytes before processing. This change removes the redundant conversions and makes the flow bytes-first. Both getLicenseUrl() and getLicenseUrlFromPssh() now delegate to a shared internal method that extracts the WRMHEADER directly from PRO bytes.
219 lines
5.6 KiB
JavaScript
219 lines
5.6 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.drm.PlayReady');
|
|
|
|
goog.require('shaka.log');
|
|
goog.require('shaka.util.BufferUtils');
|
|
goog.require('shaka.util.Pssh');
|
|
goog.require('shaka.util.StringUtils');
|
|
goog.require('shaka.util.TXml');
|
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
|
|
|
|
/**
|
|
* @summary A set of functions for parsing Microsoft Playready Objects.
|
|
*/
|
|
shaka.drm.PlayReady = class {
|
|
/**
|
|
* 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.drm.PlayReady.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.drm.PlayReady.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
|
|
return shaka.drm.PlayReady.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 Header Object
|
|
*
|
|
* @param {!Uint8Array} proBytes
|
|
* @return {?shaka.extern.xml.Node}
|
|
* @private
|
|
*/
|
|
static getPlayReadyHeaderObject_(proBytes) {
|
|
const PLAYREADY_RECORD_TYPES = shaka.drm.PlayReady.PLAYREADY_RECORD_TYPES;
|
|
|
|
const records = shaka.drm.PlayReady.parseMsPro_(proBytes);
|
|
const record = records.find((record) => {
|
|
return record.type === PLAYREADY_RECORD_TYPES.RIGHTS_MANAGEMENT;
|
|
});
|
|
|
|
if (!record) {
|
|
return null;
|
|
}
|
|
|
|
const xml = shaka.util.StringUtils.fromUTF16(record.value, true);
|
|
const rootElement = shaka.util.TXml.parseXmlString(xml, 'WRMHEADER');
|
|
if (!rootElement) {
|
|
return null;
|
|
}
|
|
return rootElement;
|
|
}
|
|
|
|
/**
|
|
* Gets a PlayReady license URL from a PlayReady Header Object
|
|
*
|
|
* @param {!shaka.extern.xml.Node} element
|
|
* @return {string}
|
|
*/
|
|
static getLicenseUrl(element) {
|
|
const bytes = shaka.util.Uint8ArrayUtils.fromBase64(
|
|
/** @type {string} */ (shaka.util.TXml.getTextContents(element)));
|
|
return shaka.drm.PlayReady.getLicenseFromHeader_(bytes);
|
|
}
|
|
|
|
/**
|
|
* Gets a PlayReady license URL from a PSSH
|
|
*
|
|
* @param {!Uint8Array} data
|
|
* @return {string}
|
|
*/
|
|
static getLicenseUrlFromPssh(data) {
|
|
const proData = shaka.util.Pssh.getPsshData(data);
|
|
return shaka.drm.PlayReady.getLicenseFromHeader_(proData);
|
|
}
|
|
|
|
/**
|
|
* Gets a PlayReady license URL from PlayReady Header Object bytes.
|
|
*
|
|
* @param {!Uint8Array} bytes
|
|
* @return {string}
|
|
* @private
|
|
*/
|
|
static getLicenseFromHeader_(bytes) {
|
|
try {
|
|
const headerObject =
|
|
shaka.drm.PlayReady.getPlayReadyHeaderObject_(bytes);
|
|
if (!headerObject) {
|
|
return '';
|
|
}
|
|
|
|
return shaka.drm.PlayReady.getLaurl_(headerObject);
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @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.drm.PlayReady.PlayReadyRecord;
|
|
|
|
/**
|
|
* Enum for PlayReady record types.
|
|
* @enum {number}
|
|
*/
|
|
shaka.drm.PlayReady.PLAYREADY_RECORD_TYPES = {
|
|
RIGHTS_MANAGEMENT: 0x001,
|
|
RESERVED: 0x002,
|
|
EMBEDDED_LICENSE: 0x003,
|
|
};
|