@svta/common-media-library
Version:
A common library for media playback in JavaScript
286 lines • 9.93 kB
JavaScript
import { CONTAINERS } from './CONTAINERS.js';
import { DATA } from './fields/DATA.js';
import { INT } from './fields/INT.js';
import { STRING } from './fields/STRING.js';
import { TEMPLATE } from './fields/TEMPLATE.js';
import { UINT } from './fields/UINT.js';
import { UTF8 } from './fields/UTF8.js';
import { readData } from './readers/readData.js';
import { readInt } from './readers/readInt.js';
import { readString } from './readers/readString.js';
import { readTemplate } from './readers/readTemplate.js';
import { readTerminatedString } from './readers/readTerminatedString.js';
import { readUint } from './readers/readUint.js';
import { readUtf8String } from './readers/readUtf8String.js';
import { readUtf8TerminatedString } from './readers/readUtf8TerminatedString.js';
/**
* ISO BMFF data view. Similar to DataView, but with additional methods for reading ISO BMFF data.
* It implements the iterator protocol, so it can be used in a for...of loop.
*
* @group ISOBMFF
*
* @beta
*/
export class IsoView {
/**
* Creates a new IsoView instance. Similar to DataView, but with additional
* methods for reading ISO BMFF data. It implements the iterator protocol,
* so it can be used in a for...of loop.
*
* @param raw - The raw data to view.
* @param config - The configuration for the IsoView.
*/
constructor(raw, config) {
this.truncated = false;
/**
* Creates a new IsoView instance with a slice of the current data view.
*
* @param size - The size of the slice.
* @returns A new IsoView instance.
*/
this.slice = (size) => {
const dataView = new DataView(this.dataView.buffer, this.offset, size);
this.offset += size;
return new IsoView(dataView, this.config);
};
this.read = (type, size = 0) => {
// TODO: Change all sizes from bits to bytes
const { dataView, offset } = this;
let result;
let cursor = size;
switch (type) {
case UINT:
result = readUint(dataView, offset, size);
break;
case INT:
result = readInt(dataView, offset, size);
break;
case TEMPLATE:
result = readTemplate(dataView, offset, size);
break;
case STRING:
if (size === -1) {
result = readTerminatedString(dataView, offset);
cursor = result.length + 1;
}
else {
result = readString(dataView, offset, size);
}
break;
case DATA:
result = readData(dataView, offset, size);
cursor = result.length;
break;
case UTF8:
if (size === -1) {
result = readUtf8TerminatedString(dataView, offset);
cursor = result.length + 1;
}
else {
result = readUtf8String(dataView, offset);
}
break;
default:
result = -1;
}
this.offset += cursor;
return result;
};
/**
* Reads a unsigned integer from the data view.
*
* @param size - The size of the integer in bytes.
* @returns The unsigned integer.
*/
this.readUint = (size) => {
return this.read(UINT, size);
};
/**
* Reads a signed integer from the data view.
*
* @param size - The size of the integer in bytes.
* @returns The signed integer.
*/
this.readInt = (size) => {
return this.read(INT, size);
};
/**
* Reads a string from the data view.
*
* @param size - The size of the string in bytes.
* @returns The string.
*/
this.readString = (size) => {
return this.read(STRING, size);
};
/**
* Reads a template from the data view.
*
* @param size - The size of the template in bytes.
* @returns The template.
*/
this.readTemplate = (size) => {
return this.read(TEMPLATE, size);
};
/**
* Reads a byte array from the data view.
*
* @param size - The size of the data in bytes.
* @returns The data.
*/
this.readData = (size) => {
return this.read(DATA, size);
};
/**
* Reads a UTF-8 string from the data view.
*
* @param size - The size of the string in bytes.
* @returns The UTF-8 string.
*/
this.readUtf8 = (size) => {
return this.read(UTF8, size);
};
/**
* Reads a full box from the data view.
*
* @returns The full box.
*/
this.readFullBox = () => {
return {
version: this.readUint(1),
flags: this.readUint(3),
};
};
/**
* Reads an array of values from the data view.
*
* @param type - The type of the values.
* @param size - The size of the values in bytes.
* @param length - The number of values to read.
* @returns The array of values.
*/
this.readArray = (type, size, length) => {
const value = [];
for (let i = 0; i < length; i++) {
value.push(this.read(type, size));
}
return value;
};
/**
* Reads a raw box from the data view.
*
* @returns The box.
*/
this.readBox = () => {
const { dataView, offset } = this;
// read box size and type without advancing the cursor in case the box is truncated
let cursor = 0;
const box = {
size: readUint(dataView, offset, 4),
type: readString(dataView, offset + 4, 4),
};
cursor += 8;
if (box.size === 1) {
box.largesize = readUint(dataView, offset + cursor, 8);
cursor += 8;
}
const actualSize = box.largesize || box.size;
if (this.cursor + actualSize > dataView.byteLength) {
this.truncated = true;
throw new Error('Truncated box');
}
this.offset += cursor;
if (box.type === 'uuid') {
box.usertype = this.readArray('uint', 1, 16);
}
const viewSize = box.size === 0 ? this.bytesRemaining : actualSize - cursor;
box.data = this.slice(viewSize);
return box;
};
/**
* Reads a number of boxes from the data view.
*
* @param length - The number of boxes to read.
* @returns The boxes.
*/
this.readBoxes = (length) => {
const result = [];
for (const box of this) {
result.push(box);
if (length > 0 && result.length >= length) {
break;
}
}
return result;
};
/**
* Reads a number of entries from the data view.
*
* @param length - The number of entries to read.
* @param map - The function to map the entries.
* @returns The entries.
*/
this.readEntries = (length, map) => {
const result = [];
for (let i = 0; i < length; i++) {
result.push(map());
}
return result;
};
this.dataView = (raw instanceof ArrayBuffer) ? new DataView(raw) : (raw instanceof Uint8Array) ? new DataView(raw.buffer, raw.byteOffset, raw.byteLength) : raw;
this.offset = this.dataView.byteOffset;
this.config = config || { recursive: false, parsers: {} };
}
/**
* The current byteoffset in the data view.
*/
get cursor() {
return this.offset - this.dataView.byteOffset;
}
/**
* Whether the end of the data view has been reached.
*/
get done() {
return this.cursor >= this.dataView.byteLength || this.truncated;
}
/**
* The number of bytes remaining in the data view.
*/
get bytesRemaining() {
return this.dataView.byteLength - this.cursor;
}
/**
* Iterates over the boxes in the data view.
*
* @returns A generator of boxes.
*/
*[Symbol.iterator]() {
const { parsers = {}, recursive = false } = this.config;
while (!this.done) {
try {
const { type, data, ...rest } = this.readBox();
const box = { type, ...rest };
const parser = parsers[type] || parsers[type.trim()]; // url and urn boxes have a trailing space in their type field
if (parser) {
Object.assign(box, parser(data, this.config));
}
box.view = data;
if (CONTAINERS.includes(type)) {
const boxes = [];
for (const child of data) {
if (recursive) {
yield child;
}
boxes.push(child);
}
box.boxes = boxes;
}
yield box;
}
catch (error) {
break;
}
}
}
}
//# sourceMappingURL=IsoView.js.map