mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-15 16:06:41 +03:00
90a737961f
A new config is added to config the version: 'auto', 'draft-14', 'draft-16' --------- Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>
358 lines
8.7 KiB
JavaScript
358 lines
8.7 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.msf.Reader');
|
|
goog.provide('shaka.msf.Writer');
|
|
|
|
goog.require('shaka.util.BufferUtils');
|
|
goog.require('shaka.util.IReleasable');
|
|
goog.require('shaka.util.StringUtils');
|
|
|
|
goog.requireType('shaka.msf.Utils');
|
|
|
|
/**
|
|
* Reader wraps a stream and provides convenience methods for reading
|
|
* pieces from a stream.
|
|
*
|
|
* @implements {shaka.util.IReleasable}
|
|
*/
|
|
shaka.msf.Reader = class {
|
|
/**
|
|
* @param {!Uint8Array} buffer
|
|
* @param {!ReadableStream<!Uint8Array>} stream
|
|
*/
|
|
constructor(buffer, stream) {
|
|
/** @private {!Uint8Array} */
|
|
this.buffer_ = buffer;
|
|
/** @private {!ReadableStream<!Uint8Array>} */
|
|
this.stream_ = stream;
|
|
/** @private {!ReadableStreamDefaultReader<!Uint8Array>} */
|
|
this.reader_ = /** @type {!ReadableStreamDefaultReader<!Uint8Array>} */ (
|
|
stream.getReader());
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
getByteLength() {
|
|
return this.buffer_.byteLength;
|
|
}
|
|
|
|
/**
|
|
* @return {!Uint8Array}
|
|
*/
|
|
getBuffer() {
|
|
return shaka.util.BufferUtils.toUint8(this.buffer_);
|
|
}
|
|
|
|
/**
|
|
* Adds more data to the buffer, returning true if more data was added.
|
|
*
|
|
* @return {!Promise<boolean>}
|
|
* @private
|
|
*/
|
|
async fill_() {
|
|
const result = await this.reader_.read();
|
|
if (result.done) {
|
|
return false;
|
|
}
|
|
|
|
const buffer = shaka.util.BufferUtils.toUint8(result.value);
|
|
|
|
if (this.buffer_.byteLength === 0) {
|
|
this.buffer_ = buffer;
|
|
} else {
|
|
const temp = new Uint8Array(this.buffer_.byteLength + buffer.byteLength);
|
|
temp.set(this.buffer_);
|
|
temp.set(buffer, this.buffer_.byteLength);
|
|
this.buffer_ = temp;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add more data to the buffer until it's at least size bytes.
|
|
*
|
|
* @param {number} size
|
|
* @return {!Promise}
|
|
* @private
|
|
*/
|
|
async fillTo_(size) {
|
|
while (this.buffer_.byteLength < size) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
if (!(await this.fill_())) {
|
|
throw new Error('unexpected end of stream');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Consumes the first size bytes of the buffer.
|
|
*
|
|
* @param {number} size
|
|
* @return {!Uint8Array}
|
|
* @private
|
|
*/
|
|
slice_(size) {
|
|
const result = shaka.util.BufferUtils.toUint8(this.buffer_, 0, size);
|
|
this.buffer_ = shaka.util.BufferUtils.toUint8(this.buffer_, size);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @param {number} size
|
|
* @return {!Promise<!Uint8Array>}
|
|
*/
|
|
async read(size) {
|
|
if (size === 0) {
|
|
return new Uint8Array([]);
|
|
}
|
|
await this.fillTo_(size);
|
|
return this.slice_(size);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<!Uint8Array>}
|
|
*/
|
|
async readAll() {
|
|
// eslint-disable-next-line no-empty,no-await-in-loop
|
|
while (await this.fill_()) {}
|
|
return this.slice_(this.buffer_.byteLength);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<!Array<string>>}
|
|
*/
|
|
async tuple() {
|
|
// Get the count of tuple elements
|
|
const count = await this.u53();
|
|
|
|
// Read each tuple element individually
|
|
const tupleElements = [];
|
|
for (let i = 0; i < count; i++) {
|
|
// Each element is a var int length followed by that many bytes
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const length = await this.u53();
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const bytes = await this.read(length);
|
|
const element = shaka.util.StringUtils.fromUTF8(bytes);
|
|
tupleElements.push(element);
|
|
}
|
|
|
|
return tupleElements;
|
|
}
|
|
|
|
/**
|
|
* @param {(number|undefined)=} maxLength
|
|
* @return {!Promise<string>}
|
|
*/
|
|
async string(maxLength) {
|
|
const length = await this.u53();
|
|
if (maxLength !== undefined && length > maxLength) {
|
|
throw new Error(
|
|
`string length ${length} exceeds max length ${maxLength}`);
|
|
}
|
|
|
|
const buffer = await this.read(length);
|
|
return shaka.util.StringUtils.fromUTF8(buffer);
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<number>}
|
|
*/
|
|
async u8() {
|
|
await this.fillTo_(1);
|
|
return this.slice_(1)[0];
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async u8Bool() {
|
|
return (await this.u8()) !== 0;
|
|
}
|
|
|
|
/**
|
|
* Returns a Number using 53-bits, the max Javascript can use for integer math
|
|
* @return {!Promise<number>}
|
|
*/
|
|
async u53() {
|
|
const result = await this.u53WithSize();
|
|
return result.value;
|
|
}
|
|
|
|
/**
|
|
* Returns a Number using 53-bits and tracks the number of bytes read
|
|
* @return {!Promise<{value: number, bytesRead: number}>}
|
|
*/
|
|
async u53WithSize() {
|
|
const result = await this.u62WithSize();
|
|
const v = result.value;
|
|
if (v > Number.MAX_SAFE_INTEGER) {
|
|
throw new Error('value larger than 53-bits; use v62 instead');
|
|
}
|
|
|
|
return {value: Number(v), bytesRead: result.bytesRead};
|
|
}
|
|
|
|
/**
|
|
* If the number is greater than 53 bits, it throws an error.
|
|
*
|
|
* @return {!Promise<bigint>}
|
|
*/
|
|
async u62() {
|
|
const result = await this.u62WithSize();
|
|
return result.value;
|
|
}
|
|
|
|
/**
|
|
* Returns a number and tracks the number of bytes read
|
|
*
|
|
* @return {!Promise<{value: bigint, bytesRead: number}>}
|
|
*/
|
|
async u62WithSize() {
|
|
await this.fillTo_(1);
|
|
const size = (this.buffer_[0] & 0xc0) >> 6;
|
|
|
|
let value;
|
|
let bytesRead;
|
|
|
|
if (size === 0) {
|
|
bytesRead = 1;
|
|
const first = this.slice_(1)[0];
|
|
value = BigInt(first) & BigInt('0x3f'); // 6 bits
|
|
} else if (size === 1) {
|
|
bytesRead = 2;
|
|
await this.fillTo_(2);
|
|
const slice = this.slice_(2);
|
|
const view = shaka.util.BufferUtils.toDataView(slice);
|
|
value = BigInt(view.getInt16(0)) & BigInt('0x3fff'); // 14 bits
|
|
} else if (size === 2) {
|
|
bytesRead = 4;
|
|
await this.fillTo_(4);
|
|
const slice = this.slice_(4);
|
|
const view = shaka.util.BufferUtils.toDataView(slice);
|
|
value = BigInt(view.getUint32(0)) & BigInt('0x3fffffff'); // 30 bits
|
|
} else if (size === 3) {
|
|
bytesRead = 8;
|
|
await this.fillTo_(8);
|
|
const slice = this.slice_(8);
|
|
const view = shaka.util.BufferUtils.toDataView(slice);
|
|
value = BigInt(view.getBigUint64(0)) & BigInt('0x3fffffffffffffff');
|
|
} else {
|
|
throw new Error(`invalid size: ${size}`);
|
|
}
|
|
return {value, bytesRead};
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<!Array<shaka.msf.Utils.KeyValuePair>>}
|
|
*/
|
|
async keyValuePairs() {
|
|
const numPairs = await this.u53();
|
|
const result = [];
|
|
for (let i = 0; i < numPairs; i++) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const key = await this.u62();
|
|
if (key % BigInt(2) === BigInt(0)) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const value = await this.u62();
|
|
result.push({type: key, value});
|
|
} else {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const length = await this.u53();
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const value = await this.read(length);
|
|
result.push({type: key, value});
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<!Array<shaka.msf.Utils.KeyValuePair>>}
|
|
*/
|
|
async deltaKeyValuePairs() {
|
|
const count = await this.u53();
|
|
/** @type {!Array<shaka.msf.Utils.KeyValuePair>} */
|
|
const params = [];
|
|
let prevType = BigInt(0);
|
|
for (let i = 0; i < count; i++) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const delta = await this.u62();
|
|
const paramType = prevType + delta;
|
|
prevType = paramType;
|
|
if (paramType % BigInt(2) === BigInt(0)) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const value = await this.u62();
|
|
params.push({
|
|
type: paramType,
|
|
value,
|
|
});
|
|
} else {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const length = await this.u53();
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const value = await this.read(length);
|
|
params.push({
|
|
type: paramType,
|
|
value,
|
|
});
|
|
}
|
|
}
|
|
return params;
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async done() {
|
|
if (this.buffer_.byteLength > 0) {
|
|
return false;
|
|
}
|
|
return !(await this.fill_());
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise}
|
|
*/
|
|
async close() {
|
|
this.reader_.releaseLock();
|
|
await this.stream_.cancel('Reader closed');
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
release() {
|
|
this.reader_.releaseLock();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Writer wraps a stream and writes chunks of data.
|
|
*/
|
|
shaka.msf.Writer = class {
|
|
/**
|
|
* @param {!WritableStream} stream
|
|
*/
|
|
constructor(stream) {
|
|
/** @private {!WritableStream} */
|
|
this.stream_ = stream;
|
|
/** @private {!WritableStreamDefaultWriter} */
|
|
this.writer_ = stream.getWriter();
|
|
}
|
|
|
|
/**
|
|
* @param {!Uint8Array} value
|
|
* @return {!Promise}
|
|
*/
|
|
async write(value) {
|
|
await this.writer_.write(value);
|
|
}
|
|
};
|