diff --git a/build/conformance.textproto b/build/conformance.textproto index 643a3bd49..3657d6c34 100644 --- a/build/conformance.textproto +++ b/build/conformance.textproto @@ -138,6 +138,7 @@ requirement: { value: 'TypedArray.prototype.buffer' error_message: 'Using "TypedArray.buffer" is not safe since it doesn\'t ' 'handle sub-arrays' + whitelist_regexp: 'lib/util/buffer_utils.js' whitelist_regexp: 'lib/util/object_utils.js' whitelist_regexp: 'lib/util/uint8array_utils.js' whitelist_regexp: 'test/' diff --git a/build/types/core b/build/types/core index cf69c8971..1d040a7f2 100644 --- a/build/types/core +++ b/build/types/core @@ -55,6 +55,7 @@ +../../lib/util/abortable_operation.js +../../lib/util/array_utils.js ++../../lib/util/buffer_utils.js +../../lib/util/config_utils.js +../../lib/util/data_view_reader.js +../../lib/util/delayed_tick.js diff --git a/lib/dash/content_protection.js b/lib/dash/content_protection.js index 7dae3a876..aab0eae58 100644 --- a/lib/dash/content_protection.js +++ b/lib/dash/content_protection.js @@ -19,6 +19,7 @@ goog.provide('shaka.dash.ContentProtection'); goog.require('goog.asserts'); goog.require('shaka.log'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.Uint8ArrayUtils'); @@ -215,7 +216,7 @@ shaka.dash.ContentProtection = class { return []; } - const buffer = shaka.util.Uint8ArrayUtils.unsafeGetArrayBuffer(view); + const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer(view); const recordValue = new Uint8Array( buffer, view.byteOffset + byteOffset, byteLength); @@ -245,7 +246,7 @@ shaka.dash.ContentProtection = class { */ static parseMsPro_(data) { let byteOffset = 0; - const view = shaka.util.Uint8ArrayUtils.toDataView(data); + const view = shaka.util.BufferUtils.toDataView(data); // First 4 bytes is the PRO length (DWORD) const byteLength = view.getUint32(byteOffset, true /* littleEndian */); diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 6fdb994fd..810a2f1e2 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -37,6 +37,7 @@ goog.require('shaka.net.DataUriPlugin'); goog.require('shaka.net.NetworkingEngine'); goog.require('shaka.text.TextEngine'); goog.require('shaka.util.ArrayUtils'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); goog.require('shaka.util.Functional'); @@ -1735,7 +1736,7 @@ shaka.hls.HlsParser = class { */ getStartTimeFromTsSegment_(data) { const reader = new shaka.util.DataViewReader( - shaka.util.Uint8ArrayUtils.toDataView(data), + shaka.util.BufferUtils.toDataView(data), shaka.util.DataViewReader.Endianness.BIG_ENDIAN); const fail = () => { diff --git a/lib/media/drm_engine.js b/lib/media/drm_engine.js index 3ede7a354..7e35fb957 100644 --- a/lib/media/drm_engine.js +++ b/lib/media/drm_engine.js @@ -20,6 +20,7 @@ goog.provide('shaka.media.DrmEngine'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.net.NetworkingEngine'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Destroyer'); goog.require('shaka.util.Error'); goog.require('shaka.util.EventManager'); @@ -524,14 +525,14 @@ shaka.media.DrmEngine = class { */ newInitData(initDataType, initData) { // Aliases: - const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + const BufferUtils = shaka.util.BufferUtils; // Suppress duplicate init data. // Note that some init data are extremely large and can't portably be used // as keys in a dictionary. const metadatas = this.activeSessions_.values(); for (const metadata of metadatas) { - if (Uint8ArrayUtils.equal(initData, metadata.initData)) { + if (BufferUtils.equal(initData, metadata.initData)) { shaka.log.debug('Ignoring duplicate init data.'); return; } @@ -1258,7 +1259,7 @@ shaka.media.DrmEngine = class { // one. Try to detect this and compensate: if (typeof keyId == 'string') { const tmp = keyId; - keyId = /** @type {ArrayBuffer} */(status); + keyId = /** @type {!ArrayBuffer} */(status); status = /** @type {string} */(tmp); } @@ -1276,7 +1277,7 @@ shaka.media.DrmEngine = class { keyId.byteLength == 16 && !shaka.util.Platform.isTizen()) { // Read out some fields in little-endian: - const dataView = shaka.util.Uint8ArrayUtils.toDataView(keyId); + const dataView = shaka.util.BufferUtils.toDataView(keyId); const part0 = dataView.getUint32(0, true /* LE */); const part1 = dataView.getUint16(4, true /* LE */); const part2 = dataView.getUint16(6, true /* LE */); @@ -1722,12 +1723,12 @@ shaka.media.DrmEngine = class { return true; } return a.initDataType == b.initDataType && - shaka.util.Uint8ArrayUtils.equal(a.initData, b.initData); + shaka.util.BufferUtils.equal(a.initData, b.initData); }; for (const drmInfo of drmInfos) { // Aliases: - const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + const BufferUtils = shaka.util.BufferUtils; // Build an array of unique license servers. if (!licenseServers.includes(drmInfo.licenseServerUri)) { @@ -1737,7 +1738,7 @@ shaka.media.DrmEngine = class { // Build an array of unique server certs. if (drmInfo.serverCertificate) { const found = serverCerts.some( - (cert) => Uint8ArrayUtils.equal(cert, drmInfo.serverCertificate)); + (cert) => BufferUtils.equal(cert, drmInfo.serverCertificate)); if (!found) { serverCerts.push(drmInfo.serverCertificate); } diff --git a/lib/media/webm_segment_index_parser.js b/lib/media/webm_segment_index_parser.js index eeeac217c..759b49b67 100644 --- a/lib/media/webm_segment_index_parser.js +++ b/lib/media/webm_segment_index_parser.js @@ -21,10 +21,10 @@ goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.media.InitSegmentReference'); goog.require('shaka.media.SegmentReference'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.EbmlElement'); goog.require('shaka.util.EbmlParser'); goog.require('shaka.util.Error'); -goog.require('shaka.util.Uint8ArrayUtils'); shaka.media.WebmSegmentIndexParser = class { @@ -47,7 +47,7 @@ shaka.media.WebmSegmentIndexParser = class { const tuple = shaka.media.WebmSegmentIndexParser.parseWebmContainer_(initData); const parser = new shaka.util.EbmlParser( - shaka.util.Uint8ArrayUtils.toDataView(cuesData)); + shaka.util.BufferUtils.toDataView(cuesData)); const cuesElement = parser.parseElement(); if (cuesElement.id != shaka.media.WebmSegmentIndexParser.CUES_ID) { shaka.log.error('Not a Cues element.'); @@ -76,7 +76,7 @@ shaka.media.WebmSegmentIndexParser = class { */ static parseWebmContainer_(initData) { const parser = new shaka.util.EbmlParser( - shaka.util.Uint8ArrayUtils.toDataView(initData)); + shaka.util.BufferUtils.toDataView(initData)); // Check that the WebM container data starts with the EBML header, but // skip its contents. diff --git a/lib/net/networking_engine.js b/lib/net/networking_engine.js index acda13bc4..893d37d05 100644 --- a/lib/net/networking_engine.js +++ b/lib/net/networking_engine.js @@ -22,6 +22,7 @@ goog.require('goog.Uri'); goog.require('goog.asserts'); goog.require('shaka.net.Backoff'); goog.require('shaka.util.AbortableOperation'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.FakeEventTarget'); @@ -329,7 +330,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { // better to always pass a Uint8Array so they know what they are // getting; but we shouldn't use ArrayBuffer since that would require // copying buffers if this is a partial view. - request.body = shaka.util.Uint8ArrayUtils.toArrayBuffer(request.body); + request.body = shaka.util.BufferUtils.toArrayBuffer(request.body); } return requestFilter(type, request); }); @@ -498,7 +499,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget { const resp = responseAndGotProgress.response; if (resp.data) { // TODO: See TODO in filterRequest_. - resp.data = shaka.util.Uint8ArrayUtils.toArrayBuffer(resp.data); + resp.data = shaka.util.BufferUtils.toArrayBuffer(resp.data); } return responseFilter(type, resp); }); diff --git a/lib/polyfill/patchedmediakeys_apple.js b/lib/polyfill/patchedmediakeys_apple.js index 21df4c82e..4f530cf8a 100644 --- a/lib/polyfill/patchedmediakeys_apple.js +++ b/lib/polyfill/patchedmediakeys_apple.js @@ -20,11 +20,11 @@ goog.provide('shaka.polyfill.PatchedMediaKeysApple'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.polyfill'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.EventManager'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.FakeEventTarget'); goog.require('shaka.util.PublicPromise'); -goog.require('shaka.util.Uint8ArrayUtils'); /** @@ -60,7 +60,7 @@ shaka.polyfill.PatchedMediaKeysApple = class { // exceptions on unsupported browsers. This particular fake key ID was // suggested in w3c/encrypted-media#32. PatchedMediaKeysApple.MediaKeyStatusMap.KEY_ID_ = - shaka.util.Uint8ArrayUtils.toArrayBuffer(new Uint8Array([0])); + shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0])); // Delete mediaKeys to work around strict mode compatibility issues. // eslint-disable-next-line no-restricted-syntax @@ -172,7 +172,7 @@ shaka.polyfill.PatchedMediaKeysApple = class { const event2 = new Event('encrypted'); // TODO: validate this initDataType against the unprefixed version event2.initDataType = 'cenc'; - event2.initData = shaka.util.Uint8ArrayUtils.toArrayBuffer(event.initData); + event2.initData = shaka.util.BufferUtils.toArrayBuffer(event.initData); this.dispatchEvent(event2); } @@ -571,7 +571,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession = const event2 = new shaka.util.FakeEvent('message', { messageType: isNew ? 'license-request' : 'license-renewal', - message: shaka.util.Uint8ArrayUtils.toArrayBuffer(event.message), + message: shaka.util.BufferUtils.toArrayBuffer(event.message), }); this.dispatchEvent(event2); @@ -713,8 +713,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class { has(keyId) { const fakeKeyId = shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap.KEY_ID_; - if (this.status_ && shaka.util.Uint8ArrayUtils.equal( - new Uint8Array(keyId), new Uint8Array(fakeKeyId))) { + if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) { return true; } return false; diff --git a/lib/polyfill/patchedmediakeys_ms.js b/lib/polyfill/patchedmediakeys_ms.js index e15761117..6f48e4fbf 100644 --- a/lib/polyfill/patchedmediakeys_ms.js +++ b/lib/polyfill/patchedmediakeys_ms.js @@ -20,12 +20,12 @@ goog.provide('shaka.polyfill.PatchedMediaKeysMs'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.polyfill'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.EventManager'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.FakeEventTarget'); goog.require('shaka.util.Pssh'); goog.require('shaka.util.PublicPromise'); -goog.require('shaka.util.Uint8ArrayUtils'); /** @@ -54,7 +54,7 @@ shaka.polyfill.PatchedMediaKeysMs = class { // exceptions on unsupported browsers. This particular fake key ID was // suggested in w3c/encrypted-media#32. PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_ = - shaka.util.Uint8ArrayUtils.toArrayBuffer(new Uint8Array([0])); + shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0])); // Delete mediaKeys to work around strict mode compatibility issues. // eslint-disable-next-line no-restricted-syntax @@ -157,10 +157,11 @@ shaka.polyfill.PatchedMediaKeysMs = class { } // Dedupe psshData. + /** @type {!Array.} */ const dedupedInitDatas = []; for (const initData of unfilteredInitDatas) { const found = dedupedInitDatas.some((x) => { - return shaka.util.Uint8ArrayUtils.equal(x, initData); + return shaka.util.BufferUtils.equal(x, initData); }); if (!found) { @@ -597,7 +598,7 @@ shaka.polyfill.PatchedMediaKeysMs.MediaKeySession = const event2 = new shaka.util.FakeEvent('message', { messageType: isNew ? 'license-request' : 'license-renewal', - message: shaka.util.Uint8ArrayUtils.toArrayBuffer(event.message), + message: shaka.util.BufferUtils.toArrayBuffer(event.message), }); this.dispatchEvent(event2); @@ -749,8 +750,7 @@ shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap = class { has(keyId) { const fakeKeyId = shaka.polyfill.PatchedMediaKeysMs.MediaKeyStatusMap.KEY_ID_; - if (this.status_ && shaka.util.Uint8ArrayUtils.equal( - new Uint8Array(keyId), new Uint8Array(fakeKeyId))) { + if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) { return true; } return false; diff --git a/lib/polyfill/patchedmediakeys_webkit.js b/lib/polyfill/patchedmediakeys_webkit.js index cea3afa6b..9c71f07f6 100644 --- a/lib/polyfill/patchedmediakeys_webkit.js +++ b/lib/polyfill/patchedmediakeys_webkit.js @@ -20,6 +20,7 @@ goog.provide('shaka.polyfill.PatchedMediaKeysWebkit'); goog.require('goog.asserts'); goog.require('shaka.log'); goog.require('shaka.polyfill'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.EventManager'); goog.require('shaka.util.FakeEvent'); goog.require('shaka.util.FakeEventTarget'); @@ -69,7 +70,7 @@ shaka.polyfill.PatchedMediaKeysWebkit = class { // exceptions on unsupported browsers. This particular fake key ID was // suggested in w3c/encrypted-media#32. PatchedMediaKeysWebkit.MediaKeyStatusMap.KEY_ID_ = - shaka.util.Uint8ArrayUtils.toArrayBuffer(new Uint8Array([0])); + shaka.util.BufferUtils.toArrayBuffer(new Uint8Array([0])); // Install patches. navigator.requestMediaKeySystemAccess = @@ -944,8 +945,7 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap = class { has(keyId) { const fakeKeyId = shaka.polyfill.PatchedMediaKeysWebkit.MediaKeyStatusMap.KEY_ID_; - if (this.status_ && shaka.util.Uint8ArrayUtils.equal( - new Uint8Array(keyId), new Uint8Array(fakeKeyId))) { + if (this.status_ && shaka.util.BufferUtils.equal(keyId, fakeKeyId)) { return true; } return false; diff --git a/lib/text/mp4_vtt_parser.js b/lib/text/mp4_vtt_parser.js index b722a8b58..3fd017625 100644 --- a/lib/text/mp4_vtt_parser.js +++ b/lib/text/mp4_vtt_parser.js @@ -22,6 +22,7 @@ goog.require('shaka.log'); goog.require('shaka.text.Cue'); goog.require('shaka.text.TextEngine'); goog.require('shaka.text.VttTextParser'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); goog.require('shaka.util.Functional'); @@ -213,7 +214,7 @@ shaka.text.Mp4VttParser = class { /** @type {!shaka.util.DataViewReader} */ const reader = new shaka.util.DataViewReader( - shaka.util.Uint8ArrayUtils.toDataView(rawPayload), + shaka.util.BufferUtils.toDataView(rawPayload), shaka.util.DataViewReader.Endianness.BIG_ENDIAN); for (const presentation of presentations) { diff --git a/lib/util/buffer_utils.js b/lib/util/buffer_utils.js new file mode 100644 index 000000000..44c9d0b59 --- /dev/null +++ b/lib/util/buffer_utils.js @@ -0,0 +1,121 @@ +/** + * @license + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +goog.provide('shaka.util.BufferUtils'); + +goog.require('shaka.util.Iterables'); + + +/** + * @summary A set of BufferSource utility functions. + * @exportDoc + */ +shaka.util.BufferUtils = class { + /** + * Compare two buffers for equality. For buffers of different types, this + * compares the underlying buffers as binary data. + * + * @param {?BufferSource} arr1 + * @param {?BufferSource} arr2 + * @return {boolean} + * @export + */ + static equal(arr1, arr2) { + const BufferUtils = shaka.util.BufferUtils; + if (!arr1 && !arr2) { + return true; + } + if (!arr1 || !arr2) { + return false; + } + if (arr1.byteLength != arr2.byteLength) { + return false; + } + + // Quickly check if these are views of the same buffer. An ArrayBuffer can + // be passed but doesn't have a byteOffset field, so default to 0. + if (BufferUtils.unsafeGetArrayBuffer(arr1) == + BufferUtils.unsafeGetArrayBuffer(arr2) && + (arr1.byteOffset || 0) == (arr2.byteOffset || 0)) { + return true; + } + + const uint8A = new Uint8Array(arr1); + const uint8B = new Uint8Array(arr2); + for (const i of shaka.util.Iterables.range(arr1.byteLength)) { + if (uint8A[i] != uint8B[i]) { + return false; + } + } + return true; + } + + /** + * Gets the underlying ArrayBuffer of the given view. The caller needs to + * ensure it uses the "byteOffset" and "byteLength" fields of the view to + * only use the same "view" of the data. + * + * @param {BufferSource} view + * @return {!ArrayBuffer} + * @export + */ + static unsafeGetArrayBuffer(view) { + if (view instanceof ArrayBuffer) { + return view; + } else { + return view.buffer; + } + } + + /** + * Gets an ArrayBuffer that contains the data from the given TypedArray. Note + * this will allocate a new ArrayBuffer if the object is a partial view of + * the data. + * + * @param {!BufferSource} view + * @return {!ArrayBuffer} + * @export + */ + static toArrayBuffer(view) { + if (view instanceof ArrayBuffer) { + return view; + } else { + if (view.byteOffset == 0 && view.byteLength == view.buffer.byteLength) { + // This is a TypedArray over the whole buffer. + return view.buffer; + } + // This is a "view" on the buffer. Create a new buffer that only contains + // the data. Note that since this isn't an ArrayBuffer, the "new" call + // will allocate a new buffer to hold the copy. + return new Uint8Array(view).buffer; + } + } + + /** + * Creates a DataView over the given buffer. + * @param {BufferSource} buffer + * @return {!DataView} + * @export + */ + static toDataView(buffer) { + if (buffer instanceof ArrayBuffer) { + return new DataView(buffer); + } else { + return new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } + } +}; diff --git a/lib/util/data_view_reader.js b/lib/util/data_view_reader.js index 3785358ec..3dbba1f57 100644 --- a/lib/util/data_view_reader.js +++ b/lib/util/data_view_reader.js @@ -18,6 +18,7 @@ goog.provide('shaka.util.DataViewReader'); goog.require('goog.asserts'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.StringUtils'); @@ -198,12 +199,12 @@ shaka.util.DataViewReader = class { } const value = new Uint8Array( - shaka.util.Uint8ArrayUtils.unsafeGetArrayBuffer(this.dataView_), + shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_), this.dataView_.byteOffset + this.position_, bytes); this.position_ += bytes; - return new Uint8Array(value); + return value; } @@ -270,7 +271,7 @@ shaka.util.DataViewReader = class { } const ret = new Uint8Array( - shaka.util.Uint8ArrayUtils.unsafeGetArrayBuffer(this.dataView_), + shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_), this.dataView_.byteOffset + start, this.position_ - start); // Skip string termination. diff --git a/lib/util/ebml_parser.js b/lib/util/ebml_parser.js index 6b186ba06..4bfbf3af5 100644 --- a/lib/util/ebml_parser.js +++ b/lib/util/ebml_parser.js @@ -19,10 +19,10 @@ goog.provide('shaka.util.EbmlElement'); goog.provide('shaka.util.EbmlParser'); goog.require('goog.asserts'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Error'); goog.require('shaka.util.Iterables'); -goog.require('shaka.util.Uint8ArrayUtils'); /** @@ -83,7 +83,7 @@ shaka.util.EbmlParser = class { this.dataView_.byteLength - this.reader_.getPosition(); const dataView = new DataView( - shaka.util.Uint8ArrayUtils.unsafeGetArrayBuffer(this.dataView_), + shaka.util.BufferUtils.unsafeGetArrayBuffer(this.dataView_), this.dataView_.byteOffset + this.reader_.getPosition(), elementSize); @@ -204,10 +204,10 @@ shaka.util.EbmlParser = class { */ static isDynamicSizeValue_(vint) { const EbmlParser = shaka.util.EbmlParser; - const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; + const BufferUtils = shaka.util.BufferUtils; for (const dynamicSizeConst of EbmlParser.DYNAMIC_SIZES) { - if (Uint8ArrayUtils.equal(vint, dynamicSizeConst)) { + if (BufferUtils.equal(vint, new Uint8Array(dynamicSizeConst))) { return true; } } diff --git a/lib/util/fairplay_utils.js b/lib/util/fairplay_utils.js index a47c5ec6b..afd243563 100644 --- a/lib/util/fairplay_utils.js +++ b/lib/util/fairplay_utils.js @@ -18,7 +18,7 @@ goog.provide('shaka.util.FairPlayUtils'); goog.require('goog.Uri'); -goog.require('shaka.util.Uint8ArrayUtils'); +goog.require('shaka.util.BufferUtils'); /** @@ -36,7 +36,7 @@ shaka.util.FairPlayUtils = class { */ static defaultGetContentId(initData) { const uint8 = new Uint8Array(initData); - const dataview = shaka.util.Uint8ArrayUtils.toDataView(uint8); + const dataview = shaka.util.BufferUtils.toDataView(uint8); // The first part is a 4 byte little-endian int, which is the length of // the second part. const length = dataview.getUint32( @@ -106,7 +106,7 @@ shaka.util.FairPlayUtils = class { }; /** @param {!Uint8Array} array */ const appendWithLength = (array) => { - const view = shaka.util.Uint8ArrayUtils.toDataView(rebuiltInitData); + const view = shaka.util.BufferUtils.toDataView(rebuiltInitData); const value = array.byteLength; view.setUint32(offset, value, /* littleEndian= */ true); offset += 4; diff --git a/lib/util/mp4_parser.js b/lib/util/mp4_parser.js index 4713500d1..e615ff9e0 100644 --- a/lib/util/mp4_parser.js +++ b/lib/util/mp4_parser.js @@ -19,9 +19,9 @@ goog.provide('shaka.util.Mp4Parser'); goog.require('goog.asserts'); goog.require('shaka.log'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.DataViewReader'); goog.require('shaka.util.Iterables'); -goog.require('shaka.util.Uint8ArrayUtils'); /** @@ -94,7 +94,7 @@ shaka.util.Mp4Parser = class { */ parse(data, partialOkay) { const reader = new shaka.util.DataViewReader( - shaka.util.Uint8ArrayUtils.toDataView(data), + shaka.util.BufferUtils.toDataView(data), shaka.util.DataViewReader.Endianness.BIG_ENDIAN); this.done_ = false; @@ -156,7 +156,7 @@ shaka.util.Mp4Parser = class { (payloadSize > 0) ? reader.readBytes(payloadSize) : new Uint8Array(0); const payloadReader = new shaka.util.DataViewReader( - shaka.util.Uint8ArrayUtils.toDataView(payload), + shaka.util.BufferUtils.toDataView(payload), shaka.util.DataViewReader.Endianness.BIG_ENDIAN); /** @type {shaka.extern.ParsedBox} */ diff --git a/lib/util/string_utils.js b/lib/util/string_utils.js index 3d094184e..c6fde34aa 100644 --- a/lib/util/string_utils.js +++ b/lib/util/string_utils.js @@ -19,6 +19,7 @@ goog.provide('shaka.util.StringUtils'); goog.require('goog.asserts'); goog.require('shaka.log'); +goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.Error'); goog.require('shaka.util.Iterables'); @@ -96,7 +97,7 @@ shaka.util.StringUtils = class { // Use a DataView to ensure correct endianness. const length = Math.floor(data.byteLength / 2); const arr = new Uint16Array(length); - const dataView = shaka.util.Uint8ArrayUtils.toDataView(data); + const dataView = shaka.util.BufferUtils.toDataView(data); for (const i of shaka.util.Iterables.range(length)) { arr[i] = dataView.getUint16(i * 2, littleEndian); } @@ -173,7 +174,7 @@ shaka.util.StringUtils = class { for (const {i, item} of enumerate(utf8)) { result[i] = item.charCodeAt(0); } - return shaka.util.Uint8ArrayUtils.toArrayBuffer(result); + return shaka.util.BufferUtils.toArrayBuffer(result); } diff --git a/lib/util/uint8array_utils.js b/lib/util/uint8array_utils.js index 549fa9095..676d24cdd 100644 --- a/lib/util/uint8array_utils.js +++ b/lib/util/uint8array_utils.js @@ -109,35 +109,6 @@ shaka.util.Uint8ArrayUtils = class { } - /** - * 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.)} arr1 - * @param {(Uint8Array|Array.)} arr2 - * @return {boolean} - * @export - */ - static equal(arr1, arr2) { - if (!arr1 && !arr2) { - return true; - } - if (!arr1 || !arr2) { - return false; - } - if (arr1.length != arr2.length) { - return false; - } - for (const i of shaka.util.Iterables.range(arr1.length)) { - if (arr1[i] != arr2[i]) { - return false; - } - } - return true; - } - - /** * Concatenate Uint8Arrays. * @param {...!Uint8Array} varArgs @@ -158,60 +129,4 @@ shaka.util.Uint8ArrayUtils = class { } return result; } - - - /** - * 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); - } - } - - - /** - * Gets the underlying ArrayBuffer of the given view. The caller needs to - * ensure it uses the "byteOffset" and "byteLength" fields of the view to - * only use the same "view" of the data. - * - * @param {!BufferSource} view - * @return {!ArrayBuffer} - */ - static unsafeGetArrayBuffer(view) { - if (view instanceof ArrayBuffer) { - return view; - } else { - return view.buffer; - } - } - - - /** - * Gets an ArrayBuffer that contains the data from the given TypedArray. Note - * this will allocate a new ArrayBuffer if the object is a partial view of - * the data. - * - * @param {!BufferSource} view - * @return {!ArrayBuffer} - */ - static toArrayBuffer(view) { - if (view instanceof ArrayBuffer) { - return view; - } else { - if (view.byteOffset == 0 && view.byteLength == view.buffer.byteLength) { - // This is a TypedArray over the whole buffer. - return view.buffer; - } - // This is a "view" on the buffer. Create a new buffer that only contains - // the data. - const ret = new Uint8Array(view.byteLength); - ret.set(view); - return ret.buffer; - } - } }; diff --git a/test/util/buffer_utils_unit.js b/test/util/buffer_utils_unit.js new file mode 100644 index 000000000..a5ef3f65b --- /dev/null +++ b/test/util/buffer_utils_unit.js @@ -0,0 +1,89 @@ +/** + * @license + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +describe('BufferUtils', () => { + const BufferUtils = shaka.util.BufferUtils; + + describe('equal', () => { + it('allows null', () => { + const buffer = new Uint8Array([0]); + expect(BufferUtils.equal(buffer, null)).toBe(false); + expect(BufferUtils.equal(null, buffer)).toBe(false); + expect(BufferUtils.equal(null, null)).toBe(true); + }); + + it('checks length', () => { + const a = new Uint8Array([0]); + const b = new Uint8Array([0, 1]); + expect(BufferUtils.equal(a, b)).toBe(false); + }); + + it('compares values', () => { + const a = new Uint8Array([0, 1]); + const b = new Uint8Array([0, 1]); + const c = new Uint8Array([0, 2]); + expect(a).not.toBe(b); + expect(a.buffer).not.toBe(b.buffer); + expect(BufferUtils.equal(a, a)).toBe(true); + expect(BufferUtils.equal(a, b)).toBe(true); + expect(BufferUtils.equal(a, c)).toBe(false); + }); + + // TODO(modmaker): Fix comparisons of different types. + xit('compares different types', () => { + const a = new Uint8Array([0, 1, 2, 3]); + const b = new DataView(new ArrayBuffer(4)); + b.setUint16(0, 0x0001, false); + b.setUint16(2, 0x0203, false); + expect(BufferUtils.equal(a, b)).toBe(true); + }); + + it('compares with same buffer', () => { + const a = new Uint8Array([0, 1, 2, 3]); + const b = new Uint16Array(a.buffer); + expect(BufferUtils.equal(a, b)).toBe(true); + }); + + it('compares different views', () => { + const top = new Uint8Array([0, 1, 2, 3, 0, 1, 2, 3]); + const a = new Uint8Array(top.buffer, 0, 4); + const b = new Uint8Array(top.buffer, 2, 4); + const c = new Uint8Array(top.buffer, 4, 4); + const d = new Uint16Array(top.buffer, 0, 2); + const e = new DataView(top.buffer, 0, 4); + expect(BufferUtils.equal(top, a)).toBe(false); + expect(BufferUtils.equal(top, b)).toBe(false); + expect(BufferUtils.equal(top, c)).toBe(false); + expect(BufferUtils.equal(a, b)).toBe(false); + expect(BufferUtils.equal(a, c)).toBe(true); + expect(BufferUtils.equal(a, d)).toBe(true); + expect(BufferUtils.equal(a, e)).toBe(true); + expect(BufferUtils.equal(a, e)).toBe(true); + }); + + it('compares ArrayBuffers', () => { + const a = new Uint8Array([0, 1, 2, 3]); + const b = new Uint8Array([0, 1, 2, 3]); + const c = new Uint8Array([0, 1, 2, 4]); + const d = new Uint8Array([0, 1, 2]); + expect(BufferUtils.equal(a.buffer, a.buffer)).toBe(true); + expect(BufferUtils.equal(a.buffer, b.buffer)).toBe(true); + expect(BufferUtils.equal(a.buffer, c.buffer)).toBe(false); + expect(BufferUtils.equal(a.buffer, d.buffer)).toBe(false); + }); + }); +});