mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
Update Util files to ES6
Issue #1157 Change-Id: Ib81d198e46bc57745f60af328d1160064e253ba3
This commit is contained in:
@@ -484,7 +484,6 @@ shaka.hls.HlsParser = class {
|
||||
* @private
|
||||
*/
|
||||
async createPeriod_(playlist) {
|
||||
const Functional = shaka.util.Functional;
|
||||
const tags = playlist.tags;
|
||||
|
||||
const mediaTags =
|
||||
@@ -515,7 +514,7 @@ shaka.hls.HlsParser = class {
|
||||
});
|
||||
|
||||
const allVariants = await Promise.all(variantsPromises);
|
||||
let variants = allVariants.reduce(Functional.collapseArrays, []);
|
||||
let variants = allVariants.reduce(shaka.util.Functional.collapseArrays, []);
|
||||
// Filter out null variants.
|
||||
variants = variants.filter((variant) => variant != null);
|
||||
|
||||
|
||||
+122
-119
@@ -23,19 +23,108 @@ goog.require('shaka.util.MultiMap');
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new EventManager. An EventManager maintains a collection of "event
|
||||
* @summary
|
||||
* An EventManager maintains a collection of "event
|
||||
* bindings" between event targets and event listeners.
|
||||
*
|
||||
* @struct
|
||||
* @constructor
|
||||
* @implements {shaka.util.IReleasable}
|
||||
*/
|
||||
shaka.util.EventManager = function() {
|
||||
shaka.util.EventManager = class {
|
||||
constructor() {
|
||||
/**
|
||||
* Maps an event type to an array of event bindings.
|
||||
* @private {shaka.util.MultiMap.<!shaka.util.EventManager.Binding_>}
|
||||
*/
|
||||
this.bindingMap_ = new shaka.util.MultiMap();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maps an event type to an array of event bindings.
|
||||
* @private {shaka.util.MultiMap.<!shaka.util.EventManager.Binding_>}
|
||||
* Detaches all event listeners.
|
||||
* @override
|
||||
*/
|
||||
this.bindingMap_ = new shaka.util.MultiMap();
|
||||
release() {
|
||||
this.removeAll();
|
||||
this.bindingMap_ = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attaches an event listener to an event target.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
*/
|
||||
listen(target, type, listener) {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const binding = new shaka.util.EventManager.Binding_(target, type,
|
||||
listener);
|
||||
this.bindingMap_.push(type, binding);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attaches an event listener to an event target. The listener will be
|
||||
* removed when the first instance of the event is fired.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
*/
|
||||
listenOnce(target, type, listener) {
|
||||
// Install a shim listener that will stop listening after the first event.
|
||||
const shim = (event) => {
|
||||
// Stop listening to this event.
|
||||
this.unlisten(target, type, shim);
|
||||
// Call the original listener.
|
||||
listener(event);
|
||||
};
|
||||
this.listen(target, type, shim);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detaches an event listener from an event target.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType=} listener The event listener.
|
||||
*/
|
||||
unlisten(target, type, listener) {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.bindingMap_.get(type) || [];
|
||||
|
||||
for (const binding of list) {
|
||||
if (binding.target == target) {
|
||||
if (listener == binding.listener || !listener) {
|
||||
binding.unlisten();
|
||||
this.bindingMap_.remove(type, binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detaches all event listeners from all targets.
|
||||
*/
|
||||
removeAll() {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.bindingMap_.getAll();
|
||||
|
||||
for (const binding of list) {
|
||||
binding.unlisten();
|
||||
}
|
||||
|
||||
this.bindingMap_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -45,127 +134,41 @@ shaka.util.EventManager = function() {
|
||||
shaka.util.EventManager.ListenerType;
|
||||
|
||||
|
||||
/**
|
||||
* Detaches all event listeners.
|
||||
* @override
|
||||
*/
|
||||
shaka.util.EventManager.prototype.release = function() {
|
||||
this.removeAll();
|
||||
this.bindingMap_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attaches an event listener to an event target.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
*/
|
||||
shaka.util.EventManager.prototype.listen = function(target, type, listener) {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const binding = new shaka.util.EventManager.Binding_(target, type, listener);
|
||||
this.bindingMap_.push(type, binding);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attaches an event listener to an event target. The listener will be removed
|
||||
* when the first instance of the event is fired.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
*/
|
||||
shaka.util.EventManager.prototype.listenOnce =
|
||||
function(target, type, listener) {
|
||||
// Install a shim listener that will stop listening after the first event.
|
||||
const shim = (event) => {
|
||||
// Stop listening to this event.
|
||||
this.unlisten(target, type, shim);
|
||||
// Call the original listener.
|
||||
listener(event);
|
||||
};
|
||||
this.listen(target, type, shim);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Detaches an event listener from an event target.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType=} listener The event listener.
|
||||
*/
|
||||
shaka.util.EventManager.prototype.unlisten = function(target, type, listener) {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.bindingMap_.get(type) || [];
|
||||
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
const binding = list[i];
|
||||
|
||||
if (binding.target == target) {
|
||||
if (listener == binding.listener || !listener) {
|
||||
binding.unlisten();
|
||||
this.bindingMap_.remove(type, binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Detaches all event listeners from all targets.
|
||||
*/
|
||||
shaka.util.EventManager.prototype.removeAll = function() {
|
||||
if (!this.bindingMap_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const list = this.bindingMap_.getAll();
|
||||
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
list[i].unlisten();
|
||||
}
|
||||
|
||||
this.bindingMap_.clear();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Binding_ and attaches the event listener to the event target.
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
* @constructor
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
shaka.util.EventManager.Binding_ = function(target, type, listener) {
|
||||
/** @type {EventTarget} */
|
||||
this.target = target;
|
||||
shaka.util.EventManager.Binding_ = class {
|
||||
/**
|
||||
* @param {EventTarget} target The event target.
|
||||
* @param {string} type The event type.
|
||||
* @param {shaka.util.EventManager.ListenerType} listener The event listener.
|
||||
*/
|
||||
constructor(target, type, listener) {
|
||||
/** @type {EventTarget} */
|
||||
this.target = target;
|
||||
|
||||
/** @type {string} */
|
||||
this.type = type;
|
||||
/** @type {string} */
|
||||
this.type = type;
|
||||
|
||||
/** @type {?shaka.util.EventManager.ListenerType} */
|
||||
this.listener = listener;
|
||||
/** @type {?shaka.util.EventManager.ListenerType} */
|
||||
this.listener = listener;
|
||||
|
||||
this.target.addEventListener(type, listener, false);
|
||||
};
|
||||
this.target.addEventListener(type, listener, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detaches the event listener from the event target. This does nothing if the
|
||||
* event listener is already detached.
|
||||
*/
|
||||
shaka.util.EventManager.Binding_.prototype.unlisten = function() {
|
||||
goog.asserts.assert(this.target, 'Missing target');
|
||||
this.target.removeEventListener(this.type, this.listener, false);
|
||||
/**
|
||||
* Detaches the event listener from the event target. This does nothing if
|
||||
* the event listener is already detached.
|
||||
*/
|
||||
unlisten() {
|
||||
goog.asserts.assert(this.target, 'Missing target');
|
||||
this.target.removeEventListener(this.type, this.listener, false);
|
||||
|
||||
this.target = null;
|
||||
this.listener = null;
|
||||
this.target = null;
|
||||
this.listener = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+45
-45
@@ -19,57 +19,57 @@ goog.provide('shaka.util.Functional');
|
||||
|
||||
|
||||
/**
|
||||
* @namespace shaka.util.Functional
|
||||
* @summary A set of functional utility functions.
|
||||
*/
|
||||
shaka.util.Functional = class {
|
||||
/**
|
||||
* Creates a promise chain that calls the given callback for each element in
|
||||
* the array in a catch of a promise.
|
||||
*
|
||||
* e.g.:
|
||||
* Promise.reject().catch(callback(array[0])).catch(callback(array[1]));
|
||||
*
|
||||
* @param {!Array.<ELEM>} array
|
||||
* @param {function(ELEM):!Promise.<RESULT>} callback
|
||||
* @return {!Promise.<RESULT>}
|
||||
* @template ELEM,RESULT
|
||||
*/
|
||||
static createFallbackPromiseChain(array, callback) {
|
||||
return array.reduce(((callback, promise, elem) => {
|
||||
return promise.catch(callback.bind(null, elem));
|
||||
}).bind(null, callback), Promise.reject());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a promise chain that calls the given callback for each element in
|
||||
* the array in a catch of a promise.
|
||||
*
|
||||
* e.g.:
|
||||
* Promise.reject().catch(callback(array[0])).catch(callback(array[1]));
|
||||
*
|
||||
* @param {!Array.<ELEM>} array
|
||||
* @param {function(ELEM):!Promise.<RESULT>} callback
|
||||
* @return {!Promise.<RESULT>}
|
||||
* @template ELEM,RESULT
|
||||
*/
|
||||
shaka.util.Functional.createFallbackPromiseChain = function(array, callback) {
|
||||
return array.reduce(((callback, promise, elem) => {
|
||||
return promise.catch(callback.bind(null, elem));
|
||||
}).bind(null, callback), Promise.reject());
|
||||
};
|
||||
/**
|
||||
* Returns the first array concatenated to the second; used to collapse an
|
||||
* array of arrays into a single array.
|
||||
*
|
||||
* @param {!Array.<T>} all
|
||||
* @param {!Array.<T>} part
|
||||
* @return {!Array.<T>}
|
||||
* @template T
|
||||
*/
|
||||
static collapseArrays(all, part) {
|
||||
return all.concat(part);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first array concatenated to the second; used to collapse an
|
||||
* array of arrays into a single array.
|
||||
*
|
||||
* @param {!Array.<T>} all
|
||||
* @param {!Array.<T>} part
|
||||
* @return {!Array.<T>}
|
||||
* @template T
|
||||
*/
|
||||
shaka.util.Functional.collapseArrays = function(all, part) {
|
||||
return all.concat(part);
|
||||
};
|
||||
/**
|
||||
* A no-op function. Useful in promise chains.
|
||||
*/
|
||||
static noop() {}
|
||||
|
||||
|
||||
/**
|
||||
* A no-op function. Useful in promise chains.
|
||||
*/
|
||||
shaka.util.Functional.noop = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the given value is not null; useful for filtering out null values.
|
||||
*
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
* @template T
|
||||
*/
|
||||
shaka.util.Functional.isNotNull = function(value) {
|
||||
return value != null;
|
||||
/**
|
||||
* Returns if the given value is not null; useful for filtering out null
|
||||
* values.
|
||||
*
|
||||
* @param {T} value
|
||||
* @return {boolean}
|
||||
* @template T
|
||||
*/
|
||||
static isNotNull(value) {
|
||||
return value != null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,54 +22,53 @@ goog.require('shaka.util.Functional');
|
||||
|
||||
|
||||
/**
|
||||
* @namespace shaka.util.ManifestParserUtils
|
||||
* @summary Utility functions for manifest parsing.
|
||||
*/
|
||||
shaka.util.ManifestParserUtils = class {
|
||||
/**
|
||||
* Resolves an array of relative URIs to the given base URIs. This will result
|
||||
* in M*N number of URIs.
|
||||
*
|
||||
* @param {!Array.<string>} baseUris
|
||||
* @param {!Array.<string>} relativeUris
|
||||
* @return {!Array.<string>}
|
||||
*/
|
||||
static resolveUris(baseUris, relativeUris) {
|
||||
const Functional = shaka.util.Functional;
|
||||
if (relativeUris.length == 0) {
|
||||
return baseUris;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves an array of relative URIs to the given base URIs. This will result
|
||||
* in M*N number of URIs.
|
||||
*
|
||||
* @param {!Array.<string>} baseUris
|
||||
* @param {!Array.<string>} relativeUris
|
||||
* @return {!Array.<string>}
|
||||
*/
|
||||
shaka.util.ManifestParserUtils.resolveUris = function(baseUris, relativeUris) {
|
||||
const Functional = shaka.util.Functional;
|
||||
if (relativeUris.length == 0) {
|
||||
return baseUris;
|
||||
const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
|
||||
// Resolve each URI relative to each base URI, creating an Array of Arrays.
|
||||
// Then flatten the Arrays into a single Array.
|
||||
return baseUris.map((uri) => new goog.Uri(uri))
|
||||
.map((base) => relativeAsGoog.map(base.resolve.bind(base)))
|
||||
.reduce(Functional.collapseArrays, [])
|
||||
.map((uri) => uri.toString());
|
||||
}
|
||||
|
||||
const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
|
||||
// Resolve each URI relative to each base URI, creating an Array of Arrays.
|
||||
// Then flatten the Arrays into a single Array.
|
||||
return baseUris.map((uri) => new goog.Uri(uri))
|
||||
.map((base) => relativeAsGoog.map(base.resolve.bind(base)))
|
||||
.reduce(Functional.collapseArrays, [])
|
||||
.map((uri) => uri.toString());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DrmInfo object from the given info.
|
||||
*
|
||||
* @param {string} keySystem
|
||||
* @param {Array.<shaka.extern.InitDataOverride>} initData
|
||||
* @return {shaka.extern.DrmInfo}
|
||||
*/
|
||||
shaka.util.ManifestParserUtils.createDrmInfo = function(keySystem, initData) {
|
||||
return {
|
||||
keySystem: keySystem,
|
||||
licenseServerUri: '',
|
||||
distinctiveIdentifierRequired: false,
|
||||
persistentStateRequired: false,
|
||||
audioRobustness: '',
|
||||
videoRobustness: '',
|
||||
serverCertificate: null,
|
||||
initData: initData || [],
|
||||
keyIds: [],
|
||||
};
|
||||
/**
|
||||
* Creates a DrmInfo object from the given info.
|
||||
*
|
||||
* @param {string} keySystem
|
||||
* @param {Array.<shaka.extern.InitDataOverride>} initData
|
||||
* @return {shaka.extern.DrmInfo}
|
||||
*/
|
||||
static createDrmInfo(keySystem, initData) {
|
||||
return {
|
||||
keySystem: keySystem,
|
||||
licenseServerUri: '',
|
||||
distinctiveIdentifierRequired: false,
|
||||
persistentStateRequired: false,
|
||||
audioRobustness: '',
|
||||
videoRobustness: '',
|
||||
serverCertificate: null,
|
||||
initData: initData || [],
|
||||
keyIds: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+26
-27
@@ -19,36 +19,35 @@ goog.provide('shaka.util.MapUtils');
|
||||
|
||||
|
||||
/**
|
||||
* @namespace shaka.util.MapUtils
|
||||
* @summary A set of map/object utility functions.
|
||||
*/
|
||||
shaka.util.MapUtils = class {
|
||||
/**
|
||||
* @param {!Object.<KEY, VALUE>} object
|
||||
* @return {!Map.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
*/
|
||||
static asMap(object) {
|
||||
const map = new Map();
|
||||
Object.keys(object).forEach((key) => {
|
||||
map.set(key, object[key]);
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Object.<KEY, VALUE>} object
|
||||
* @return {!Map.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
*/
|
||||
shaka.util.MapUtils.asMap = function(object) {
|
||||
const map = new Map();
|
||||
Object.keys(object).forEach((key) => {
|
||||
map.set(key, object[key]);
|
||||
});
|
||||
/**
|
||||
* @param {!Map.<KEY, VALUE>} map
|
||||
* @return {!Object.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
*/
|
||||
static asObject(map) {
|
||||
const obj = {};
|
||||
map.forEach((value, key) => {
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Map.<KEY, VALUE>} map
|
||||
* @return {!Object.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
*/
|
||||
shaka.util.MapUtils.asObject = function(map) {
|
||||
const obj = {};
|
||||
map.forEach((value, key) => {
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
return obj;
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
|
||||
+242
-242
@@ -23,20 +23,252 @@ goog.require('shaka.util.DataViewReader');
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MP4 Parser
|
||||
* @struct
|
||||
* @constructor
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser = function() {
|
||||
/** @private {!Object.<number, shaka.util.Mp4Parser.BoxType_>} */
|
||||
this.headers_ = [];
|
||||
shaka.util.Mp4Parser = class {
|
||||
constructor() {
|
||||
/** @private {!Object.<number, shaka.util.Mp4Parser.BoxType_>} */
|
||||
this.headers_ = [];
|
||||
|
||||
/** @private {!Object.<number, !shaka.util.Mp4Parser.CallbackType>} */
|
||||
this.boxDefinitions_ = [];
|
||||
/** @private {!Object.<number, !shaka.util.Mp4Parser.CallbackType>} */
|
||||
this.boxDefinitions_ = [];
|
||||
|
||||
/** @private {boolean} */
|
||||
this.done_ = false;
|
||||
/** @private {boolean} */
|
||||
this.done_ = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare a box type as a Box.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {!shaka.util.Mp4Parser.CallbackType} definition
|
||||
* @return {!shaka.util.Mp4Parser}
|
||||
* @export
|
||||
*/
|
||||
box(type, definition) {
|
||||
const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
|
||||
this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.BASIC_BOX;
|
||||
this.boxDefinitions_[typeCode] = definition;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Declare a box type as a Full Box.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {!shaka.util.Mp4Parser.CallbackType} definition
|
||||
* @return {!shaka.util.Mp4Parser}
|
||||
* @export
|
||||
*/
|
||||
fullBox(type, definition) {
|
||||
const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
|
||||
this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.FULL_BOX;
|
||||
this.boxDefinitions_[typeCode] = definition;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stop parsing. Useful for extracting information from partial segments and
|
||||
* avoiding an out-of-bounds error once you find what you are looking for.
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
stop() {
|
||||
this.done_ = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given data using the added callbacks.
|
||||
*
|
||||
* @param {!BufferSource} data
|
||||
* @param {boolean=} partialOkay If true, allow reading partial payloads
|
||||
* from some boxes. If the goal is a child box, we can sometimes find it
|
||||
* without enough data to find all child boxes.
|
||||
* @export
|
||||
*/
|
||||
parse(data, partialOkay) {
|
||||
const wrapped = new Uint8Array(data);
|
||||
const reader = new shaka.util.DataViewReader(
|
||||
new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength),
|
||||
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
||||
|
||||
this.done_ = false;
|
||||
while (reader.hasMoreData() && !this.done_) {
|
||||
this.parseNext(0, reader, partialOkay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the next box on the current level.
|
||||
*
|
||||
* @param {number} absStart The absolute start position in the original
|
||||
* byte array.
|
||||
* @param {!shaka.util.DataViewReader} reader
|
||||
* @param {boolean=} partialOkay If true, allow reading partial payloads
|
||||
* from some boxes. If the goal is a child box, we can sometimes find it
|
||||
* without enough data to find all child boxes.
|
||||
* @export
|
||||
*/
|
||||
parseNext(absStart, reader, partialOkay) {
|
||||
const start = reader.getPosition();
|
||||
|
||||
let size = reader.readUint32();
|
||||
const type = reader.readUint32();
|
||||
const name = shaka.util.Mp4Parser.typeToString(type);
|
||||
shaka.log.v2('Parsing MP4 box', name);
|
||||
|
||||
switch (size) {
|
||||
case 0:
|
||||
size = reader.getLength() - start;
|
||||
break;
|
||||
case 1:
|
||||
size = reader.readUint64();
|
||||
break;
|
||||
}
|
||||
|
||||
const boxDefinition = this.boxDefinitions_[type];
|
||||
|
||||
if (boxDefinition) {
|
||||
let version = null;
|
||||
let flags = null;
|
||||
|
||||
if (this.headers_[type] == shaka.util.Mp4Parser.BoxType_.FULL_BOX) {
|
||||
const versionAndFlags = reader.readUint32();
|
||||
version = versionAndFlags >>> 24;
|
||||
flags = versionAndFlags & 0xFFFFFF;
|
||||
}
|
||||
|
||||
// Read the whole payload so that the current level can be safely read
|
||||
// regardless of how the payload is parsed.
|
||||
let end = start + size;
|
||||
if (partialOkay && end > reader.getLength()) {
|
||||
// For partial reads, truncate the payload if we must.
|
||||
end = reader.getLength();
|
||||
}
|
||||
const payloadSize = end - reader.getPosition();
|
||||
const payload =
|
||||
(payloadSize > 0) ? reader.readBytes(payloadSize) : new Uint8Array(0);
|
||||
|
||||
const payloadReader = new shaka.util.DataViewReader(
|
||||
new DataView(
|
||||
payload.buffer, payload.byteOffset, payload.byteLength),
|
||||
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
||||
|
||||
/** @type {shaka.extern.ParsedBox} */
|
||||
const box = {
|
||||
parser: this,
|
||||
partialOkay: partialOkay || false,
|
||||
version: version,
|
||||
flags: flags,
|
||||
reader: payloadReader,
|
||||
size: size,
|
||||
start: start + absStart,
|
||||
};
|
||||
|
||||
boxDefinition(box);
|
||||
} else {
|
||||
// Move the read head to be at the end of the box.
|
||||
// If the box is longer than the remaining parts of the file, e.g. the
|
||||
// mp4 is improperly formatted, or this was a partial range request that
|
||||
// ended in the middle of a box, just skip to the end.
|
||||
const skipLength = Math.min(
|
||||
start + size - reader.getPosition(),
|
||||
reader.getLength() - reader.getPosition());
|
||||
reader.skip(skipLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A callback that tells the Mp4 parser to treat the body of a box as a series
|
||||
* of boxes. The number of boxes is limited by the size of the parent box.
|
||||
*
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @export
|
||||
*/
|
||||
static children(box) {
|
||||
while (box.reader.hasMoreData() && !box.parser.done_) {
|
||||
box.parser.parseNext(box.start, box.reader, box.partialOkay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A callback that tells the Mp4 parser to treat the body of a box as a sample
|
||||
* description. A sample description box has a fixed number of children. The
|
||||
* number of children is represented by a 4 byte unsigned integer. Each child
|
||||
* is a box.
|
||||
*
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @export
|
||||
*/
|
||||
static sampleDescription(box) {
|
||||
for (let count = box.reader.readUint32();
|
||||
count > 0 && !box.parser.done_;
|
||||
count -= 1) {
|
||||
box.parser.parseNext(box.start, box.reader, box.partialOkay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a callback that tells the Mp4 parser to treat the body of a box as a
|
||||
* binary blob and to parse the body's contents using the provided callback.
|
||||
*
|
||||
* @param {function(!Uint8Array)} callback
|
||||
* @return {!shaka.util.Mp4Parser.CallbackType}
|
||||
* @export
|
||||
*/
|
||||
static allData(callback) {
|
||||
return function(box) {
|
||||
const all = box.reader.getLength() - box.reader.getPosition();
|
||||
callback(box.reader.readBytes(all));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an ascii string name to the integer type for a box.
|
||||
*
|
||||
* @param {string} name The name of the box. The name must be four
|
||||
* characters long.
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
static typeFromString_(name) {
|
||||
goog.asserts.assert(
|
||||
name.length == 4,
|
||||
'Mp4 box names must be 4 characters long');
|
||||
|
||||
let code = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
code = (code << 8) | name.charCodeAt(i);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert an integer type from a box into an ascii string name.
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @param {number} type The type of the box, a uint32.
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
static typeToString(type) {
|
||||
const name = String.fromCharCode(
|
||||
(type >> 24) & 0xff,
|
||||
(type >> 16) & 0xff,
|
||||
(type >> 8) & 0xff,
|
||||
type & 0xff);
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -60,235 +292,3 @@ shaka.util.Mp4Parser.BoxType_ = {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Declare a box type as a Box.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {!shaka.util.Mp4Parser.CallbackType} definition
|
||||
* @return {!shaka.util.Mp4Parser}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.prototype.box = function(type, definition) {
|
||||
const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
|
||||
this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.BASIC_BOX;
|
||||
this.boxDefinitions_[typeCode] = definition;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Declare a box type as a Full Box.
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {!shaka.util.Mp4Parser.CallbackType} definition
|
||||
* @return {!shaka.util.Mp4Parser}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.prototype.fullBox = function(type, definition) {
|
||||
const typeCode = shaka.util.Mp4Parser.typeFromString_(type);
|
||||
this.headers_[typeCode] = shaka.util.Mp4Parser.BoxType_.FULL_BOX;
|
||||
this.boxDefinitions_[typeCode] = definition;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stop parsing. Useful for extracting information from partial segments and
|
||||
* avoiding an out-of-bounds error once you find what you are looking for.
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.prototype.stop = function() {
|
||||
this.done_ = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse the given data using the added callbacks.
|
||||
*
|
||||
* @param {!BufferSource} data
|
||||
* @param {boolean=} partialOkay If true, allow reading partial payloads
|
||||
* from some boxes. If the goal is a child box, we can sometimes find it
|
||||
* without enough data to find all child boxes.
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.prototype.parse = function(data, partialOkay) {
|
||||
const wrapped = new Uint8Array(data);
|
||||
const reader = new shaka.util.DataViewReader(
|
||||
new DataView(wrapped.buffer, wrapped.byteOffset, wrapped.byteLength),
|
||||
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
||||
|
||||
this.done_ = false;
|
||||
while (reader.hasMoreData() && !this.done_) {
|
||||
this.parseNext(0, reader, partialOkay);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse the next box on the current level.
|
||||
*
|
||||
* @param {number} absStart The absolute start position in the original
|
||||
* byte array.
|
||||
* @param {!shaka.util.DataViewReader} reader
|
||||
* @param {boolean=} partialOkay If true, allow reading partial payloads
|
||||
* from some boxes. If the goal is a child box, we can sometimes find it
|
||||
* without enough data to find all child boxes.
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.prototype.parseNext =
|
||||
function(absStart, reader, partialOkay) {
|
||||
const start = reader.getPosition();
|
||||
|
||||
let size = reader.readUint32();
|
||||
const type = reader.readUint32();
|
||||
const name = shaka.util.Mp4Parser.typeToString(type);
|
||||
shaka.log.v2('Parsing MP4 box', name);
|
||||
|
||||
switch (size) {
|
||||
case 0:
|
||||
size = reader.getLength() - start;
|
||||
break;
|
||||
case 1:
|
||||
size = reader.readUint64();
|
||||
break;
|
||||
}
|
||||
|
||||
const boxDefinition = this.boxDefinitions_[type];
|
||||
|
||||
if (boxDefinition) {
|
||||
let version = null;
|
||||
let flags = null;
|
||||
|
||||
if (this.headers_[type] == shaka.util.Mp4Parser.BoxType_.FULL_BOX) {
|
||||
const versionAndFlags = reader.readUint32();
|
||||
version = versionAndFlags >>> 24;
|
||||
flags = versionAndFlags & 0xFFFFFF;
|
||||
}
|
||||
|
||||
// Read the whole payload so that the current level can be safely read
|
||||
// regardless of how the payload is parsed.
|
||||
let end = start + size;
|
||||
if (partialOkay && end > reader.getLength()) {
|
||||
// For partial reads, truncate the payload if we must.
|
||||
end = reader.getLength();
|
||||
}
|
||||
const payloadSize = end - reader.getPosition();
|
||||
const payload =
|
||||
(payloadSize > 0) ? reader.readBytes(payloadSize) : new Uint8Array(0);
|
||||
|
||||
const payloadReader = new shaka.util.DataViewReader(
|
||||
new DataView(
|
||||
payload.buffer, payload.byteOffset, payload.byteLength),
|
||||
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
|
||||
|
||||
/** @type {shaka.extern.ParsedBox} */
|
||||
const box = {
|
||||
parser: this,
|
||||
partialOkay: partialOkay || false,
|
||||
version: version,
|
||||
flags: flags,
|
||||
reader: payloadReader,
|
||||
size: size,
|
||||
start: start + absStart,
|
||||
};
|
||||
|
||||
boxDefinition(box);
|
||||
} else {
|
||||
// Move the read head to be at the end of the box.
|
||||
// If the box is longer than the remaining parts of the file, e.g. the
|
||||
// mp4 is improperly formatted, or this was a partial range request that
|
||||
// ended in the middle of a box, just skip to the end.
|
||||
const skipLength = Math.min(
|
||||
start + size - reader.getPosition(),
|
||||
reader.getLength() - reader.getPosition());
|
||||
reader.skip(skipLength);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback that tells the Mp4 parser to treat the body of a box as a series
|
||||
* of boxes. The number of boxes is limited by the size of the parent box.
|
||||
*
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.children = function(box) {
|
||||
while (box.reader.hasMoreData() && !box.parser.done_) {
|
||||
box.parser.parseNext(box.start, box.reader, box.partialOkay);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A callback that tells the Mp4 parser to treat the body of a box as a sample
|
||||
* description. A sample description box has a fixed number of children. The
|
||||
* number of children is represented by a 4 byte unsigned integer. Each child
|
||||
* is a box.
|
||||
*
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.sampleDescription = function(box) {
|
||||
for (let count = box.reader.readUint32();
|
||||
count > 0 && !box.parser.done_;
|
||||
count -= 1) {
|
||||
box.parser.parseNext(box.start, box.reader, box.partialOkay);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a callback that tells the Mp4 parser to treat the body of a box as a
|
||||
* binary blob and to parse the body's contents using the provided callback.
|
||||
*
|
||||
* @param {function(!Uint8Array)} callback
|
||||
* @return {!shaka.util.Mp4Parser.CallbackType}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.allData = function(callback) {
|
||||
return function(box) {
|
||||
const all = box.reader.getLength() - box.reader.getPosition();
|
||||
callback(box.reader.readBytes(all));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convert an ascii string name to the integer type for a box.
|
||||
*
|
||||
* @param {string} name The name of the box. The name must be four
|
||||
* characters long.
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
shaka.util.Mp4Parser.typeFromString_ = function(name) {
|
||||
goog.asserts.assert(
|
||||
name.length == 4,
|
||||
'Mp4 box names must be 4 characters long');
|
||||
|
||||
let code = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
code = (code << 8) | name.charCodeAt(i);
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convert an integer type from a box into an ascii string name.
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @param {number} type The type of the box, a uint32.
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Mp4Parser.typeToString = function(type) {
|
||||
const name = String.fromCharCode(
|
||||
(type >> 24) & 0xff,
|
||||
(type >> 16) & 0xff,
|
||||
(type >> 8) & 0xff,
|
||||
type & 0xff);
|
||||
return name;
|
||||
};
|
||||
|
||||
+74
-74
@@ -19,89 +19,89 @@ goog.provide('shaka.util.MultiMap');
|
||||
|
||||
|
||||
/**
|
||||
* A simple multimap template.
|
||||
* @constructor
|
||||
* @struct
|
||||
* @summary A simple multimap template.
|
||||
* @template T
|
||||
*/
|
||||
shaka.util.MultiMap = function() {
|
||||
/** @private {!Object.<string, !Array.<T>>} */
|
||||
this.map_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a key, value pair to the map.
|
||||
* @param {string} key
|
||||
* @param {T} value
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.push = function(key, value) {
|
||||
if (this.map_.hasOwnProperty(key)) {
|
||||
this.map_[key].push(value);
|
||||
} else {
|
||||
this.map_[key] = [value];
|
||||
shaka.util.MultiMap = class {
|
||||
constructor() {
|
||||
/** @private {!Object.<string, !Array.<T>>} */
|
||||
this.map_ = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of values by key.
|
||||
* @param {string} key
|
||||
* @return {Array.<T>} or null if no such key exists.
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.get = function(key) {
|
||||
const list = this.map_[key];
|
||||
// slice() clones the list so that it and the map can each be modified
|
||||
// without affecting the other.
|
||||
return list ? list.slice() : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of all values.
|
||||
* @return {!Array.<T>}
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.getAll = function() {
|
||||
const list = [];
|
||||
for (const key in this.map_) {
|
||||
list.push.apply(list, this.map_[key]);
|
||||
/**
|
||||
* Add a key, value pair to the map.
|
||||
* @param {string} key
|
||||
* @param {T} value
|
||||
*/
|
||||
push(key, value) {
|
||||
if (this.map_.hasOwnProperty(key)) {
|
||||
this.map_[key].push(value);
|
||||
} else {
|
||||
this.map_[key] = [value];
|
||||
}
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove a specific value, if it exists.
|
||||
* @param {string} key
|
||||
* @param {T} value
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.remove = function(key, value) {
|
||||
const list = this.map_[key];
|
||||
if (!list) {
|
||||
return;
|
||||
/**
|
||||
* Get a list of values by key.
|
||||
* @param {string} key
|
||||
* @return {Array.<T>} or null if no such key exists.
|
||||
*/
|
||||
get(key) {
|
||||
const list = this.map_[key];
|
||||
// slice() clones the list so that it and the map can each be modified
|
||||
// without affecting the other.
|
||||
return list ? list.slice() : null;
|
||||
}
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
if (list[i] == value) {
|
||||
list.splice(i, 1);
|
||||
--i;
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of all values.
|
||||
* @return {!Array.<T>}
|
||||
*/
|
||||
getAll() {
|
||||
const list = [];
|
||||
for (const key in this.map_) {
|
||||
list.push.apply(list, this.map_[key]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove a specific value, if it exists.
|
||||
* @param {string} key
|
||||
* @param {T} value
|
||||
*/
|
||||
remove(key, value) {
|
||||
const list = this.map_[key];
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
if (list[i] == value) {
|
||||
list.splice(i, 1);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear all keys and values from the multimap.
|
||||
*/
|
||||
clear() {
|
||||
this.map_ = {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {function(string, !Array.<T>)} callback
|
||||
*/
|
||||
forEach(callback) {
|
||||
for (const key in this.map_) {
|
||||
callback(key, this.map_[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clear all keys and values from the multimap.
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.clear = function() {
|
||||
this.map_ = {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {function(string, !Array.<T>)} callback
|
||||
*/
|
||||
shaka.util.MultiMap.prototype.forEach = function(callback) {
|
||||
for (const key in this.map_) {
|
||||
callback(key, this.map_[key]);
|
||||
}
|
||||
};
|
||||
|
||||
+68
-65
@@ -24,83 +24,86 @@ goog.require('shaka.util.Uint8ArrayUtils');
|
||||
|
||||
|
||||
/**
|
||||
* @summary
|
||||
* Parse a PSSH box and extract the system IDs.
|
||||
*
|
||||
* @param {!Uint8Array} psshBox
|
||||
* @constructor
|
||||
* @struct
|
||||
* @throws {shaka.util.Error} if a PSSH box is truncated or contains a size
|
||||
* field over 53 bits.
|
||||
*/
|
||||
shaka.util.Pssh = function(psshBox) {
|
||||
shaka.util.Pssh = class {
|
||||
/**
|
||||
* In hex.
|
||||
* @type {!Array.<string>}
|
||||
* @param {!Uint8Array} psshBox
|
||||
* @throws {shaka.util.Error} if a PSSH box is truncated or contains a size
|
||||
* field over 53 bits.
|
||||
*/
|
||||
this.systemIds = [];
|
||||
constructor(psshBox) {
|
||||
/**
|
||||
* In hex.
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
this.systemIds = [];
|
||||
|
||||
/**
|
||||
* In hex.
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
this.cencKeyIds = [];
|
||||
/**
|
||||
* In hex.
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
this.cencKeyIds = [];
|
||||
|
||||
/*
|
||||
* Array of tuples that define the startIndex + size for each
|
||||
* discrete pssh within |psshBox|
|
||||
* */
|
||||
this.dataBoundaries = [];
|
||||
/*
|
||||
* Array of tuples that define the startIndex + size for each
|
||||
* discrete pssh within |psshBox|
|
||||
* */
|
||||
this.dataBoundaries = [];
|
||||
|
||||
new shaka.util.Mp4Parser()
|
||||
.fullBox('pssh', this.parseBox_.bind(this)).parse(psshBox.buffer);
|
||||
new shaka.util.Mp4Parser()
|
||||
.fullBox('pssh', this.parseBox_.bind(this)).parse(psshBox.buffer);
|
||||
|
||||
if (this.dataBoundaries.length == 0) {
|
||||
shaka.log.warning('No pssh box found!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @private
|
||||
*/
|
||||
shaka.util.Pssh.prototype.parseBox_ = function(box) {
|
||||
goog.asserts.assert(
|
||||
box.version != null,
|
||||
'PSSH boxes are full boxes and must have a valid version');
|
||||
|
||||
goog.asserts.assert(
|
||||
box.flags != null,
|
||||
'PSSH boxes are full boxes and must have a valid flag');
|
||||
|
||||
if (box.version > 1) {
|
||||
shaka.log.warning('Unrecognized PSSH version found!');
|
||||
return;
|
||||
}
|
||||
|
||||
const systemId = shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16));
|
||||
const keyIds = [];
|
||||
if (box.version > 0) {
|
||||
const numKeyIds = box.reader.readUint32();
|
||||
for (let i = 0; i < numKeyIds; ++i) {
|
||||
const keyId = shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16));
|
||||
keyIds.push(keyId);
|
||||
if (this.dataBoundaries.length == 0) {
|
||||
shaka.log.warning('No pssh box found!');
|
||||
}
|
||||
}
|
||||
|
||||
const dataSize = box.reader.readUint32();
|
||||
box.reader.skip(dataSize); // Ignore the data section.
|
||||
|
||||
// Now that everything has been succesfully parsed from this box,
|
||||
// update member variables.
|
||||
this.cencKeyIds.push.apply(this.cencKeyIds, keyIds);
|
||||
this.systemIds.push(systemId);
|
||||
this.dataBoundaries.push({
|
||||
start: box.start,
|
||||
end: box.start + box.size - 1,
|
||||
});
|
||||
/**
|
||||
* @param {!shaka.extern.ParsedBox} box
|
||||
* @private
|
||||
*/
|
||||
parseBox_(box) {
|
||||
goog.asserts.assert(
|
||||
box.version != null,
|
||||
'PSSH boxes are full boxes and must have a valid version');
|
||||
|
||||
if (box.reader.getPosition() != box.reader.getLength()) {
|
||||
shaka.log.warning('Mismatch between box size and data size!');
|
||||
goog.asserts.assert(
|
||||
box.flags != null,
|
||||
'PSSH boxes are full boxes and must have a valid flag');
|
||||
|
||||
if (box.version > 1) {
|
||||
shaka.log.warning('Unrecognized PSSH version found!');
|
||||
return;
|
||||
}
|
||||
|
||||
const systemId = shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16));
|
||||
const keyIds = [];
|
||||
if (box.version > 0) {
|
||||
const numKeyIds = box.reader.readUint32();
|
||||
for (let i = 0; i < numKeyIds; ++i) {
|
||||
const keyId =
|
||||
shaka.util.Uint8ArrayUtils.toHex(box.reader.readBytes(16));
|
||||
keyIds.push(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
const dataSize = box.reader.readUint32();
|
||||
box.reader.skip(dataSize); // Ignore the data section.
|
||||
|
||||
// Now that everything has been succesfully parsed from this box,
|
||||
// update member variables.
|
||||
this.cencKeyIds.push.apply(this.cencKeyIds, keyIds);
|
||||
this.systemIds.push(systemId);
|
||||
this.dataBoundaries.push({
|
||||
start: box.start,
|
||||
end: box.start + box.size - 1,
|
||||
});
|
||||
|
||||
if (box.reader.getPosition() != box.reader.getLength()) {
|
||||
shaka.log.warning('Mismatch between box size and data size!');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+34
-31
@@ -19,46 +19,49 @@ goog.provide('shaka.util.PublicPromise');
|
||||
|
||||
|
||||
/**
|
||||
* @summary
|
||||
* A utility to create Promises with convenient public resolve and reject
|
||||
* methods.
|
||||
*
|
||||
* @constructor
|
||||
* @struct
|
||||
* @extends {Promise.<T>}
|
||||
* @return {Promise.<T>}
|
||||
* @template T
|
||||
*/
|
||||
shaka.util.PublicPromise = function() {
|
||||
let resolvePromise;
|
||||
let rejectPromise;
|
||||
shaka.util.PublicPromise = class {
|
||||
/**
|
||||
* @return {Promise.<T>}
|
||||
*/
|
||||
constructor() {
|
||||
let resolvePromise;
|
||||
let rejectPromise;
|
||||
|
||||
// Promise.call causes an error. It seems that inheriting from a native
|
||||
// Promise is not permitted by JavaScript interpreters.
|
||||
// Promise.call causes an error. It seems that inheriting from a native
|
||||
// Promise is not permitted by JavaScript interpreters.
|
||||
|
||||
// The work-around is to construct a Promise object, modify it to look like
|
||||
// the compiler's picture of PublicPromise, then return it. The caller of
|
||||
// new PublicPromise will receive |promise| instead of |this|, and the
|
||||
// compiler will be aware of the additional properties |resolve| and
|
||||
// |reject|.
|
||||
// The work-around is to construct a Promise object, modify it to look like
|
||||
// the compiler's picture of PublicPromise, then return it. The caller of
|
||||
// new PublicPromise will receive |promise| instead of |this|, and the
|
||||
// compiler will be aware of the additional properties |resolve| and
|
||||
// |reject|.
|
||||
|
||||
const promise = new Promise(((resolve, reject) => {
|
||||
resolvePromise = resolve;
|
||||
rejectPromise = reject;
|
||||
}));
|
||||
const promise = new Promise(((resolve, reject) => {
|
||||
resolvePromise = resolve;
|
||||
rejectPromise = reject;
|
||||
}));
|
||||
|
||||
// Now cast the Promise object to our subclass PublicPromise so that the
|
||||
// compiler will permit us to attach resolve() and reject() to it.
|
||||
const publicPromise = /** @type {shaka.util.PublicPromise} */(promise);
|
||||
publicPromise.resolve = resolvePromise;
|
||||
publicPromise.reject = rejectPromise;
|
||||
// Now cast the Promise object to our subclass PublicPromise so that the
|
||||
// compiler will permit us to attach resolve() and reject() to it.
|
||||
const publicPromise = /** @type {shaka.util.PublicPromise} */(promise);
|
||||
publicPromise.resolve = resolvePromise;
|
||||
publicPromise.reject = rejectPromise;
|
||||
|
||||
return publicPromise;
|
||||
return publicPromise;
|
||||
}
|
||||
|
||||
|
||||
/** @param {T=} value */
|
||||
resolve(value) {}
|
||||
|
||||
|
||||
/** @param {*=} reason */
|
||||
reject(reason) {}
|
||||
};
|
||||
|
||||
|
||||
/** @param {T=} value */
|
||||
shaka.util.PublicPromise.prototype.resolve = function(value) {};
|
||||
|
||||
|
||||
/** @param {*=} reason */
|
||||
shaka.util.PublicPromise.prototype.reject = function(reason) {};
|
||||
|
||||
+658
-662
File diff suppressed because it is too large
Load Diff
+192
-190
@@ -26,202 +26,204 @@ goog.require('shaka.util.Error');
|
||||
* @summary A set of string utility functions.
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.util.StringUtils = class {
|
||||
/**
|
||||
* Creates a string from the given buffer as UTF-8 encoding.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
static fromUTF8(data) {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let uint8 = new Uint8Array(data);
|
||||
// If present, strip off the UTF-8 BOM.
|
||||
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
|
||||
uint8 = uint8.subarray(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string from the given buffer as UTF-8 encoding.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.StringUtils.fromUTF8 = function(data) {
|
||||
if (!data) {
|
||||
return '';
|
||||
// http://stackoverflow.com/a/13691499
|
||||
const utf8 = shaka.util.StringUtils.fromCharCode(uint8);
|
||||
// This converts each character in the string to an escape sequence. If the
|
||||
// character is in the ASCII range, it is not converted; otherwise it is
|
||||
// converted to a URI escape sequence.
|
||||
// Example: '\x67\x35\xe3\x82\xac' -> 'g#%E3%82%AC'
|
||||
const escaped = escape(utf8);
|
||||
// Decode the escaped sequence. This will interpret UTF-8 sequences into
|
||||
// the correct character.
|
||||
// Example: 'g#%E3%82%AC' -> 'g#€'
|
||||
try {
|
||||
return decodeURIComponent(escaped);
|
||||
} catch (e) {
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.BAD_ENCODING);
|
||||
}
|
||||
}
|
||||
|
||||
let uint8 = new Uint8Array(data);
|
||||
// If present, strip off the UTF-8 BOM.
|
||||
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
|
||||
uint8 = uint8.subarray(3);
|
||||
|
||||
/**
|
||||
* Creates a string from the given buffer as UTF-16 encoding.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @param {boolean} littleEndian
|
||||
true to read little endian, false to read big.
|
||||
* @param {boolean=} noThrow true to avoid throwing in cases where we may
|
||||
* expect invalid input. If noThrow is true and the data has an odd
|
||||
* length,it will be truncated.
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
static fromUTF16(data, littleEndian, noThrow) {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!noThrow && data.byteLength % 2 != 0) {
|
||||
shaka.log.error('Data has an incorrect length, must be even.');
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.BAD_ENCODING);
|
||||
}
|
||||
|
||||
/** @type {ArrayBuffer} */
|
||||
let buffer;
|
||||
if (data instanceof ArrayBuffer) {
|
||||
buffer = data;
|
||||
} else {
|
||||
// Have to create a new buffer because the argument may be a smaller
|
||||
// view on a larger ArrayBuffer. We cannot use an ArrayBufferView in
|
||||
// a DataView.
|
||||
const temp = new Uint8Array(data.byteLength);
|
||||
temp.set(new Uint8Array(data));
|
||||
buffer = temp.buffer;
|
||||
}
|
||||
|
||||
// Use a DataView to ensure correct endianness.
|
||||
const length = Math.floor(data.byteLength / 2);
|
||||
const arr = new Uint16Array(length);
|
||||
const dataView = new DataView(buffer);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = dataView.getUint16(i * 2, littleEndian);
|
||||
}
|
||||
return shaka.util.StringUtils.fromCharCode(arr);
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/13691499
|
||||
const utf8 = shaka.util.StringUtils.fromCharCode(uint8);
|
||||
// This converts each character in the string to an escape sequence. If the
|
||||
// character is in the ASCII range, it is not converted; otherwise it is
|
||||
// converted to a URI escape sequence.
|
||||
// Example: '\x67\x35\xe3\x82\xac' -> 'g#%E3%82%AC'
|
||||
const escaped = escape(utf8);
|
||||
// Decode the escaped sequence. This will interpret UTF-8 sequences into the
|
||||
// correct character.
|
||||
// Example: 'g#%E3%82%AC' -> 'g#€'
|
||||
try {
|
||||
return decodeURIComponent(escaped);
|
||||
} catch (e) {
|
||||
|
||||
/**
|
||||
* Creates a string from the given buffer, auto-detecting the encoding that is
|
||||
* being used. If it cannot detect the encoding, it will throw an exception.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
static fromBytesAutoDetect(data) {
|
||||
const StringUtils = shaka.util.StringUtils;
|
||||
|
||||
const uint8 = new Uint8Array(data);
|
||||
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
|
||||
return StringUtils.fromUTF8(uint8);
|
||||
} else if (uint8[0] == 0xfe && uint8[1] == 0xff) {
|
||||
return StringUtils.fromUTF16(uint8.subarray(2), false /* littleEndian */);
|
||||
} else if (uint8[0] == 0xff && uint8[1] == 0xfe) {
|
||||
return StringUtils.fromUTF16(uint8.subarray(2), true /* littleEndian */);
|
||||
}
|
||||
|
||||
const isAscii = (function(arr, i) {
|
||||
// arr[i] >= ' ' && arr[i] <= '~';
|
||||
return arr.byteLength <= i || (arr[i] >= 0x20 && arr[i] <= 0x7e);
|
||||
}.bind(null, uint8));
|
||||
|
||||
shaka.log.debug(
|
||||
'Unable to find byte-order-mark, making an educated guess.');
|
||||
if (uint8[0] == 0 && uint8[2] == 0) {
|
||||
return StringUtils.fromUTF16(data, false /* littleEndian */);
|
||||
} else if (uint8[1] == 0 && uint8[3] == 0) {
|
||||
return StringUtils.fromUTF16(data, true /* littleEndian */);
|
||||
} else if (isAscii(0) && isAscii(1) && isAscii(2) && isAscii(3)) {
|
||||
return StringUtils.fromUTF8(data);
|
||||
}
|
||||
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.BAD_ENCODING);
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.UNABLE_TO_DETECT_ENCODING);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a ArrayBuffer from the given string, converting to UTF-8 encoding.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {!ArrayBuffer}
|
||||
* @export
|
||||
*/
|
||||
static toUTF8(str) {
|
||||
// http://stackoverflow.com/a/13691499
|
||||
// Converts the given string to a URI encoded string. If a character falls
|
||||
// in the ASCII range, it is not converted; otherwise it will be converted
|
||||
// to a series of URI escape sequences according to UTF-8.
|
||||
// Example: 'g#€' -> 'g#%E3%82%AC'
|
||||
const encoded = encodeURIComponent(str);
|
||||
// Convert each escape sequence individually into a character. Each escape
|
||||
// sequence is interpreted as a code-point, so if an escape sequence happens
|
||||
// to be part of a multi-byte sequence, each byte will be converted to a
|
||||
// single character.
|
||||
// Example: 'g#%E3%82%AC' -> '\x67\x35\xe3\x82\xac'
|
||||
const utf8 = unescape(encoded);
|
||||
|
||||
const result = new Uint8Array(utf8.length);
|
||||
for (let i = 0; i < utf8.length; ++i) {
|
||||
result[i] = utf8.charCodeAt(i);
|
||||
}
|
||||
return result.buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a ArrayBuffer from the given string, converting to UTF-16 encoding.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {boolean} littleEndian
|
||||
* @return {!ArrayBuffer}
|
||||
* @export
|
||||
*/
|
||||
static toUTF16(str, littleEndian) {
|
||||
const result = new Uint8Array(str.length * 2);
|
||||
const view = new DataView(result.buffer);
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
const value = str.charCodeAt(i);
|
||||
view.setUint16(/* position= */ i * 2, value, littleEndian);
|
||||
}
|
||||
return result.buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new string from the given array of char codes.
|
||||
*
|
||||
* Using String.fromCharCode.apply is risky because you can trigger stack
|
||||
* errors on very large arrays. This breaks up the array into several pieces
|
||||
* to avoid this.
|
||||
*
|
||||
* @param {!TypedArray} array
|
||||
* @return {string}
|
||||
*/
|
||||
static fromCharCode(array) {
|
||||
const max = 16000;
|
||||
let ret = '';
|
||||
for (let i = 0; i < array.length; i += max) {
|
||||
const subArray = array.subarray(i, i + max);
|
||||
ret += String.fromCharCode.apply(null, subArray);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string from the given buffer as UTF-16 encoding.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @param {boolean} littleEndian true to read little endian, false to read big.
|
||||
* @param {boolean=} noThrow true to avoid throwing in cases where we may
|
||||
* expect invalid input. If noThrow is true and the data has an odd length,
|
||||
* it will be truncated.
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.StringUtils.fromUTF16 = function(data, littleEndian, noThrow) {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!noThrow && data.byteLength % 2 != 0) {
|
||||
shaka.log.error('Data has an incorrect length, must be even.');
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.BAD_ENCODING);
|
||||
}
|
||||
|
||||
/** @type {ArrayBuffer} */
|
||||
let buffer;
|
||||
if (data instanceof ArrayBuffer) {
|
||||
buffer = data;
|
||||
} else {
|
||||
// Have to create a new buffer because the argument may be a smaller
|
||||
// view on a larger ArrayBuffer. We cannot use an ArrayBufferView in
|
||||
// a DataView.
|
||||
const temp = new Uint8Array(data.byteLength);
|
||||
temp.set(new Uint8Array(data));
|
||||
buffer = temp.buffer;
|
||||
}
|
||||
|
||||
// Use a DataView to ensure correct endianness.
|
||||
const length = Math.floor(data.byteLength / 2);
|
||||
const arr = new Uint16Array(length);
|
||||
const dataView = new DataView(buffer);
|
||||
for (let i = 0; i < length; i++) {
|
||||
arr[i] = dataView.getUint16(i * 2, littleEndian);
|
||||
}
|
||||
return shaka.util.StringUtils.fromCharCode(arr);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a string from the given buffer, auto-detecting the encoding that is
|
||||
* being used. If it cannot detect the encoding, it will throw an exception.
|
||||
*
|
||||
* @param {?BufferSource} data
|
||||
* @return {string}
|
||||
* @throws {shaka.util.Error}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.StringUtils.fromBytesAutoDetect = function(data) {
|
||||
const StringUtils = shaka.util.StringUtils;
|
||||
|
||||
const uint8 = new Uint8Array(data);
|
||||
if (uint8[0] == 0xef && uint8[1] == 0xbb && uint8[2] == 0xbf) {
|
||||
return StringUtils.fromUTF8(uint8);
|
||||
} else if (uint8[0] == 0xfe && uint8[1] == 0xff) {
|
||||
return StringUtils.fromUTF16(uint8.subarray(2), false /* littleEndian */);
|
||||
} else if (uint8[0] == 0xff && uint8[1] == 0xfe) {
|
||||
return StringUtils.fromUTF16(uint8.subarray(2), true /* littleEndian */);
|
||||
}
|
||||
|
||||
const isAscii = (function(arr, i) {
|
||||
// arr[i] >= ' ' && arr[i] <= '~';
|
||||
return arr.byteLength <= i || (arr[i] >= 0x20 && arr[i] <= 0x7e);
|
||||
}.bind(null, uint8));
|
||||
|
||||
shaka.log.debug('Unable to find byte-order-mark, making an educated guess.');
|
||||
if (uint8[0] == 0 && uint8[2] == 0) {
|
||||
return StringUtils.fromUTF16(data, false /* littleEndian */);
|
||||
} else if (uint8[1] == 0 && uint8[3] == 0) {
|
||||
return StringUtils.fromUTF16(data, true /* littleEndian */);
|
||||
} else if (isAscii(0) && isAscii(1) && isAscii(2) && isAscii(3)) {
|
||||
return StringUtils.fromUTF8(data);
|
||||
}
|
||||
|
||||
throw new shaka.util.Error(
|
||||
shaka.util.Error.Severity.CRITICAL,
|
||||
shaka.util.Error.Category.TEXT,
|
||||
shaka.util.Error.Code.UNABLE_TO_DETECT_ENCODING);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a ArrayBuffer from the given string, converting to UTF-8 encoding.
|
||||
*
|
||||
* @param {string} str
|
||||
* @return {!ArrayBuffer}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.StringUtils.toUTF8 = function(str) {
|
||||
// http://stackoverflow.com/a/13691499
|
||||
// Converts the given string to a URI encoded string. If a character falls
|
||||
// in the ASCII range, it is not converted; otherwise it will be converted to
|
||||
// a series of URI escape sequences according to UTF-8.
|
||||
// Example: 'g#€' -> 'g#%E3%82%AC'
|
||||
const encoded = encodeURIComponent(str);
|
||||
// Convert each escape sequence individually into a character. Each escape
|
||||
// sequence is interpreted as a code-point, so if an escape sequence happens
|
||||
// to be part of a multi-byte sequence, each byte will be converted to a
|
||||
// single character.
|
||||
// Example: 'g#%E3%82%AC' -> '\x67\x35\xe3\x82\xac'
|
||||
const utf8 = unescape(encoded);
|
||||
|
||||
const result = new Uint8Array(utf8.length);
|
||||
for (let i = 0; i < utf8.length; ++i) {
|
||||
result[i] = utf8.charCodeAt(i);
|
||||
}
|
||||
return result.buffer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a ArrayBuffer from the given string, converting to UTF-16 encoding.
|
||||
*
|
||||
* @param {string} str
|
||||
* @param {boolean} littleEndian
|
||||
* @return {!ArrayBuffer}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.StringUtils.toUTF16 = function(str, littleEndian) {
|
||||
const result = new Uint8Array(str.length * 2);
|
||||
const view = new DataView(result.buffer);
|
||||
for (let i = 0; i < str.length; ++i) {
|
||||
const value = str.charCodeAt(i);
|
||||
view.setUint16(/* position= */ i * 2, value, littleEndian);
|
||||
}
|
||||
return result.buffer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new string from the given array of char codes.
|
||||
*
|
||||
* Using String.fromCharCode.apply is risky because you can trigger stack errors
|
||||
* on very large arrays. This breaks up the array into several pieces to avoid
|
||||
* this.
|
||||
*
|
||||
* @param {!TypedArray} array
|
||||
* @return {string}
|
||||
*/
|
||||
shaka.util.StringUtils.fromCharCode = function(array) {
|
||||
const max = 16000;
|
||||
let ret = '';
|
||||
for (let i = 0; i < array.length; i += max) {
|
||||
const subArray = array.subarray(i, i + max);
|
||||
ret += String.fromCharCode.apply(null, subArray);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
+109
-108
@@ -22,123 +22,124 @@ goog.require('goog.asserts');
|
||||
|
||||
/**
|
||||
* Reads elements from strings.
|
||||
*
|
||||
* @param {string} data
|
||||
* @constructor
|
||||
* @struct
|
||||
*/
|
||||
shaka.util.TextParser = function(data) {
|
||||
shaka.util.TextParser = class {
|
||||
/**
|
||||
* @const
|
||||
* @private {string}
|
||||
* @param {string} data
|
||||
*/
|
||||
this.data_ = data;
|
||||
constructor(data) {
|
||||
/**
|
||||
* @const
|
||||
* @private {string}
|
||||
*/
|
||||
this.data_ = data;
|
||||
|
||||
/** @private {number} */
|
||||
this.position_ = 0;
|
||||
};
|
||||
|
||||
|
||||
/** @return {boolean} Whether it is at the end of the string. */
|
||||
shaka.util.TextParser.prototype.atEnd = function() {
|
||||
return this.position_ == this.data_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads a line from the parser. This will read but not return the newline.
|
||||
* Returns null at the end.
|
||||
*
|
||||
* @return {?string}
|
||||
*/
|
||||
shaka.util.TextParser.prototype.readLine = function() {
|
||||
return this.readRegexReturnCapture_(/(.*?)(\n|$)/gm, 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads a word from the parser. This will not read or return any whitespace
|
||||
* before or after the word (including newlines). Returns null at the end.
|
||||
*
|
||||
* @return {?string}
|
||||
*/
|
||||
shaka.util.TextParser.prototype.readWord = function() {
|
||||
return this.readRegexReturnCapture_(/[^ \t\n]*/gm, 0);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Skips any continuous whitespace from the parser. Returns null at the end.
|
||||
*/
|
||||
shaka.util.TextParser.prototype.skipWhitespace = function() {
|
||||
this.readRegex(/[ \t]+/gm);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads the given regular expression from the parser. This requires the match
|
||||
* to be at the current position; there is no need to include a head anchor.
|
||||
* This requires that the regex have the global flag to be set so that it can
|
||||
* set lastIndex to start the search at the current position. Returns null at
|
||||
* the end or if the regex does not match the current position.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
shaka.util.TextParser.prototype.readRegex = function(regex) {
|
||||
const index = this.indexOf_(regex);
|
||||
if (this.atEnd() || index == null || index.position != this.position_) {
|
||||
return null;
|
||||
/** @private {number} */
|
||||
this.position_ = 0;
|
||||
}
|
||||
|
||||
this.position_ += index.length;
|
||||
return index.results;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads a regex from the parser and returns the given capture.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @param {number} index
|
||||
* @return {?string}
|
||||
* @private
|
||||
*/
|
||||
shaka.util.TextParser.prototype.readRegexReturnCapture_ = function(
|
||||
regex, index) {
|
||||
if (this.atEnd()) {
|
||||
return null;
|
||||
/** @return {boolean} Whether it is at the end of the string. */
|
||||
atEnd() {
|
||||
return this.position_ == this.data_.length;
|
||||
}
|
||||
|
||||
const ret = this.readRegex(regex);
|
||||
if (!ret) {
|
||||
return null;
|
||||
} else {
|
||||
return ret[index];
|
||||
|
||||
/**
|
||||
* Reads a line from the parser. This will read but not return the newline.
|
||||
* Returns null at the end.
|
||||
*
|
||||
* @return {?string}
|
||||
*/
|
||||
readLine() {
|
||||
return this.readRegexReturnCapture_(/(.*?)(\n|$)/gm, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a word from the parser. This will not read or return any whitespace
|
||||
* before or after the word (including newlines). Returns null at the end.
|
||||
*
|
||||
* @return {?string}
|
||||
*/
|
||||
readWord() {
|
||||
return this.readRegexReturnCapture_(/[^ \t\n]*/gm, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skips any continuous whitespace from the parser. Returns null at the end.
|
||||
*/
|
||||
skipWhitespace() {
|
||||
this.readRegex(/[ \t]+/gm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the given regular expression from the parser. This requires the
|
||||
* match to be at the current position; there is no need to include a head
|
||||
* anchor.
|
||||
* This requires that the regex have the global flag to be set so that it can
|
||||
* set lastIndex to start the search at the current position. Returns null at
|
||||
* the end or if the regex does not match the current position.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @return {Array.<string>}
|
||||
*/
|
||||
readRegex(regex) {
|
||||
const index = this.indexOf_(regex);
|
||||
if (this.atEnd() || index == null || index.position != this.position_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.position_ += index.length;
|
||||
return index.results;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index info about a regular expression match.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @return {?{position: number, length: number, results: !Array.<string>}}
|
||||
* @private
|
||||
*/
|
||||
shaka.util.TextParser.prototype.indexOf_ = function(regex) {
|
||||
// The global flag is required to use lastIndex.
|
||||
goog.asserts.assert(regex.global, 'global flag should be set');
|
||||
|
||||
regex.lastIndex = this.position_;
|
||||
const results = regex.exec(this.data_);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
position: results.index,
|
||||
length: results[0].length,
|
||||
results: results,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reads a regex from the parser and returns the given capture.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @param {number} index
|
||||
* @return {?string}
|
||||
* @private
|
||||
*/
|
||||
readRegexReturnCapture_(regex, index) {
|
||||
if (this.atEnd()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ret = this.readRegex(regex);
|
||||
if (!ret) {
|
||||
return null;
|
||||
} else {
|
||||
return ret[index];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the index info about a regular expression match.
|
||||
*
|
||||
* @param {!RegExp} regex
|
||||
* @return {?{position: number, length: number, results: !Array.<string>}}
|
||||
* @private
|
||||
*/
|
||||
indexOf_(regex) {
|
||||
// The global flag is required to use lastIndex.
|
||||
goog.asserts.assert(regex.global, 'global flag should be set');
|
||||
|
||||
regex.lastIndex = this.position_;
|
||||
const results = regex.exec(this.data_);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return {
|
||||
position: results.index,
|
||||
length: results[0].length,
|
||||
results: results,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+127
-127
@@ -21,152 +21,152 @@ goog.require('shaka.util.StringUtils');
|
||||
|
||||
|
||||
/**
|
||||
* @namespace shaka.util.Uint8ArrayUtils
|
||||
* @summary A set of Uint8Array utility functions.
|
||||
* @exportDoc
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert a Uint8Array to a base64 string. The output will be standard alphabet
|
||||
* as opposed to base64url safe alphabet.
|
||||
* @param {!Uint8Array} u8Arr
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
|
||||
shaka.util.Uint8ArrayUtils.toStandardBase64 = function(u8Arr) {
|
||||
const bytes = shaka.util.StringUtils.fromCharCode(u8Arr);
|
||||
return btoa(bytes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a Uint8Array to a base64 string. The output will always use the
|
||||
* alternate encoding/alphabet also known as "base64url".
|
||||
* @param {!Uint8Array} arr
|
||||
* @param {boolean=} padding If true, pad the output with equals signs.
|
||||
* Defaults to true.
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.toBase64 = function(arr, padding) {
|
||||
padding = (padding == undefined) ? true : padding;
|
||||
const base64 = shaka.util.Uint8ArrayUtils.toStandardBase64(arr)
|
||||
.replace(/\+/g, '-').replace(/\//g, '_');
|
||||
return padding ? base64 : base64.replace(/[=]*$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a base64 string to a Uint8Array. Accepts either the standard
|
||||
* alphabet or the alternate "base64url" alphabet.
|
||||
* @param {string} str
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.fromBase64 = function(str) {
|
||||
// atob creates a "raw string" where each character is interpreted as a byte.
|
||||
const bytes = window.atob(str.replace(/-/g, '+').replace(/_/g, '/'));
|
||||
const result = new Uint8Array(bytes.length);
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
result[i] = bytes.charCodeAt(i);
|
||||
shaka.util.Uint8ArrayUtils = class {
|
||||
/**
|
||||
* Convert a Uint8Array to a base64 string. The output will be standard
|
||||
* alphabet as opposed to base64url safe alphabet.
|
||||
* @param {!Uint8Array} u8Arr
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
static toStandardBase64(u8Arr) {
|
||||
const bytes = shaka.util.StringUtils.fromCharCode(u8Arr);
|
||||
return btoa(bytes);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convert a hex string to a Uint8Array.
|
||||
* @param {string} str
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.fromHex = function(str) {
|
||||
const arr = new Uint8Array(str.length / 2);
|
||||
for (let i = 0; i < str.length; i += 2) {
|
||||
arr[i / 2] = window.parseInt(str.substr(i, 2), 16);
|
||||
/**
|
||||
* Convert a Uint8Array to a base64 string. The output will always use the
|
||||
* alternate encoding/alphabet also known as "base64url".
|
||||
* @param {!Uint8Array} arr
|
||||
* @param {boolean=} padding If true, pad the output with equals signs.
|
||||
* Defaults to true.
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
static toBase64(arr, padding) {
|
||||
padding = (padding == undefined) ? true : padding;
|
||||
const base64 = shaka.util.Uint8ArrayUtils.toStandardBase64(arr)
|
||||
.replace(/\+/g, '-').replace(/\//g, '_');
|
||||
return padding ? base64 : base64.replace(/[=]*$/, '');
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convert a Uint8Array to a hex string.
|
||||
* @param {!Uint8Array} arr
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.toHex = function(arr) {
|
||||
let hex = '';
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
let value = arr[i].toString(16);
|
||||
if (value.length == 1) {
|
||||
value = '0' + value;
|
||||
/**
|
||||
* Convert a base64 string to a Uint8Array. Accepts either the standard
|
||||
* alphabet or the alternate "base64url" alphabet.
|
||||
* @param {string} str
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
static fromBase64(str) {
|
||||
// atob creates a "raw string" where each character is interpreted as a
|
||||
// byte.
|
||||
const bytes = window.atob(str.replace(/-/g, '+').replace(/_/g, '/'));
|
||||
const result = new Uint8Array(bytes.length);
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
result[i] = bytes.charCodeAt(i);
|
||||
}
|
||||
hex += value;
|
||||
return result;
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compare two Uint8Arrays for equality.
|
||||
* For convenience, this also accepts Arrays, so that one can trivially compare
|
||||
* a Uint8Array to an Array of numbers.
|
||||
*
|
||||
* @param {(Uint8Array|Array.<number>)} arr1
|
||||
* @param {(Uint8Array|Array.<number>)} arr2
|
||||
* @return {boolean}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.equal = function(arr1, arr2) {
|
||||
if (!arr1 && !arr2) {
|
||||
return true;
|
||||
/**
|
||||
* Convert a hex string to a Uint8Array.
|
||||
* @param {string} str
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
static fromHex(str) {
|
||||
const arr = new Uint8Array(str.length / 2);
|
||||
for (let i = 0; i < str.length; i += 2) {
|
||||
arr[i / 2] = window.parseInt(str.substr(i, 2), 16);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
if (!arr1 || !arr2) {
|
||||
return false;
|
||||
|
||||
|
||||
/**
|
||||
* Convert a Uint8Array to a hex string.
|
||||
* @param {!Uint8Array} arr
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
static toHex(arr) {
|
||||
let hex = '';
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
let value = arr[i].toString(16);
|
||||
if (value.length == 1) {
|
||||
value = '0' + value;
|
||||
}
|
||||
hex += value;
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
if (arr1.length != arr2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < arr1.length; ++i) {
|
||||
if (arr1[i] != arr2[i]) {
|
||||
|
||||
|
||||
/**
|
||||
* Compare two Uint8Arrays for equality.
|
||||
* For convenience, this also accepts Arrays, so that one can trivially
|
||||
* compare a Uint8Array to an Array of numbers.
|
||||
*
|
||||
* @param {(Uint8Array|Array.<number>)} arr1
|
||||
* @param {(Uint8Array|Array.<number>)} arr2
|
||||
* @return {boolean}
|
||||
* @export
|
||||
*/
|
||||
static equal(arr1, arr2) {
|
||||
if (!arr1 && !arr2) {
|
||||
return true;
|
||||
}
|
||||
if (!arr1 || !arr2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Concatenate Uint8Arrays.
|
||||
* @param {...!Uint8Array} varArgs
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.concat = function(...varArgs) {
|
||||
let totalLength = 0;
|
||||
for (let i = 0; i < varArgs.length; ++i) {
|
||||
totalLength += varArgs[i].length;
|
||||
if (arr1.length != arr2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < arr1.length; ++i) {
|
||||
if (arr1[i] != arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < varArgs.length; ++i) {
|
||||
result.set(varArgs[i], offset);
|
||||
offset += varArgs[i].length;
|
||||
|
||||
/**
|
||||
* Concatenate Uint8Arrays.
|
||||
* @param {...!Uint8Array} varArgs
|
||||
* @return {!Uint8Array}
|
||||
* @export
|
||||
*/
|
||||
static concat(...varArgs) {
|
||||
let totalLength = 0;
|
||||
for (let i = 0; i < varArgs.length; ++i) {
|
||||
totalLength += varArgs[i].length;
|
||||
}
|
||||
|
||||
const result = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < varArgs.length; ++i) {
|
||||
result.set(varArgs[i], offset);
|
||||
offset += varArgs[i].length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DataView over the given buffer.
|
||||
* @param {!BufferSource} buffer
|
||||
* @return {!DataView}
|
||||
*/
|
||||
shaka.util.Uint8ArrayUtils.toDataView = function(buffer) {
|
||||
if (buffer instanceof ArrayBuffer) {
|
||||
return new DataView(buffer);
|
||||
} else {
|
||||
return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
/**
|
||||
* Creates a DataView over the given buffer.
|
||||
* @param {!BufferSource} buffer
|
||||
* @return {!DataView}
|
||||
*/
|
||||
static toDataView(buffer) {
|
||||
if (buffer instanceof ArrayBuffer) {
|
||||
return new DataView(buffer);
|
||||
} else {
|
||||
return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+294
-295
@@ -22,332 +22,331 @@ goog.require('shaka.util.StringUtils');
|
||||
|
||||
|
||||
/**
|
||||
* @namespace shaka.util.XmlUtils
|
||||
* @summary A set of XML utility functions.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Finds a child XML element.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} name The child XML element's tag name.
|
||||
* @return {Element} The child XML element, or null if a child XML element does
|
||||
* not exist with the given tag name OR if there exists more than one
|
||||
* child XML element with the given tag name.
|
||||
*/
|
||||
shaka.util.XmlUtils.findChild = function(elem, name) {
|
||||
const children = shaka.util.XmlUtils.findChildren(elem, name);
|
||||
if (children.length != 1) {
|
||||
return null;
|
||||
}
|
||||
return children[0];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds a namespace-qualified child XML element.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} ns The child XML element's namespace URI.
|
||||
* @param {string} name The child XML element's local name.
|
||||
* @return {Element} The child XML element, or null if a child XML element does
|
||||
* not exist with the given tag name OR if there exists more than one
|
||||
* child XML element with the given tag name.
|
||||
*/
|
||||
shaka.util.XmlUtils.findChildNS = function(elem, ns, name) {
|
||||
const children = shaka.util.XmlUtils.findChildrenNS(elem, ns, name);
|
||||
if (children.length != 1) {
|
||||
return null;
|
||||
}
|
||||
return children[0];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds child XML elements.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} name The child XML element's tag name.
|
||||
* @return {!Array.<!Element>} The child XML elements.
|
||||
*/
|
||||
shaka.util.XmlUtils.findChildren = function(elem, name) {
|
||||
return Array.prototype.filter.call(elem.childNodes, (child) => {
|
||||
return child instanceof Element && child.tagName == name;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Finds namespace-qualified child XML elements.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} ns The child XML element's namespace URI.
|
||||
* @param {string} name The child XML element's local name.
|
||||
* @return {!Array.<!Element>} The child XML elements.
|
||||
*/
|
||||
shaka.util.XmlUtils.findChildrenNS = function(elem, ns, name) {
|
||||
return Array.prototype.filter.call(elem.childNodes, (child) => {
|
||||
return child instanceof Element && child.localName == name &&
|
||||
child.namespaceURI == ns;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets a namespace-qualified attribute.
|
||||
* @param {!Element} elem The element to get from.
|
||||
* @param {string} ns The namespace URI.
|
||||
* @param {string} name The local name of the attribute.
|
||||
* @return {?string} The attribute's value, or null if not present.
|
||||
*/
|
||||
shaka.util.XmlUtils.getAttributeNS = function(elem, ns, name) {
|
||||
// Some browsers return the empty string when the attribute is missing,
|
||||
// so check if it exists first. See: https://mzl.la/2L7F0UK
|
||||
return elem.hasAttributeNS(ns, name) ? elem.getAttributeNS(ns, name) : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the text contents of a node.
|
||||
* @param {!Node} elem The XML element.
|
||||
* @return {?string} The text contents, or null if there are none.
|
||||
*/
|
||||
shaka.util.XmlUtils.getContents = function(elem) {
|
||||
const isText = (child) => {
|
||||
return child.nodeType == Node.TEXT_NODE ||
|
||||
child.nodeType == Node.CDATA_SECTION_NODE;
|
||||
};
|
||||
if (!Array.prototype.every.call(elem.childNodes, isText)) {
|
||||
return null;
|
||||
shaka.util.XmlUtils = class {
|
||||
/**
|
||||
* Finds a child XML element.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} name The child XML element's tag name.
|
||||
* @return {Element} The child XML element, or null if a child XML element
|
||||
* does not exist with the given tag name OR if there exists more than one
|
||||
* child XML element with the given tag name.
|
||||
*/
|
||||
static findChild(elem, name) {
|
||||
const children = shaka.util.XmlUtils.findChildren(elem, name);
|
||||
if (children.length != 1) {
|
||||
return null;
|
||||
}
|
||||
return children[0];
|
||||
}
|
||||
|
||||
// Read merged text content from all text nodes.
|
||||
return elem.textContent.trim();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an attribute by its name.
|
||||
* @param {!Element} elem The XML element.
|
||||
* @param {string} name The attribute name.
|
||||
* @param {function(string): (T|null)} parseFunction A function that parses
|
||||
* the attribute.
|
||||
* @param {(T|null)=} defaultValue The attribute's default value, if not
|
||||
* specified, the attibute's default value is null.
|
||||
* @return {(T|null)} The parsed attribute on success, or the attribute's
|
||||
* default value if the attribute does not exist or could not be parsed.
|
||||
* @template T
|
||||
*/
|
||||
shaka.util.XmlUtils.parseAttr = function(
|
||||
elem, name, parseFunction, defaultValue = null) {
|
||||
let parsedValue = null;
|
||||
|
||||
const value = elem.getAttribute(name);
|
||||
if (value != null) {
|
||||
parsedValue = parseFunction(value);
|
||||
}
|
||||
return parsedValue == null ? defaultValue : parsedValue;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an XML date string.
|
||||
* @param {string} dateString
|
||||
* @return {?number} The parsed date in seconds on success; otherwise, return
|
||||
* null.
|
||||
*/
|
||||
shaka.util.XmlUtils.parseDate = function(dateString) {
|
||||
if (!dateString) {
|
||||
return null;
|
||||
/**
|
||||
* Finds a namespace-qualified child XML element.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} ns The child XML element's namespace URI.
|
||||
* @param {string} name The child XML element's local name.
|
||||
* @return {Element} The child XML element, or null if a child XML element
|
||||
* does not exist with the given tag name OR if there exists more than one
|
||||
* child XML element with the given tag name.
|
||||
*/
|
||||
static findChildNS(elem, ns, name) {
|
||||
const children = shaka.util.XmlUtils.findChildrenNS(elem, ns, name);
|
||||
if (children.length != 1) {
|
||||
return null;
|
||||
}
|
||||
return children[0];
|
||||
}
|
||||
|
||||
// Times in the manifest should be in UTC. If they don't specify a timezone,
|
||||
// Date.parse() will use the local timezone instead of UTC. So manually add
|
||||
// the timezone if missing ('Z' indicates the UTC timezone).
|
||||
// Format: YYYY-MM-DDThh:mm:ss.ssssss
|
||||
if (/^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/.test(dateString)) {
|
||||
dateString += 'Z';
|
||||
|
||||
/**
|
||||
* Finds child XML elements.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} name The child XML element's tag name.
|
||||
* @return {!Array.<!Element>} The child XML elements.
|
||||
*/
|
||||
static findChildren(elem, name) {
|
||||
return Array.prototype.filter.call(elem.childNodes, (child) => {
|
||||
return child instanceof Element && child.tagName == name;
|
||||
});
|
||||
}
|
||||
|
||||
const result = Date.parse(dateString);
|
||||
return (!isNaN(result) ? Math.floor(result / 1000.0) : null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses an XML duration string.
|
||||
* Negative values are not supported. Years and months are treated as exactly
|
||||
* 365 and 30 days respectively.
|
||||
* @param {string} durationString The duration string, e.g., "PT1H3M43.2S",
|
||||
* which means 1 hour, 3 minutes, and 43.2 seconds.
|
||||
* @return {?number} The parsed duration in seconds on success; otherwise,
|
||||
* return null.
|
||||
* @see {@link http://www.datypic.com/sc/xsd/t-xsd_duration.html}
|
||||
*/
|
||||
shaka.util.XmlUtils.parseDuration = function(durationString) {
|
||||
if (!durationString) {
|
||||
return null;
|
||||
/**
|
||||
* Finds namespace-qualified child XML elements.
|
||||
* @param {!Node} elem The parent XML element.
|
||||
* @param {string} ns The child XML element's namespace URI.
|
||||
* @param {string} name The child XML element's local name.
|
||||
* @return {!Array.<!Element>} The child XML elements.
|
||||
*/
|
||||
static findChildrenNS(elem, ns, name) {
|
||||
return Array.prototype.filter.call(elem.childNodes, (child) => {
|
||||
return child instanceof Element && child.localName == name &&
|
||||
child.namespaceURI == ns;
|
||||
});
|
||||
}
|
||||
|
||||
const re = '^P(?:([0-9]*)Y)?(?:([0-9]*)M)?(?:([0-9]*)D)?' +
|
||||
'(?:T(?:([0-9]*)H)?(?:([0-9]*)M)?(?:([0-9.]*)S)?)?$';
|
||||
const matches = new RegExp(re).exec(durationString);
|
||||
|
||||
if (!matches) {
|
||||
shaka.log.warning('Invalid duration string:', durationString);
|
||||
return null;
|
||||
/**
|
||||
* Gets a namespace-qualified attribute.
|
||||
* @param {!Element} elem The element to get from.
|
||||
* @param {string} ns The namespace URI.
|
||||
* @param {string} name The local name of the attribute.
|
||||
* @return {?string} The attribute's value, or null if not present.
|
||||
*/
|
||||
static getAttributeNS(elem, ns, name) {
|
||||
// Some browsers return the empty string when the attribute is missing,
|
||||
// so check if it exists first. See: https://mzl.la/2L7F0UK
|
||||
return elem.hasAttributeNS(ns, name) ? elem.getAttributeNS(ns, name) : null;
|
||||
}
|
||||
|
||||
// Note: Number(null) == 0 but Number(undefined) == NaN.
|
||||
const years = Number(matches[1] || null);
|
||||
const months = Number(matches[2] || null);
|
||||
const days = Number(matches[3] || null);
|
||||
const hours = Number(matches[4] || null);
|
||||
const minutes = Number(matches[5] || null);
|
||||
const seconds = Number(matches[6] || null);
|
||||
|
||||
// Assume a year always has 365 days and a month always has 30 days.
|
||||
const d = (60 * 60 * 24 * 365) * years +
|
||||
(60 * 60 * 24 * 30) * months +
|
||||
(60 * 60 * 24) * days +
|
||||
(60 * 60) * hours +
|
||||
60 * minutes +
|
||||
seconds;
|
||||
return isFinite(d) ? d : null;
|
||||
};
|
||||
/**
|
||||
* Gets the text contents of a node.
|
||||
* @param {!Node} elem The XML element.
|
||||
* @return {?string} The text contents, or null if there are none.
|
||||
*/
|
||||
static getContents(elem) {
|
||||
const isText = (child) => {
|
||||
return child.nodeType == Node.TEXT_NODE ||
|
||||
child.nodeType == Node.CDATA_SECTION_NODE;
|
||||
};
|
||||
if (!Array.prototype.every.call(elem.childNodes, isText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a range string.
|
||||
* @param {string} rangeString The range string, e.g., "101-9213".
|
||||
* @return {?{start: number, end: number}} The parsed range on success;
|
||||
* otherwise, return null.
|
||||
*/
|
||||
shaka.util.XmlUtils.parseRange = function(rangeString) {
|
||||
const matches = /([0-9]+)-([0-9]+)/.exec(rangeString);
|
||||
|
||||
if (!matches) {
|
||||
return null;
|
||||
// Read merged text content from all text nodes.
|
||||
return elem.textContent.trim();
|
||||
}
|
||||
|
||||
const start = Number(matches[1]);
|
||||
if (!isFinite(start)) {
|
||||
return null;
|
||||
|
||||
/**
|
||||
* Parses an attribute by its name.
|
||||
* @param {!Element} elem The XML element.
|
||||
* @param {string} name The attribute name.
|
||||
* @param {function(string): (T|null)} parseFunction A function that parses
|
||||
* the attribute.
|
||||
* @param {(T|null)=} defaultValue The attribute's default value, if not
|
||||
* specified, the attibute's default value is null.
|
||||
* @return {(T|null)} The parsed attribute on success, or the attribute's
|
||||
* default value if the attribute does not exist or could not be parsed.
|
||||
* @template T
|
||||
*/
|
||||
static parseAttr(
|
||||
elem, name, parseFunction, defaultValue = null) {
|
||||
let parsedValue = null;
|
||||
|
||||
const value = elem.getAttribute(name);
|
||||
if (value != null) {
|
||||
parsedValue = parseFunction(value);
|
||||
}
|
||||
return parsedValue == null ? defaultValue : parsedValue;
|
||||
}
|
||||
|
||||
const end = Number(matches[2]);
|
||||
if (!isFinite(end)) {
|
||||
return null;
|
||||
|
||||
/**
|
||||
* Parses an XML date string.
|
||||
* @param {string} dateString
|
||||
* @return {?number} The parsed date in seconds on success; otherwise, return
|
||||
* null.
|
||||
*/
|
||||
static parseDate(dateString) {
|
||||
if (!dateString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Times in the manifest should be in UTC. If they don't specify a timezone,
|
||||
// Date.parse() will use the local timezone instead of UTC. So manually add
|
||||
// the timezone if missing ('Z' indicates the UTC timezone).
|
||||
// Format: YYYY-MM-DDThh:mm:ss.ssssss
|
||||
if (/^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/.test(dateString)) {
|
||||
dateString += 'Z';
|
||||
}
|
||||
|
||||
const result = Date.parse(dateString);
|
||||
return (!isNaN(result) ? Math.floor(result / 1000.0) : null);
|
||||
}
|
||||
|
||||
return {start: start, end: end};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an XML duration string.
|
||||
* Negative values are not supported. Years and months are treated as exactly
|
||||
* 365 and 30 days respectively.
|
||||
* @param {string} durationString The duration string, e.g., "PT1H3M43.2S",
|
||||
* which means 1 hour, 3 minutes, and 43.2 seconds.
|
||||
* @return {?number} The parsed duration in seconds on success; otherwise,
|
||||
* return null.
|
||||
* @see {@link http://www.datypic.com/sc/xsd/t-xsd_duration.html}
|
||||
*/
|
||||
static parseDuration(durationString) {
|
||||
if (!durationString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed integer on success; otherwise, return null.
|
||||
*/
|
||||
shaka.util.XmlUtils.parseInt = function(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) ? n : null;
|
||||
};
|
||||
const re = '^P(?:([0-9]*)Y)?(?:([0-9]*)M)?(?:([0-9]*)D)?' +
|
||||
'(?:T(?:([0-9]*)H)?(?:([0-9]*)M)?(?:([0-9.]*)S)?)?$';
|
||||
const matches = new RegExp(re).exec(durationString);
|
||||
|
||||
if (!matches) {
|
||||
shaka.log.warning('Invalid duration string:', durationString);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a positive integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed positive integer on success; otherwise,
|
||||
* return null.
|
||||
*/
|
||||
shaka.util.XmlUtils.parsePositiveInt = function(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) && (n > 0) ? n : null;
|
||||
};
|
||||
// Note: Number(null) == 0 but Number(undefined) == NaN.
|
||||
const years = Number(matches[1] || null);
|
||||
const months = Number(matches[2] || null);
|
||||
const days = Number(matches[3] || null);
|
||||
const hours = Number(matches[4] || null);
|
||||
const minutes = Number(matches[5] || null);
|
||||
const seconds = Number(matches[6] || null);
|
||||
|
||||
|
||||
/**
|
||||
* Parses a non-negative integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed non-negative integer on success; otherwise,
|
||||
* return null.
|
||||
*/
|
||||
shaka.util.XmlUtils.parseNonNegativeInt = function(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) && (n >= 0) ? n : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a floating point number.
|
||||
* @param {string} floatString The floating point number string.
|
||||
* @return {?number} The parsed floating point number on success; otherwise,
|
||||
* return null. May return -Infinity or Infinity.
|
||||
*/
|
||||
shaka.util.XmlUtils.parseFloat = function(floatString) {
|
||||
const n = Number(floatString);
|
||||
return !isNaN(n) ? n : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate a division expressed as a string.
|
||||
* @param {string} exprString
|
||||
* The expression to evaluate, e.g. "200/2". Can also be a single number.
|
||||
* @return {?number} The evaluated expression as floating point number on
|
||||
* success; otherwise return null.
|
||||
*/
|
||||
shaka.util.XmlUtils.evalDivision = function(exprString) {
|
||||
let res;
|
||||
let n;
|
||||
if ((res = exprString.match(/^(\d+)\/(\d+)$/))) {
|
||||
n = Number(res[1]) / Number(res[2]);
|
||||
} else {
|
||||
n = Number(exprString);
|
||||
// Assume a year always has 365 days and a month always has 30 days.
|
||||
const d = (60 * 60 * 24 * 365) * years +
|
||||
(60 * 60 * 24 * 30) * months +
|
||||
(60 * 60 * 24) * days +
|
||||
(60 * 60) * hours +
|
||||
60 * minutes +
|
||||
seconds;
|
||||
return isFinite(d) ? d : null;
|
||||
}
|
||||
return !isNaN(n) ? n : null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a string and return the resulting root element if
|
||||
* it was valid XML.
|
||||
* @param {string} xmlString
|
||||
* @param {string} expectedRootElemName
|
||||
* @return {Element}
|
||||
*/
|
||||
shaka.util.XmlUtils.parseXmlString = function(xmlString, expectedRootElemName) {
|
||||
const parser = new DOMParser();
|
||||
let rootElem = null;
|
||||
let xml = null;
|
||||
try {
|
||||
xml = parser.parseFromString(xmlString, 'text/xml');
|
||||
} catch (exception) {}
|
||||
if (xml) {
|
||||
// The top-level element in the loaded xml should have the
|
||||
// same type as the element linking.
|
||||
if (xml.documentElement.tagName == expectedRootElemName) {
|
||||
rootElem = xml.documentElement;
|
||||
/**
|
||||
* Parses a range string.
|
||||
* @param {string} rangeString The range string, e.g., "101-9213".
|
||||
* @return {?{start: number, end: number}} The parsed range on success;
|
||||
* otherwise, return null.
|
||||
*/
|
||||
static parseRange(rangeString) {
|
||||
const matches = /([0-9]+)-([0-9]+)/.exec(rangeString);
|
||||
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const start = Number(matches[1]);
|
||||
if (!isFinite(start)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const end = Number(matches[2]);
|
||||
if (!isFinite(end)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {start: start, end: end};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses an integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed integer on success; otherwise, return null.
|
||||
*/
|
||||
static parseInt(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) ? n : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a positive integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed positive integer on success; otherwise,
|
||||
* return null.
|
||||
*/
|
||||
static parsePositiveInt(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) && (n > 0) ? n : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a non-negative integer.
|
||||
* @param {string} intString The integer string.
|
||||
* @return {?number} The parsed non-negative integer on success; otherwise,
|
||||
* return null.
|
||||
*/
|
||||
static parseNonNegativeInt(intString) {
|
||||
const n = Number(intString);
|
||||
return (n % 1 === 0) && (n >= 0) ? n : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses a floating point number.
|
||||
* @param {string} floatString The floating point number string.
|
||||
* @return {?number} The parsed floating point number on success; otherwise,
|
||||
* return null. May return -Infinity or Infinity.
|
||||
*/
|
||||
static parseFloat(floatString) {
|
||||
const n = Number(floatString);
|
||||
return !isNaN(n) ? n : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate a division expressed as a string.
|
||||
* @param {string} exprString
|
||||
* The expression to evaluate, e.g. "200/2". Can also be a single number.
|
||||
* @return {?number} The evaluated expression as floating point number on
|
||||
* success; otherwise return null.
|
||||
*/
|
||||
static evalDivision(exprString) {
|
||||
let res;
|
||||
let n;
|
||||
if ((res = exprString.match(/^(\d+)\/(\d+)$/))) {
|
||||
n = Number(res[1]) / Number(res[2]);
|
||||
} else {
|
||||
n = Number(exprString);
|
||||
}
|
||||
return !isNaN(n) ? n : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse a string and return the resulting root element if
|
||||
* it was valid XML.
|
||||
* @param {string} xmlString
|
||||
* @param {string} expectedRootElemName
|
||||
* @return {Element}
|
||||
*/
|
||||
static parseXmlString(xmlString, expectedRootElemName) {
|
||||
const parser = new DOMParser();
|
||||
let rootElem = null;
|
||||
let xml = null;
|
||||
try {
|
||||
xml = parser.parseFromString(xmlString, 'text/xml');
|
||||
} catch (exception) {}
|
||||
if (xml) {
|
||||
// The top-level element in the loaded xml should have the
|
||||
// same type as the element linking.
|
||||
if (xml.documentElement.tagName == expectedRootElemName) {
|
||||
rootElem = xml.documentElement;
|
||||
}
|
||||
}
|
||||
if (rootElem && rootElem.getElementsByTagName('parsererror').length > 0) {
|
||||
return null;
|
||||
} // It had a parser error in it.
|
||||
|
||||
return rootElem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse some UTF8 data and return the resulting root element if
|
||||
* it was valid XML.
|
||||
* @param {ArrayBuffer} data
|
||||
* @param {string} expectedRootElemName
|
||||
* @return {Element}
|
||||
*/
|
||||
static parseXml(data, expectedRootElemName) {
|
||||
try {
|
||||
const string = shaka.util.StringUtils.fromUTF8(data);
|
||||
return shaka.util.XmlUtils.parseXmlString(string, expectedRootElemName);
|
||||
} catch (exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (rootElem && rootElem.getElementsByTagName('parsererror').length > 0) {
|
||||
return null;
|
||||
} // It had a parser error in it.
|
||||
|
||||
return rootElem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse some UTF8 data and return the resulting root element if
|
||||
* it was valid XML.
|
||||
* @param {ArrayBuffer} data
|
||||
* @param {string} expectedRootElemName
|
||||
* @return {Element}
|
||||
*/
|
||||
shaka.util.XmlUtils.parseXml = function(data, expectedRootElemName) {
|
||||
try {
|
||||
const string = shaka.util.StringUtils.fromUTF8(data);
|
||||
return shaka.util.XmlUtils.parseXmlString(string, expectedRootElemName);
|
||||
} catch (exception) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user