UNPKG

shaka-player

Version:
301 lines (260 loc) 7.8 kB
/*! @license * Shaka Player * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.util.DataViewWriter'); goog.require('goog.asserts'); goog.require('shaka.util.BufferUtils'); goog.require('shaka.util.StringUtils'); goog.require('shaka.util.Error'); /** * @summary DataViewWriter abstracts a growable DataView for binary writing. * @export */ shaka.util.DataViewWriter = class { /** * @param {number} initialSize * @param {shaka.util.DataViewWriter.Endianness} endianness The endianness. */ constructor(initialSize, endianness) { /** @private {!Uint8Array} */ this.buffer_ = new Uint8Array(initialSize); /** @private {!DataView} */ this.dataView_ = shaka.util.BufferUtils.toDataView(this.buffer_); /** @private {boolean} */ this.littleEndian_ = endianness == shaka.util.DataViewWriter.Endianness.LITTLE_ENDIAN; /** @private {number} */ this.position_ = 0; } /** @return {number} */ getPosition() { return this.position_; } /** @return {number} */ getLength() { return this.position_; } /** @return {!Uint8Array} */ getBytes() { return shaka.util.BufferUtils.toUint8(this.buffer_, 0, this.position_); } /** * Resets the position. */ reset() { this.position_ = 0; } /** * @param {number} bytes * @private */ ensureSpace_(bytes) { const required = this.position_ + bytes; if (required <= this.buffer_.length) { return; } const newSize = Math.max(this.buffer_.length * 2, required); const newBuffer = new Uint8Array(newSize); newBuffer.set(this.buffer_); this.buffer_ = newBuffer; this.dataView_ = shaka.util.BufferUtils.toDataView(this.buffer_); } /** @param {number} value */ writeUint8(value) { this.ensureSpace_(1); this.dataView_.setUint8(this.position_, value & 0xff); this.position_ += 1; } /** @param {number} value */ writeUint16(value) { this.ensureSpace_(2); this.dataView_.setUint16( this.position_, value & 0xffff, this.littleEndian_); this.position_ += 2; } /** @param {number} value */ writeUint32(value) { this.ensureSpace_(4); this.dataView_.setUint32(this.position_, value >>> 0, this.littleEndian_); this.position_ += 4; } /** @param {number} value */ writeUint64(value) { if (value < 0 || value > Number.MAX_SAFE_INTEGER) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.JS_INTEGER_OVERFLOW); } this.ensureSpace_(8); const high = Math.floor(value / 0x100000000); const low = value >>> 0; if (this.littleEndian_) { this.dataView_.setUint32(this.position_, low, true); this.dataView_.setUint32(this.position_ + 4, high, true); } else { this.dataView_.setUint32(this.position_, high, false); this.dataView_.setUint32(this.position_ + 4, low, false); } this.position_ += 8; } /** * @param {!Uint8Array} bytes */ writeBytes(bytes) { goog.asserts.assert(bytes, 'Bad call to writeBytes'); this.ensureSpace_(bytes.byteLength); const view = shaka.util.BufferUtils.toUint8( this.buffer_, this.position_, bytes.byteLength); view.set(bytes); this.position_ += bytes.byteLength; } /** * Writes a UTF-8 string prefixed by its length as uint32. * @param {string} str */ writeString(str) { const bytes = shaka.util.BufferUtils.toUint8( shaka.util.StringUtils.toUTF8(str)); this.writeUint32(bytes.length); this.writeBytes(bytes); } /** * Variable-length unsigned integer (up to 53 bits). * @param {number} value */ writeVarInt53(value) { if (value < 0) { throw new Error(`Underflow: ${value}`); } const MAX_U6 = (1 << 6) - 1; const MAX_U14 = (1 << 14) - 1; const MAX_U30 = (1 << 30) - 1; const MAX_U53 = Number.MAX_SAFE_INTEGER; if (value <= MAX_U6) { // 1-byte encoding (0xxxxxxx) this.writeUint8(value); } else if (value <= MAX_U14) { // 2-byte encoding (10xxxxxx xxxxxxxx) this.writeUint8(((value >> 8) & 0x3f) | 0x40); this.writeUint8(value & 0xff); } else if (value <= MAX_U30) { // 4-byte encoding (110xxxxx xxxxxxxx xxxxxxxx xxxxxxxx) this.writeUint8(((value >> 24) & 0x1f) | 0x80); this.writeUint8((value >> 16) & 0xff); this.writeUint8((value >> 8) & 0xff); this.writeUint8(value & 0xff); } else if (value <= MAX_U53) { // 8-byte encoding (1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx // xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx) const high = Math.floor(value / 0x100000000); const low = value % 0x100000000; this.writeUint8(((high >> 24) & 0x0f) | 0xc0); this.writeUint8((high >> 16) & 0xff); this.writeUint8((high >> 8) & 0xff); this.writeUint8(high & 0xff); this.writeUint8((low >> 24) & 0xff); this.writeUint8((low >> 16) & 0xff); this.writeUint8((low >> 8) & 0xff); this.writeUint8(low & 0xff); } else { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.JS_INTEGER_OVERFLOW); } } /** * Variable-length unsigned integer (up to 62 bits). * @param {!bigint} value */ writeVarInt62(value) { if (value < BigInt(0)) { throw new Error(`Underflow: ${value}`); } if (value <= BigInt(Number.MAX_SAFE_INTEGER)) { this.writeVarInt53(Number(value)); return; } const maskFF = BigInt(0xff); const mask0F = BigInt(0x0f); const prefixC0 = BigInt(0xc0); this.ensureSpace_(8); this.writeUint8(Number(((value >> BigInt(56)) & mask0F) | prefixC0)); this.writeUint8(Number((value >> BigInt(48)) & maskFF)); this.writeUint8(Number((value >> BigInt(40)) & maskFF)); this.writeUint8(Number((value >> BigInt(32)) & maskFF)); this.writeUint8(Number((value >> BigInt(24)) & maskFF)); this.writeUint8(Number((value >> BigInt(16)) & maskFF)); this.writeUint8(Number((value >> BigInt(8)) & maskFF)); this.writeUint8(Number(value & maskFF)); } /** * Writes a UTF-8 string prefixed by its length as a var int (up to 53 bits). * @param {string} str */ writeStringVarInt(str) { const bytes = shaka.util.BufferUtils.toUint8( shaka.util.StringUtils.toUTF8(str)); this.writeVarInt53(bytes.length); this.writeBytes(bytes); } /** * @param {number} position */ seek(position) { goog.asserts.assert(position >= 0, 'Bad seek'); if (position > this.buffer_.length) { throw this.outOfBounds_(); } this.position_ = position; } /** * @param {number} bytes */ skip(bytes) { goog.asserts.assert(bytes >= 0, 'Bad skip'); this.ensureSpace_(bytes); this.position_ += bytes; } /** * Reserve 2 bytes and return their position for later patching. * @return {number} */ reserveUint16() { const pos = this.position_; this.skip(2); return pos; } /** * @param {number} position * @param {number} value */ patchUint16(position, value) { const cur = this.position_; this.position_ = position; this.writeUint16(value); this.position_ = cur; } /** * @return {!shaka.util.Error} * @private */ outOfBounds_() { return new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.BUFFER_WRITE_OUT_OF_BOUNDS); } }; /** * Endianness. * @enum {number} * @export */ shaka.util.DataViewWriter.Endianness = { 'BIG_ENDIAN': 0, 'LITTLE_ENDIAN': 1, };