mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
ef361ed039
Spec: https://datatracker.ietf.org/doc/draft-ietf-moq-transport/14/ Spec: https://datatracker.ietf.org/doc/draft-ietf-moq-warp/01/ Note: this is experimental and not included in the default builds --------- Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
303 lines
7.6 KiB
JavaScript
303 lines
7.6 KiB
JavaScript
/*! @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 {!number} value
|
|
*/
|
|
writeVarInt62(value) {
|
|
if (value < 0) {
|
|
throw new Error(`Underflow: ${value}`);
|
|
}
|
|
|
|
if (value <= Number.MAX_SAFE_INTEGER) {
|
|
this.writeVarInt53(value);
|
|
return;
|
|
}
|
|
|
|
const v = BigInt(value);
|
|
|
|
const maskFF = BigInt(0xff);
|
|
const mask0F = BigInt(0x0f);
|
|
const prefixC0 = BigInt(0xc0);
|
|
|
|
this.ensureSpace_(8);
|
|
|
|
this.writeUint8(Number(((v >> BigInt(56)) & mask0F) | prefixC0));
|
|
this.writeUint8(Number((v >> BigInt(48)) & maskFF));
|
|
this.writeUint8(Number((v >> BigInt(40)) & maskFF));
|
|
this.writeUint8(Number((v >> BigInt(32)) & maskFF));
|
|
this.writeUint8(Number((v >> BigInt(24)) & maskFF));
|
|
this.writeUint8(Number((v >> BigInt(16)) & maskFF));
|
|
this.writeUint8(Number((v >> BigInt(8)) & maskFF));
|
|
this.writeUint8(Number(v & 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,
|
|
};
|