feat: support skip collect child for memory and setup stream start offset

This commit is contained in:
master 2025-03-18 01:45:36 +08:00
parent bbc9c86531
commit 85eecbf6ac
9 changed files with 97 additions and 31 deletions

View File

@ -1,6 +1,6 @@
{
"name": "konoebml",
"version": "0.1.0-rc.2",
"version": "0.1.0-rc.3",
"description": "A modern JavaScript implementation of EBML RFC8794",
"main": "./dist/index.cjs",
"module": "./dist/index.js",

View File

@ -1,5 +1,5 @@
import { type EbmlTagIdType, isEbmlMasterTagId } from './models/enums';
import type { EbmlTagTrait } from './models/tag-trait';
import type { DecodeContentOptions, EbmlTagTrait } from './models/tag-trait';
import type { FileDataViewController } from './adapters';
import {
checkVintSafeSize,
@ -71,8 +71,9 @@ export async function decodeEbmlTagHeader(
}
export async function* decodeEbmlContent(
controller: FileDataViewController
options: DecodeContentOptions
): AsyncGenerator<EbmlTagTrait, void, unknown> {
const controller = options.dataViewController;
while (true) {
const offset = controller.getOffset();
@ -112,7 +113,7 @@ export async function* decodeEbmlContent(
parent: undefined,
});
for await (const item of tag.decodeContent(controller)) {
for await (const item of tag.decodeContent(options)) {
yield item;
}

View File

@ -1,6 +1,9 @@
import { Queue } from 'mnemonist';
import type { FileDataViewController } from './adapters';
import type { EbmlTagTrait } from './models/tag-trait';
import type {
DecodeContentCollectChildPredicate,
EbmlTagTrait,
} from './models/tag-trait';
import { decodeEbmlContent } from './decode-utils';
import { StreamFlushReason, UnreachableOrLogicError } from './errors';
import { dataViewSlice } from './tools';
@ -10,6 +13,11 @@ export type EbmlStreamDecoderChunkType =
| ArrayBuffer
| ArrayBufferLike;
export interface EbmlDecodeStreamTransformerOptions {
collectChild?: DecodeContentCollectChildPredicate;
streamStartOffset?: number;
}
export class EbmlDecodeStreamTransformer
implements
Transformer<EbmlStreamDecoderChunkType, EbmlTagTrait>,
@ -23,6 +31,11 @@ export class EbmlDecodeStreamTransformer
private _tickIdleCallback: VoidFunction | undefined;
private _currentTask: Promise<void> | undefined;
private _writeBuffer = new Queue<EbmlTagTrait>();
public readonly options: EbmlDecodeStreamTransformerOptions;
constructor(options: EbmlDecodeStreamTransformerOptions = {}) {
this.options = options;
}
public getBuffer(): Uint8Array {
return this._buffer;
@ -171,7 +184,10 @@ export class EbmlDecodeStreamTransformer
if (!this._currentTask && !isFlush) {
const decode = async () => {
try {
for await (const tag of decodeEbmlContent(this)) {
for await (const tag of decodeEbmlContent({
collectChild: this.options.collectChild,
dataViewController: this,
})) {
this.tryEnqueueToBuffer(tag);
}
this._currentTask = undefined;
@ -189,7 +205,7 @@ export class EbmlDecodeStreamTransformer
}
async start(ctrl: TransformStreamDefaultController<EbmlTagTrait>) {
this._offset = 0;
this._offset = this.options.streamStartOffset ?? 0;
this._buffer = new Uint8Array(0);
this._requests.clear();
this._tickIdleCallback = undefined;
@ -223,14 +239,17 @@ export class EbmlDecodeStreamTransformer
}
}
export interface EbmlStreamDecoderOptions
extends EbmlDecodeStreamTransformerOptions {}
export class EbmlStreamDecoder extends TransformStream<
EbmlStreamDecoderChunkType,
EbmlTagTrait
> {
public readonly transformer: EbmlDecodeStreamTransformer;
constructor() {
const transformer = new EbmlDecodeStreamTransformer();
constructor(options: EbmlStreamDecoderOptions = {}) {
const transformer = new EbmlDecodeStreamTransformer(options);
super(transformer);
this.transformer = transformer;
}

View File

@ -1,8 +1,22 @@
export { EbmlBlockTag } from './models/tag-block';
export { EbmlDataTag } from './models/tag-data';
export { EbmlMasterTag } from './models/tag-master';
export { EbmlSimpleBlockTag } from './models/tag-simple-block';
export { EbmlTagTrait } from './models/tag-trait';
export {
EbmlBlockTag,
type CreateEbmlBlockTagOptions,
} from './models/tag-block';
export { EbmlDataTag, type CreateEbmlDataTagOptions } from './models/tag-data';
export {
EbmlMasterTag,
type CreateEbmlMasterTagOptions,
} from './models/tag-master';
export {
EbmlSimpleBlockTag,
type CreateEbmlSimpleBlockTagOptions,
} from './models/tag-simple-block';
export {
EbmlTagTrait,
type DecodeContentCollectChildPredicate,
type DecodeContentOptions,
type CreateEbmlTagOptions,
} from './models/tag-trait';
export {
createEbmlTag,
createEbmlTagForManuallyBuild,
@ -15,6 +29,8 @@ export {
EbmlStreamDecoder,
EbmlDecodeStreamTransformer,
type EbmlStreamDecoderChunkType,
type EbmlStreamDecoderOptions,
type EbmlDecodeStreamTransformerOptions,
} from './decoder';
export {
EbmlStreamEncoder,

View File

@ -14,7 +14,7 @@ import {
EbmlTagIdEnum,
} from './enums';
import { EbmlElementType } from './enums';
import type { FileDataViewController } from '../adapters';
import type { DecodeContentOptions } from './tag-trait';
export interface CreateEbmlBlockTagOptions
extends Omit<CreateEbmlDataTagOptions, 'id' | 'type'> {
@ -77,7 +77,8 @@ export class EbmlBlockTag extends EbmlDataTag {
}
// biome-ignore lint/correctness/useYield: <explanation>
async *decodeContentImpl(controller: FileDataViewController) {
async *decodeContentImpl(options: DecodeContentOptions) {
const controller = options.dataViewController;
const offset = controller.getOffset();
const view = await controller.read(offset, this.contentLength, true);
const track = readVint(view)!;

View File

@ -1,7 +1,10 @@
import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait';
import {
type CreateEbmlTagOptions,
type DecodeContentOptions,
EbmlTagTrait,
} from './tag-trait';
import { EbmlElementType } from './enums';
import {
dataViewSlice,
dataViewSliceToBuf,
readAscii,
readFloat,
@ -15,7 +18,6 @@ import {
writeUtf8,
} from '../tools';
import { EbmlTagPosition } from './enums';
import type { FileDataViewController } from 'src/adapters';
export type CreateEbmlDataTagOptions = Omit<CreateEbmlTagOptions, 'position'>;
@ -30,7 +32,8 @@ export class EbmlDataTag extends EbmlTagTrait {
}
// biome-ignore lint/correctness/useYield: <explanation>
override async *decodeContentImpl(controller: FileDataViewController) {
override async *decodeContentImpl(options: DecodeContentOptions) {
const controller = options.dataViewController;
const offset = controller.getOffset();
const view = await controller.read(offset, this.contentLength, true);
switch (this.type) {

View File

@ -1,9 +1,12 @@
import { type CreateEbmlTagOptions, EbmlTagTrait } from './tag-trait';
import {
type CreateEbmlTagOptions,
type DecodeContentOptions,
EbmlTagTrait,
} from './tag-trait';
import { EbmlElementType, EbmlTagPosition, isEbmlMasterTagId } from './enums';
import { decodeEbmlTagHeader } from '../decode-utils';
import { createEbmlTag } from 'src/factory';
import type { EbmlMasterTagIdType } from './enums';
import type { FileDataViewController } from '../adapters';
export interface CreateEbmlMasterTagOptions
extends Omit<CreateEbmlTagOptions, 'position' | 'type' | 'id'> {
@ -35,7 +38,9 @@ export class EbmlMasterTag extends EbmlTagTrait {
}
}
async *decodeContentImpl(controller: FileDataViewController) {
async *decodeContentImpl(options: DecodeContentOptions) {
const controller = options.dataViewController;
const collectChild = options.collectChild;
while (true) {
const offset = controller.getOffset();
@ -79,13 +84,22 @@ export class EbmlMasterTag extends EbmlTagTrait {
parent: this,
});
for await (const item of tag.decodeContent(controller)) {
for await (const item of tag.decodeContent(options)) {
yield item;
}
tag.endOffset = controller.getOffset();
this._children.push(tag);
let shouldCollectChild: boolean;
if (typeof collectChild === 'function') {
shouldCollectChild = !!collectChild(tag, this);
} else {
shouldCollectChild = !!collectChild;
}
if (shouldCollectChild) {
this._children.push(tag);
}
yield tag;
}

View File

@ -1,7 +1,7 @@
import { readVint } from '../tools';
import { type CreateEbmlBlockTagOptions, EbmlBlockTag } from './tag-block';
import type { EbmlSimpleBlockTagIdType } from './enums';
import type { FileDataViewController } from '../adapters';
import type { DecodeContentOptions } from './tag-trait';
export interface CreateEbmlSimpleBlockTagOptions
extends Omit<CreateEbmlBlockTagOptions, 'id'> {
@ -33,12 +33,13 @@ export class EbmlSimpleBlockTag extends EbmlBlockTag {
yield this.payload;
}
async *decodeContentImpl(controller: FileDataViewController) {
async *decodeContentImpl(options: DecodeContentOptions) {
const controller = options.dataViewController;
const offset = controller.getOffset();
const view = await controller.read(offset, this.contentLength, true);
for await (const item of super.decodeContentImpl(controller)) {
for await (const item of super.decodeContentImpl(options)) {
yield item;
}

View File

@ -4,6 +4,7 @@ import type { EbmlElementType } from './enums';
import { hexStringToBuf, UNKNOWN_SIZE_VINT_BUF, writeVint } from '../tools';
import type { FileDataViewController } from '../adapters';
import { InconsistentOffsetOnDecodingContentError } from '../errors';
import type { EbmlMasterTag } from './tag-master';
export interface CreateEbmlTagOptions {
id: EbmlTagIdType;
@ -16,6 +17,15 @@ export interface CreateEbmlTagOptions {
parent?: EbmlTagTrait;
}
export type DecodeContentCollectChildPredicate =
| boolean
| ((child: EbmlTagTrait, parent: EbmlMasterTag) => boolean);
export interface DecodeContentOptions {
collectChild?: DecodeContentCollectChildPredicate;
dataViewController: FileDataViewController;
}
export abstract class EbmlTagTrait {
/**
* The id of the EBML tag.
@ -117,7 +127,7 @@ export abstract class EbmlTagTrait {
* @param controller DataView controller, simulate async filesystem file
*/
protected abstract decodeContentImpl(
controller: FileDataViewController
options: DecodeContentOptions
): AsyncGenerator<EbmlTagTrait, void, unknown>;
/**
@ -126,13 +136,14 @@ export abstract class EbmlTagTrait {
* @returns Deep traversal async iterators of all descendants
*/
public async *decodeContent(
controller: FileDataViewController
options: DecodeContentOptions
): AsyncGenerator<EbmlTagTrait, void, unknown> {
const controller = options.dataViewController;
if (this.contentLength === 0 || this.position === EbmlTagPosition.Start) {
return;
}
const startOffset = controller.getOffset();
for await (const tag of this.decodeContentImpl(controller)) {
for await (const tag of this.decodeContentImpl(options)) {
yield tag;
}
const endOffset = controller.getOffset();