From 28016fee0045abcaf22f3ebb99f08f3d737636e1 Mon Sep 17 00:00:00 2001 From: Bradley Odell Date: Sat, 26 Jun 2021 01:46:23 -0700 Subject: [PATCH] Working on implementing new parser... --- .gitignore | 6 + boxes/ftyp/package.json | 3 +- boxes/ftyp/src/ftyp.ts | 27 +++ package-lock.json | 25 ++- packages/core/package.json | 2 +- packages/core/src/Box.ts | 151 +++++++++++++++- packages/core/src/BoxEncoding.ts | 117 +++++++++++++ packages/core/src/core.ts | 21 +-- packages/core/src/tsconfig.json | 1 - packages/encoding/README.md | 2 - packages/encoding/cjs/package.json | 3 - packages/encoding/package.json | 47 ----- packages/encoding/src/encoding.ts | 52 ------ packages/encoding/src/tsconfig.cjs.json | 7 - packages/encoding/src/tsconfig.json | 10 -- packages/encoding/tsconfig.json | 6 - packages/parser/package.json | 3 +- packages/parser/src/parser.ts | 219 ++++++++++++------------ packages/tsconfig.json | 1 - 19 files changed, 424 insertions(+), 279 deletions(-) create mode 100644 boxes/ftyp/src/ftyp.ts create mode 100644 packages/core/src/BoxEncoding.ts delete mode 100644 packages/encoding/README.md delete mode 100644 packages/encoding/cjs/package.json delete mode 100644 packages/encoding/package.json delete mode 100644 packages/encoding/src/encoding.ts delete mode 100644 packages/encoding/src/tsconfig.cjs.json delete mode 100644 packages/encoding/src/tsconfig.json delete mode 100644 packages/encoding/tsconfig.json diff --git a/.gitignore b/.gitignore index ef7d7a1..c3d9ff1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,12 @@ # Generated JavaScript files dist/ +boxes/**/cjs/**/*.js +boxes/**/cjs/**/*.js.map +boxes/**/cjs/**/*.d.ts +boxes/**/test/**/*.js +boxes/**/test/**/*.js.map +boxes/**/test/**/*.d.ts packages/**/cjs/**/*.js packages/**/cjs/**/*.js.map packages/**/cjs/**/*.d.ts diff --git a/boxes/ftyp/package.json b/boxes/ftyp/package.json index 1595b73..b0b48b7 100644 --- a/boxes/ftyp/package.json +++ b/boxes/ftyp/package.json @@ -34,7 +34,8 @@ }, "types": "./dist/ftyp.d.ts", "dependencies": { - "@isomp4/core": "file:../../packages/core" + "@isomp4/core": "file:../../packages/core", + "buffer": "^6.0.0" }, "scripts": { "clean": "shx rm -rf ./dist ./cjs/**/*.js ./cjs/**/*.js.map ./cjs/**/*.d.ts ./test/**/*.js ./test/**/*.js.map ./test/**/*.d.ts ./**/*.tsbuildinfo" diff --git a/boxes/ftyp/src/ftyp.ts b/boxes/ftyp/src/ftyp.ts new file mode 100644 index 0000000..e147c7a --- /dev/null +++ b/boxes/ftyp/src/ftyp.ts @@ -0,0 +1,27 @@ +import type {Buffer} from "buffer"; +import type {Box, BoxEncoding, BoxHeader, FourCC} from "@isomp4/core"; +import {AbstractBoxEncoding} from "@isomp4/core"; + +export interface FileTypeBox extends Box { + readonly majorBrand: FourCC; + readonly minorBrand: FourCC; + readonly compatibleBrands: readonly FourCC[]; +} + +export const ftyp: BoxEncoding = new class extends AbstractBoxEncoding { + + public override readonly type: FourCC = "ftyp"; + + public override encodingLength(obj: FileTypeBox): number { + return 0; + } + + public override encodeTo(obj: FileTypeBox, buf: Buffer): number { + return 0; + } + + public override decodeWithHeader(header: BoxHeader, buffer: Buffer): FileTypeBox | number { + return 0; + } + +}(); diff --git a/package-lock.json b/package-lock.json index af2e03e..119e5f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,8 @@ "version": "0.0.1", "license": "MPL-2.0", "dependencies": { - "@isomp4/core": "file:../../packages/core" + "@isomp4/core": "file:../../packages/core", + "buffer": "^6.0.0" }, "engines": { "node": ">=16.0.0", @@ -216,10 +217,6 @@ "resolved": "packages/core", "link": true }, - "node_modules/@isomp4/encoding": { - "resolved": "packages/encoding", - "link": true - }, "node_modules/@isomp4/parser": { "resolved": "packages/parser", "link": true @@ -8645,7 +8642,7 @@ "version": "0.0.1", "license": "MPL-2.0", "dependencies": { - "@isomp4/encoding": "file:../encoding" + "buffer": "^6.0.0" }, "engines": { "node": ">=16.0.0", @@ -8655,6 +8652,7 @@ "packages/encoding": { "name": "@isomp4/encoding", "version": "0.0.1", + "extraneous": true, "license": "MPL-2.0", "dependencies": { "buffer": "^6.0.0" @@ -8669,7 +8667,8 @@ "version": "0.0.1", "license": "MPL-2.0", "dependencies": { - "@isomp4/core": "file:../core" + "@isomp4/core": "file:../core", + "buffer": "^6.0.0" }, "engines": { "node": ">=16.0.0", @@ -8782,7 +8781,8 @@ "@isomp4/box-ftyp": { "version": "file:boxes/ftyp", "requires": { - "@isomp4/core": "file:../../packages/core" + "@isomp4/core": "file:../../packages/core", + "buffer": "^6.0.0" } }, "@isomp4/box-mdat": { @@ -8805,12 +8805,6 @@ }, "@isomp4/core": { "version": "file:packages/core", - "requires": { - "@isomp4/encoding": "file:../encoding" - } - }, - "@isomp4/encoding": { - "version": "file:packages/encoding", "requires": { "buffer": "^6.0.0" } @@ -8818,7 +8812,8 @@ "@isomp4/parser": { "version": "file:packages/parser", "requires": { - "@isomp4/core": "file:../core" + "@isomp4/core": "file:../core", + "buffer": "^6.0.0" } }, "@lerna/add": { diff --git a/packages/core/package.json b/packages/core/package.json index 753589b..64d2e2c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ }, "types": "./dist/core.d.ts", "dependencies": { - "@isomp4/encoding": "file:../encoding" + "buffer": "^6.0.0" }, "scripts": { "clean": "shx rm -rf ./dist ./cjs/**/*.js ./cjs/**/*.js.map ./cjs/**/*.d.ts ./test/**/*.js ./test/**/*.js.map ./test/**/*.d.ts ./**/*.tsbuildinfo" diff --git a/packages/core/src/Box.ts b/packages/core/src/Box.ts index 67f7414..305a171 100644 --- a/packages/core/src/Box.ts +++ b/packages/core/src/Box.ts @@ -1,3 +1,152 @@ -export interface Box { +import {Buffer} from "buffer"; +/** + * The size (in bytes) of a compact box header in the ISO base media file format. + * This header includes the 32-bit unsigned `size` field and the 32-bit unsigned `type` field. + * @see ISO/IEC 14496-12. + */ +export const BOX_HEADER_SIZE: number = 8; + +const MAX_LARGE_SIZE: bigint = BigInt(Number.MAX_SAFE_INTEGER); + +/** + * Represents a four-character code. + */ +export type FourCC = string; + +/** + * The header fields of a box structure. + */ +export interface BoxHeader { + + /** + * The length (in bytes) of just this header. + */ + readonly length: number; + + /** + * The number of bytes of the entire box, including header, fields, and children. + */ + readonly size: number; + + /** + * The unique four-character code representing the type of the box. + * Common types are: ftyp, moov, moof, mdat + */ + readonly type: FourCC; + + /** + * 64-bit extended size of the box. + */ + readonly largesize?: bigint; + + /** + * 16-byte UUID for custom user types. + */ + readonly usertype?: Buffer; + +} + +export interface Box extends BoxHeader {} + +/** + * Parses the given buffer into a box header object. + * @param buffer The buffer to read from (starting at offset 0). + * @return A parsed box header object, + * or if there isn't enough data in the buffer, returns the total + * number of bytes needed to read the box header. + */ +export function parseBoxHeader(buffer: Buffer): BoxHeader | number { + let length: number = BOX_HEADER_SIZE; + // 'size' and 'type' are always required + if (buffer.length < length) { + return length; + } + let size: number = buffer.readUInt32BE(0); + const type: FourCC = buffer.toString("binary", 4, 8); + let offset: number = 8; + let largesize: bigint | undefined; + if (size === 0) { + throw new Error("box cannot extend indefinitely"); + } else if (size === 1) { + if (buffer.length < (length += 8)) { + return length; + } + largesize = buffer.readBigUInt64BE(offset).valueOf(); + if (largesize > MAX_LARGE_SIZE) { + throw new Error("largesize mode is not supported"); + } + offset += 8; + // If the largesize can be stored in the normal size, then do so + size = Number(largesize); + } else if (size < length) { + throw new Error("invalid box size: " + size); + } else if (size === length) { + throw new Error("empty box not supported"); + } + // Check for user-defined type + let usertype: Buffer | undefined; + if (type === "uuid") { + if (buffer.length < (length += 16)) { + return length; + } + usertype = Buffer.from(buffer.slice(offset, offset + 16)); + } + return { + length, + size, + type, + largesize, + usertype, + }; +} + +/** + * The header fields of a full box structure. + */ +export interface FullBoxHeader { + + /** + * The length (in bytes) of just this header. + */ + readonly length: number; + + /** + * Specifies the version of this format of the box. + */ + readonly version: number; + + /** + * A bitfield of custom flags. + */ + readonly flags: number; + +} + +export interface FullBox extends Box, FullBoxHeader {} + +/** + * Parses the given buffer into a full box header object. + * @param buffer The buffer to read from (starting at offset 0). + * @return A parsed full box header object, + * or if there isn't enough data in the buffer, returns the total + * number of bytes needed to read the full box header. + */ +export function parseFullBoxHeader(buffer: Buffer): FullBoxHeader | number { + if (buffer.length < 4) { + return 4; + } + const version: number = buffer.readUInt8(0); + const flags: number = buffer.readUIntBE(1, 3); + return { + length: 4, + version, + flags, + }; +} + +export interface BoxContainer { + children: { + [type: string]: Box | Box[], + }; } diff --git a/packages/core/src/BoxEncoding.ts b/packages/core/src/BoxEncoding.ts new file mode 100644 index 0000000..9abd9a4 --- /dev/null +++ b/packages/core/src/BoxEncoding.ts @@ -0,0 +1,117 @@ +import {Buffer} from "buffer"; +import type {Box, BoxHeader, FourCC} from "./Box"; +import {parseBoxHeader} from "./Box"; + +/** + * + */ +export interface BoxEncoding { + + /** + * Unique box type. + */ + readonly type: FourCC; + + /** + * + */ + readonly encodedBytes: number; + + /** + * + */ + readonly decodedBytes: number; + + /** + * + * @param obj + */ + encodingLength(obj: B): number; + + /** + * Encodes the given box to a buffer. + * @param obj The box to encode. + * @return A newly allocated buffer capable of holding the encoded box data. + */ + encode(obj: B): Buffer; + + /** + * Encodes the given box into the given buffer. + * @param obj The box to encode. + * @param buf The buffer to write to. + * @return The number of bytes of available space required in the buffer. + * If 0, then the box was successfully written to the buffer. + */ + encodeTo(obj: B, buf: Buffer): number; + + /** + * + * @param buffer + */ + decode(buffer: Buffer): B | number; + + /** + * + * @param header + * @param buffer + */ + decodeWithHeader(header: BoxHeader, buffer: Buffer): B | number; + +} + +/** + * A base implementation of BoxEncoding. + */ +export abstract class AbstractBoxEncoding implements BoxEncoding { + + public abstract readonly type: FourCC; + + private _decodedBytes: number = 0; + + public get decodedBytes(): number { + return this._decodedBytes; + } + + protected set decodedBytes(value: number) { + this._decodedBytes = value; + } + + private _encodedBytes: number = 0; + + public get encodedBytes(): number { + return this._encodedBytes; + } + + protected set encodedBytes(value: number) { + this._encodedBytes = value; + } + + public abstract encodingLength(obj: B): number; + + public encode(obj: B): Buffer { + const len: number = this.encodingLength(obj); + const buf: Buffer = Buffer.alloc(len); + const required: number = this.encodeTo(obj, buf); + if (required > 0) { + throw new Error(`encodingLength(${len}) and encodeTo(${required}) are mismatched`); + } + return buf; + } + + public abstract encodeTo(obj: B, buf: Buffer): number; + + public decode(buffer: Buffer): B | number { + const parsedHeader = parseBoxHeader(buffer); + if (typeof parsedHeader === "number") { + return parsedHeader; + } + const parsedBox = this.decodeWithHeader(parsedHeader, buffer.slice(parsedHeader.length)); + if (typeof parsedBox === "number") { + return parsedHeader.length + parsedBox; + } + return parsedBox; + } + + public abstract decodeWithHeader(header: BoxHeader, buffer: Buffer): B | number; + +} diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 019db1c..c52275b 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1,19 +1,2 @@ -import type {Encoding} from "@isomp4/encoding"; -import type {Box} from "./Box"; - -export type BoxEncoding = Encoding; - -const boxes: Map = new Map(); - -export function registerBox(boxType: string, encoding: BoxEncoding): void { - if (boxes.has(boxType)) { - throw new Error("Box type is already registered: " + boxType); - } - boxes.set(boxType, encoding); -} - -export function isBoxRegistered(boxType: string): boolean { - return boxes.has(boxType); -} - -export {Box} from "./Box"; +export * from "./Box"; +export * from "./BoxEncoding"; diff --git a/packages/core/src/tsconfig.json b/packages/core/src/tsconfig.json index b8070da..8f3182b 100644 --- a/packages/core/src/tsconfig.json +++ b/packages/core/src/tsconfig.json @@ -5,7 +5,6 @@ "module": "ES2015" }, "references": [ - { "path": "../../encoding/src"}, { "path": "./tsconfig.cjs.json" } ] } diff --git a/packages/encoding/README.md b/packages/encoding/README.md deleted file mode 100644 index ba63fcc..0000000 --- a/packages/encoding/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# @isomp4/encoding -Defines interface for the abstract-encoding model: https://github.com/mafintosh/abstract-encoding diff --git a/packages/encoding/cjs/package.json b/packages/encoding/cjs/package.json deleted file mode 100644 index 5bbefff..0000000 --- a/packages/encoding/cjs/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "commonjs" -} diff --git a/packages/encoding/package.json b/packages/encoding/package.json deleted file mode 100644 index 5db87ae..0000000 --- a/packages/encoding/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@isomp4/encoding", - "version": "0.0.1", - "description": "Defines interface for the abstract-encoding model", - "keywords": [ - "iso", - "bmff", - "mp4", - "box", - "encoding", - "model" - ], - "author": { - "name": "Bradley Odell", - "url": "https://github.com/BTOdell" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/BTOdell/isomp4.git", - "directory": "packages/encoding" - }, - "homepage": "https://github.com/BTOdell/isomp4/tree/master/packages/encoding#readme", - "license": "MPL-2.0", - "publishConfig": { - "access": "public" - }, - "type": "module", - "main": "./cjs/encoding.js", - "module": "./dist/encoding.js", - "exports": { - ".": { - "require": "./cjs/encoding.js", - "default": "./dist/encoding.js" - } - }, - "types": "./dist/encoding.d.ts", - "dependencies": { - "buffer": "^6.0.0" - }, - "scripts": { - "clean": "shx rm -rf ./dist ./cjs/**/*.js ./cjs/**/*.js.map ./cjs/**/*.d.ts ./test/**/*.js ./test/**/*.js.map ./test/**/*.d.ts ./**/*.tsbuildinfo" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.14.0" - } -} diff --git a/packages/encoding/src/encoding.ts b/packages/encoding/src/encoding.ts deleted file mode 100644 index d6d88d2..0000000 --- a/packages/encoding/src/encoding.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type {Buffer} from "buffer"; - -type EncodeFunction = { - - /** - * - */ - bytes: number, - - /** - * - * @param obj - * @param buf - * @param off - */ - (obj: O, buf?: Buffer, off?: number): Buffer, - -}; - -type DecodeFunction = { - - /** - * - */ - bytes: number, - - /** - * - * @param buffer - * @param start - * @param end - */ - (buffer: Buffer, start?: number, end?: number): O, - -}; - -/** - * - */ -export interface Encoding { - - encode: EncodeFunction; - - decode: DecodeFunction; - - /** - * - * @param obj - */ - encodingLength(obj: O): number; - -} diff --git a/packages/encoding/src/tsconfig.cjs.json b/packages/encoding/src/tsconfig.cjs.json deleted file mode 100644 index 0ad1f37..0000000 --- a/packages/encoding/src/tsconfig.cjs.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "../cjs", - "declarationDir": "../dist" - } -} diff --git a/packages/encoding/src/tsconfig.json b/packages/encoding/src/tsconfig.json deleted file mode 100644 index 8f3182b..0000000 --- a/packages/encoding/src/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "../dist", - "module": "ES2015" - }, - "references": [ - { "path": "./tsconfig.cjs.json" } - ] -} diff --git a/packages/encoding/tsconfig.json b/packages/encoding/tsconfig.json deleted file mode 100644 index efeec55..0000000 --- a/packages/encoding/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "src" } - ] -} diff --git a/packages/parser/package.json b/packages/parser/package.json index aad630d..b9167ea 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -35,7 +35,8 @@ }, "types": "./dist/parser.d.ts", "dependencies": { - "@isomp4/core": "file:../core" + "@isomp4/core": "file:../core", + "buffer": "^6.0.0" }, "scripts": { "clean": "shx rm -rf ./dist ./cjs/**/*.js ./cjs/**/*.js.map ./cjs/**/*.d.ts ./test/**/*.js ./test/**/*.js.map ./test/**/*.d.ts ./**/*.tsbuildinfo" diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index d60b260..0e1da2e 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -1,71 +1,67 @@ import {Buffer} from "buffer"; +import type {BoxEncoding, BoxHeader, FourCC} from "@isomp4/core"; -type InitBoxType = "ftyp" | "moov"; -type MediaBoxType = "moof" | "mdat"; +const EMPTY_BUFFER = Buffer.allocUnsafe(0); -/** - * A supported FourCC ("four-character code") box type. - */ -export type BoxType = InitBoxType | MediaBoxType; - -/** - * The size (in bytes) of a compact box header in the ISO base media file format. - * This header includes the 32-bit unsigned `size` field and the 32-bit unsigned `type` field. - * @see ISO/IEC 14496-12. - */ -const BOX_HEADER_SIZE: number = 8; - -/** - * Determines if the given string is a supported box type. - * @param type The type to check. - */ -export function isSupportedBoxType(type: string): type is BoxType { - switch (type) { - case "ftyp": - case "moov": - case "moof": - case "mdat": - return true; - } - return false; +interface BoxState { + readonly size: number; + readonly type: string | null; + offset: number; } /** - * Parses top-level boxes of a fragmented MP4 stream. - * @see https://www.w3.org/TR/mse-byte-stream-format-isobmff/ - * - * From the ISO BMFF specification, only "ftyp", "moov", "moof", and "mdat" are supported box types. - * All other box types will be ignored. + * Parses the ISO BMFF box structure of an MP4 stream. */ export abstract class AbstractMP4Parser { /** - * A buffer to store a compact box header. + * Registered box encodings. */ - private readonly boxHeaderBuffer: Buffer; + private readonly boxes: Map; /** - * The current offset into the box header buffer to write data. - * This also acts as the number of bytes written to the box header buffer. + * A stack that keeps track of the current state in the MP4 box structure traversal. */ - private boxHeaderOffset: number; + private readonly boxStack: BoxState[]; /** - * The box that is currently being processed. + * A temporary buffer to store appended data before it's parsed. + * This buffer may be resized if a box requires more space before it can be fully parsed. */ - private currentBox: { - readonly size: number, - readonly type: BoxType | null, - offset: number, - } | null; + private buffer: Buffer; /** - * Creates a new parser for a fragmented MP4 stream. + * The number of bytes needed in the buffer to parse the next part. + */ + private bytesNeeded: number; + + /** + * Creates a new parser for an MP4 stream. */ protected constructor() { - this.boxHeaderBuffer = Buffer.alloc(BOX_HEADER_SIZE); - this.boxHeaderOffset = 0; - this.currentBox = null; + this.boxes = new Map(); + this.boxStack = []; + this.buffer = EMPTY_BUFFER; + this.bytesNeeded = 0; + } + + /** + * + * @param encoding + */ + public registerBox(encoding: BoxEncoding): void { + if (this.boxes.has(encoding.type)) { + throw new Error("Box type is already registered: " + encoding.type); + } + this.boxes.set(encoding.type, encoding); + } + + /** + * + * @param boxType + */ + public isBoxRegistered(boxType: FourCC): boolean { + return this.boxes.has(boxType); } /** @@ -73,89 +69,88 @@ export abstract class AbstractMP4Parser { * @param data The new data to append. This data does NOT need to be a complete segment (or even a fragment). */ public append(data: ArrayBufferView): void { - // Handle main MP4 box parsing - const buf: Buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength); - let offset: number = 0; - let available: number; - while ((available = buf.byteLength - offset) > 0) { - if (this.currentBox != null) { - // Pass through box data - const needed: number = this.currentBox.size - this.currentBox.offset; - const transfer: number = Math.min(available, needed); - const newOffset: number = offset + transfer; - if (this.currentBox.type != null) { - const boxData: Buffer = buf.slice(offset, newOffset); - this.onBoxData(this.currentBox.type, boxData); - } - this.currentBox.offset += transfer; - offset = newOffset; - // Once passed the box data, signal end of box and reset for next box - if (this.currentBox.offset >= this.currentBox.size) { - if (this.currentBox.type != null) { - this.onBoxEnded(this.currentBox.type); + let input: Buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength); + while (input.length > 0) { + if (this.bytesNeeded > 0) { + // Ensure that buffer is the correct size + this.ensureBuffer(this.bytesNeeded); + // Don't copy more data into the temp buffer than is needed for the next part! + const needed: number = this.bytesNeeded - this.buffer.byteOffset; + if (needed > 0) { + if (needed <= input.length) { + // Only copy 'needed' number of bytes into the temp buffer + input.copy(this.buffer, 0, 0, needed); + input = input.slice(needed); + } else { + // All input can be copied into temp buffer + this.buffer = this.buffer.slice(input.copy(this.buffer)); + // More input data is needed + return; } - this.currentBox = null; } - } else { - // Write data into box header buffer until full - { - const needed: number = this.boxHeaderBuffer.byteLength - this.boxHeaderOffset; - const transfer: number = Math.min(available, needed); - const newOffset: number = offset + transfer; - buf.copy(this.boxHeaderBuffer, this.boxHeaderOffset, offset, newOffset); - this.boxHeaderOffset += transfer; - offset = newOffset; + // Temp buffer now contains all bytes needed + // Flip buffer + const bytesNeeded = this.bytesNeeded; + const buf = Buffer.from(this.buffer.buffer, 0, bytesNeeded); + this.bytesNeeded = 0; // This must be reset before calling processBuffer() + const bytesConsumed: number = this.processBuffer(buf); + if (bytesConsumed !== bytesNeeded) { + throw new Error(`bytes consumed(${bytesConsumed}) != bytes needed(${bytesNeeded})`); } - // Once full, create current box from buffer data - if (this.boxHeaderOffset >= this.boxHeaderBuffer.byteLength) { - const size: number = this.boxHeaderBuffer.readUInt32BE(0); - const type: string = this.boxHeaderBuffer.toString("binary", 4, 8); - if (size === 0) { - throw new Error("Box cannot extend indefinitely."); - } else if (size === 1) { - throw new Error("Largesize mode is not supported."); - } else if (size < BOX_HEADER_SIZE) { - throw new Error("Invalid box size: " + size); - } else if (size === BOX_HEADER_SIZE) { - throw new Error("Empty box not supported."); - } - const boxType: BoxType | null = isSupportedBoxType(type) ? type : null; - this.currentBox = { - size: size, - type: boxType, - offset: BOX_HEADER_SIZE, - }; - // Invoke box start event - if (boxType != null) { - this.onBoxStarted(size, boxType, this.boxHeaderBuffer); - } - // Reset box header - this.boxHeaderOffset = 0; + // Reset buffer + this.buffer = Buffer.from(this.buffer.buffer); + } else { + // Avoid copying data by using the input buffer directly + const bytesConsumed: number = this.processBuffer(input); + if (bytesConsumed > 0) { + input = input.slice(bytesConsumed); } } } } + private ensureBuffer(capacity: number): void { + if (this.buffer.buffer.byteLength < capacity) { + const newBuffer: Buffer = Buffer.alloc(capacity); + // If the byteOffset is zero, then this indicates that there is no data written to the buffer + if (this.buffer.byteOffset > 0) { + // Must copy old buffer to new buffer + Buffer.from(this.buffer.buffer).copy(newBuffer); + } + this.buffer = newBuffer; + } + } + + /** + * + * @param buffer + * @return The number of bytes from the buffer that were consumed. + */ + private processBuffer(buffer: Buffer): number { + // TODO + return 0; + } + /** * Invoked when a new box starts from the source. - * @param size The size of the box. - * @param type The type of the box. - * @param header The raw header data of the box. + * @param header The parsed header data of the box. + * @param headerData The raw header data of the box. + * @return Whether the traverse the children of this box. */ - protected abstract onBoxStarted(size: number, type: BoxType, header: Buffer): void; + protected abstract onBoxStarted(header: BoxHeader, headerData: Buffer): void; /** * Invoked when new data is received for the current box. * @param type The type of the current box. * @param data The data of the box. */ - protected abstract onBoxData(type: BoxType, data: Buffer): void; + protected abstract onBoxData(type: string, data: Buffer): void; /** * Invoked when the current box ends. * @param type The type of the box that ended. */ - protected abstract onBoxEnded(type: BoxType): void; + protected abstract onBoxEnded(type: string): void; } @@ -168,15 +163,15 @@ export class MP4Parser extends AbstractMP4Parser { public boxData?: typeof MP4Parser.prototype.onBoxData; public boxEnded?: typeof MP4Parser.prototype.onBoxEnded; - protected onBoxStarted(size: number, type: BoxType, header: Buffer): void { - this.boxStarted?.(size, type, header); + protected onBoxStarted(header: BoxHeader, headerData: Buffer): void { + this.boxStarted?.(header, headerData); } - protected onBoxData(type: BoxType, data: Buffer): void { + protected onBoxData(type: string, data: Buffer): void { this.boxData?.(type, data); } - protected onBoxEnded(type: BoxType): void { + protected onBoxEnded(type: string): void { this.boxEnded?.(type); } diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 1ad3b98..7e58f74 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -2,7 +2,6 @@ "files": [], "references": [ { "path": "core" }, - { "path": "encoding" }, { "path": "parser" } ] }