@sarakusha/ebml 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.6](https://github.com/sarakusha/ebml/compare/v0.0.5...v0.0.6) (2026-06-25)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* не работал на смешанных (аудио+видео) данных ([c85d24f](https://github.com/sarakusha/ebml/commit/c85d24fa0424785af35849c430a57c238f2fd07e))
|
|
11
|
+
* parse video track by TrackNumber in VideoChunkGenerator ([ba83550](https://github.com/sarakusha/ebml/commit/ba8355026467ed8b3e1a2d5d29b68ff32bfc8b16))
|
|
12
|
+
|
|
5
13
|
### [0.0.5](https://github.com/sarakusha/ebml/compare/v0.0.4...v0.0.5) (2026-05-26)
|
|
6
14
|
|
|
7
15
|
|
|
@@ -23,20 +23,23 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
23
23
|
}
|
|
24
24
|
id = Date.now().toString(16).slice(-6);
|
|
25
25
|
constructor() {
|
|
26
|
+
let inTrackEntry = false;
|
|
27
|
+
let trackNumber;
|
|
26
28
|
let trackType;
|
|
29
|
+
let videoTrackNumber;
|
|
27
30
|
let offset = 0;
|
|
28
31
|
let block;
|
|
29
32
|
let hasReference = false;
|
|
30
33
|
let total = 0;
|
|
31
|
-
const
|
|
32
|
-
const elements = {};
|
|
33
|
-
const required = {
|
|
34
|
+
const createRequired = () => ({
|
|
34
35
|
PixelWidth: "codedWidth",
|
|
35
36
|
PixelHeight: "codedHeight",
|
|
36
37
|
CodecPrivate: "codec",
|
|
37
38
|
CodecID: "codec"
|
|
38
|
-
};
|
|
39
|
-
|
|
39
|
+
});
|
|
40
|
+
let trackConfig = {};
|
|
41
|
+
let trackRequired = createRequired();
|
|
42
|
+
const parseConfig = (meta, config, required) => {
|
|
40
43
|
switch (meta.name) {
|
|
41
44
|
case "CodecID": {
|
|
42
45
|
const codec = getCodec(meta);
|
|
@@ -58,19 +61,44 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
58
61
|
if (name) config[name] = meta.value;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
64
|
+
delete required[meta.name];
|
|
61
65
|
};
|
|
66
|
+
const isTrackNumber = (element) => element.name === "TrackNumber" && require_Element.isContentElement(element);
|
|
67
|
+
const isTrackType = (element) => element.name === "TrackType" && require_Element.isContentElement(element);
|
|
68
|
+
const isVideoBlock = (candidate) => videoTrackNumber != null && candidate.track === videoTrackNumber;
|
|
62
69
|
super({
|
|
63
70
|
transform: async (element, controller) => {
|
|
64
|
-
elements[element.name] = (elements[element.name] ?? 0) + 1;
|
|
65
71
|
try {
|
|
66
|
-
if (require_Element.
|
|
67
|
-
|
|
72
|
+
if (require_Element.isMasterElement(element) && element.name === "TrackEntry") {
|
|
73
|
+
if (element.isClosing) {
|
|
74
|
+
if (trackType === VIDEO && trackNumber != null) {
|
|
75
|
+
videoTrackNumber = trackNumber;
|
|
76
|
+
if (Object.keys(trackRequired).length === 0) this.#config.resolve(trackConfig);
|
|
77
|
+
}
|
|
78
|
+
inTrackEntry = false;
|
|
79
|
+
trackNumber = void 0;
|
|
80
|
+
trackType = void 0;
|
|
81
|
+
trackConfig = {};
|
|
82
|
+
trackRequired = createRequired();
|
|
83
|
+
} else {
|
|
84
|
+
inTrackEntry = true;
|
|
85
|
+
trackNumber = void 0;
|
|
86
|
+
trackType = void 0;
|
|
87
|
+
trackConfig = {};
|
|
88
|
+
trackRequired = createRequired();
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (inTrackEntry && isTrackNumber(element)) {
|
|
93
|
+
trackNumber = Number(element.value);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (inTrackEntry && isTrackType(element)) {
|
|
97
|
+
trackType = Number(element.value);
|
|
68
98
|
return;
|
|
69
99
|
}
|
|
70
|
-
if (require_Element.isContentElement(element) &&
|
|
71
|
-
parseConfig(element);
|
|
72
|
-
delete required[element.name];
|
|
73
|
-
if (Object.keys(required).length === 0) this.#config.resolve(config);
|
|
100
|
+
if (inTrackEntry && require_Element.isContentElement(element) && trackRequired[element.name]) {
|
|
101
|
+
parseConfig(element, trackConfig, trackRequired);
|
|
74
102
|
return;
|
|
75
103
|
}
|
|
76
104
|
if (require_Element.isTimestamp(element)) {
|
|
@@ -82,7 +110,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
82
110
|
return;
|
|
83
111
|
}
|
|
84
112
|
if (require_Element.isSimpleBlockElement(element)) {
|
|
85
|
-
if (
|
|
113
|
+
if (isVideoBlock(element)) {
|
|
86
114
|
controller.enqueue(new EncodedVideoChunk({
|
|
87
115
|
type: element.keyframe ? "key" : "delta",
|
|
88
116
|
timestamp: (offset + element.value) * 1e3,
|
|
@@ -101,7 +129,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
101
129
|
return;
|
|
102
130
|
}
|
|
103
131
|
if (require_Element.isBlockGroupElement(element) && element.isClosing) {
|
|
104
|
-
if (block) {
|
|
132
|
+
if (block && isVideoBlock(block)) {
|
|
105
133
|
controller.enqueue(new EncodedVideoChunk({
|
|
106
134
|
type: hasReference ? "delta" : "key",
|
|
107
135
|
timestamp: (block.value + offset) * 1e3,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.cjs","names":["Deferred","#config","readHexString","
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.cjs","names":["Deferred","#config","readHexString","isContentElement","isMasterElement","isTimestamp","isDuration","isSimpleBlockElement","isBlockElement","isBlockGroupElement"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n} from './Element';\nimport type { BlockElement, ContentElement, Element } from './Element';\nimport { readHexString } from './tools';\n\nconst VIDEO = 1;\n\nconst getCodec = (element: ContentElement<'string' | 'utf-8'>): string | undefined => {\n switch (element.value) {\n case 'V_MPEG4/ISO/AVC':\n return 'avc1.';\n case 'V_VP9':\n return 'vp09.00.50.08';\n case 'V_VP8':\n return 'vp8'; // 'vp08.00.41.08';\n default:\n return undefined;\n }\n};\n\nexport default class VideoChunkGenerator extends TransformStream<Element, EncodedVideoChunk> {\n #config = new Deferred<VideoDecoderConfig>();\n\n readonly clusters: number[] = [];\n\n get config(): Promise<VideoDecoderConfig> {\n return this.#config.promise;\n }\n\n readonly id: string = Date.now().toString(16).slice(-6);\n\n constructor() {\n let inTrackEntry = false;\n let trackNumber: number | undefined;\n let trackType: number | undefined;\n let videoTrackNumber: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const createRequired = (): Record<string, keyof VideoDecoderConfig> => ({\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n });\n let trackConfig: Partial<VideoDecoderConfig> = {};\n let trackRequired = createRequired();\n\n const parseConfig = (\n meta: ContentElement,\n config: Partial<VideoDecoderConfig>,\n required: Record<string, keyof VideoDecoderConfig>,\n ): void => {\n switch (meta.name) {\n case 'CodecID': {\n const codec = getCodec(meta as ContentElement<'string'>);\n if (codec) {\n config.codec = codec;\n if (!codec.endsWith('.')) {\n delete required.CodecPrivate;\n }\n }\n break;\n }\n case 'CodecPrivate':\n if (config.codec === 'avc1.') {\n const codecPrivate = meta as ContentElement<'binary'>;\n config.codec += readHexString(codecPrivate.data.subarray(1, 4));\n config.description = codecPrivate.data.slice();\n }\n break;\n default: {\n const name = required[meta.name];\n if (name) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config[name] = meta.value as any;\n }\n }\n }\n delete required[meta.name];\n };\n const isTrackNumber = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackNumber' && isContentElement(element);\n\n const isTrackType = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackType' && isContentElement(element);\n\n const isVideoBlock = (candidate: { track: number }): boolean =>\n videoTrackNumber != null && candidate.track === videoTrackNumber;\n\n super({\n transform: async (element, controller) => {\n try {\n if (isMasterElement(element) && element.name === 'TrackEntry') {\n if (element.isClosing) {\n if (trackType === VIDEO && trackNumber != null) {\n videoTrackNumber = trackNumber;\n if (Object.keys(trackRequired).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(trackConfig)}`);\n this.#config.resolve(trackConfig as VideoDecoderConfig);\n }\n }\n inTrackEntry = false;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n } else {\n inTrackEntry = true;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n }\n return;\n }\n\n if (inTrackEntry && isTrackNumber(element)) {\n trackNumber = Number(element.value);\n return;\n }\n\n if (inTrackEntry && isTrackType(element)) {\n trackType = Number(element.value);\n return;\n }\n if (\n inTrackEntry &&\n isContentElement(element) &&\n trackRequired[element.name]\n ) {\n parseConfig(element, trackConfig, trackRequired);\n return;\n }\n\n if (isTimestamp(element)) {\n offset = element.value as number;\n return;\n }\n\n if (isDuration(element)) {\n postMessage({ duration: element.value / 1000 });\n return;\n }\n\n if (isSimpleBlockElement(element)) {\n if (isVideoBlock(element)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: element.keyframe ? 'key' : 'delta',\n timestamp: (offset + element.value) * 1000,\n data: element.payload,\n }),\n );\n total += 1;\n }\n return;\n }\n\n if (isBlockElement(element)) {\n block = element;\n return;\n }\n\n if (element.name === 'ReferenceBlock') {\n hasReference = true;\n return;\n }\n\n if (isBlockGroupElement(element) && element.isClosing) {\n if (block && isVideoBlock(block)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: hasReference ? 'delta' : 'key',\n timestamp: (block.value + offset) * 1000,\n data: block.payload,\n }),\n );\n total += 1;\n }\n block = undefined;\n hasReference = false;\n }\n if (isMasterElement(element) && element.name === 'Cluster' && !element.isClosing)\n this.clusters.push(total);\n } catch (err) {\n console.error(`VideoChunkGenerator#${this.id} error while ELEMENT: ${err}`);\n controller.error(err);\n this.#config.reject(err as Error);\n }\n },\n flush: (controller) => {\n // console.info(\n // `VideoChunkGenerator#${this.id}:flush total: ${total}, clusters: ${this.clusters.join()}`\n // );\n controller.terminate();\n },\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.d.mts","names":[],"sources":["../src/VideoChunkGenerator.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.d.mts","names":[],"sources":["../src/VideoChunkGenerator.ts"],"mappings":";;;cA4BqB,mBAAA,SAA4B,eAAA,CAAgB,OAAA,EAAS,iBAAA;EAAA;WAG/D,QAAA;EAAA,IAEL,MAAA,CAAA,GAAU,OAAA,CAAQ,kBAAA;EAAA,SAIb,EAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isDuration, c as isTimestamp, i as isContentElement,
|
|
1
|
+
import { a as isDuration, c as isTimestamp, i as isContentElement, n as isBlockElement, o as isMasterElement, r as isBlockGroupElement, s as isSimpleBlockElement } from "./Element-ChEar-fo.mjs";
|
|
2
2
|
import { o as readHexString } from "./tools-CYLlbo9J.mjs";
|
|
3
3
|
import { t as Deferred } from "./Deferred-7Cu0KIje.mjs";
|
|
4
4
|
//#region src/VideoChunkGenerator.ts
|
|
@@ -19,20 +19,23 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
19
19
|
}
|
|
20
20
|
id = Date.now().toString(16).slice(-6);
|
|
21
21
|
constructor() {
|
|
22
|
+
let inTrackEntry = false;
|
|
23
|
+
let trackNumber;
|
|
22
24
|
let trackType;
|
|
25
|
+
let videoTrackNumber;
|
|
23
26
|
let offset = 0;
|
|
24
27
|
let block;
|
|
25
28
|
let hasReference = false;
|
|
26
29
|
let total = 0;
|
|
27
|
-
const
|
|
28
|
-
const elements = {};
|
|
29
|
-
const required = {
|
|
30
|
+
const createRequired = () => ({
|
|
30
31
|
PixelWidth: "codedWidth",
|
|
31
32
|
PixelHeight: "codedHeight",
|
|
32
33
|
CodecPrivate: "codec",
|
|
33
34
|
CodecID: "codec"
|
|
34
|
-
};
|
|
35
|
-
|
|
35
|
+
});
|
|
36
|
+
let trackConfig = {};
|
|
37
|
+
let trackRequired = createRequired();
|
|
38
|
+
const parseConfig = (meta, config, required) => {
|
|
36
39
|
switch (meta.name) {
|
|
37
40
|
case "CodecID": {
|
|
38
41
|
const codec = getCodec(meta);
|
|
@@ -54,19 +57,44 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
54
57
|
if (name) config[name] = meta.value;
|
|
55
58
|
}
|
|
56
59
|
}
|
|
60
|
+
delete required[meta.name];
|
|
57
61
|
};
|
|
62
|
+
const isTrackNumber = (element) => element.name === "TrackNumber" && isContentElement(element);
|
|
63
|
+
const isTrackType = (element) => element.name === "TrackType" && isContentElement(element);
|
|
64
|
+
const isVideoBlock = (candidate) => videoTrackNumber != null && candidate.track === videoTrackNumber;
|
|
58
65
|
super({
|
|
59
66
|
transform: async (element, controller) => {
|
|
60
|
-
elements[element.name] = (elements[element.name] ?? 0) + 1;
|
|
61
67
|
try {
|
|
62
|
-
if (
|
|
63
|
-
|
|
68
|
+
if (isMasterElement(element) && element.name === "TrackEntry") {
|
|
69
|
+
if (element.isClosing) {
|
|
70
|
+
if (trackType === VIDEO && trackNumber != null) {
|
|
71
|
+
videoTrackNumber = trackNumber;
|
|
72
|
+
if (Object.keys(trackRequired).length === 0) this.#config.resolve(trackConfig);
|
|
73
|
+
}
|
|
74
|
+
inTrackEntry = false;
|
|
75
|
+
trackNumber = void 0;
|
|
76
|
+
trackType = void 0;
|
|
77
|
+
trackConfig = {};
|
|
78
|
+
trackRequired = createRequired();
|
|
79
|
+
} else {
|
|
80
|
+
inTrackEntry = true;
|
|
81
|
+
trackNumber = void 0;
|
|
82
|
+
trackType = void 0;
|
|
83
|
+
trackConfig = {};
|
|
84
|
+
trackRequired = createRequired();
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (inTrackEntry && isTrackNumber(element)) {
|
|
89
|
+
trackNumber = Number(element.value);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (inTrackEntry && isTrackType(element)) {
|
|
93
|
+
trackType = Number(element.value);
|
|
64
94
|
return;
|
|
65
95
|
}
|
|
66
|
-
if (isContentElement(element) &&
|
|
67
|
-
parseConfig(element);
|
|
68
|
-
delete required[element.name];
|
|
69
|
-
if (Object.keys(required).length === 0) this.#config.resolve(config);
|
|
96
|
+
if (inTrackEntry && isContentElement(element) && trackRequired[element.name]) {
|
|
97
|
+
parseConfig(element, trackConfig, trackRequired);
|
|
70
98
|
return;
|
|
71
99
|
}
|
|
72
100
|
if (isTimestamp(element)) {
|
|
@@ -78,7 +106,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
78
106
|
return;
|
|
79
107
|
}
|
|
80
108
|
if (isSimpleBlockElement(element)) {
|
|
81
|
-
if (
|
|
109
|
+
if (isVideoBlock(element)) {
|
|
82
110
|
controller.enqueue(new EncodedVideoChunk({
|
|
83
111
|
type: element.keyframe ? "key" : "delta",
|
|
84
112
|
timestamp: (offset + element.value) * 1e3,
|
|
@@ -97,7 +125,7 @@ var VideoChunkGenerator = class extends TransformStream {
|
|
|
97
125
|
return;
|
|
98
126
|
}
|
|
99
127
|
if (isBlockGroupElement(element) && element.isClosing) {
|
|
100
|
-
if (block) {
|
|
128
|
+
if (block && isVideoBlock(block)) {
|
|
101
129
|
controller.enqueue(new EncodedVideoChunk({
|
|
102
130
|
type: hasReference ? "delta" : "key",
|
|
103
131
|
timestamp: (block.value + offset) * 1e3,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoChunkGenerator.mjs","names":["#config"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n
|
|
1
|
+
{"version":3,"file":"VideoChunkGenerator.mjs","names":["#config"],"sources":["../src/VideoChunkGenerator.ts"],"sourcesContent":["import Deferred from './Deferred';\nimport {\n isBlockElement,\n isBlockGroupElement,\n isContentElement,\n isDuration,\n isMasterElement,\n isSimpleBlockElement,\n isTimestamp,\n} from './Element';\nimport type { BlockElement, ContentElement, Element } from './Element';\nimport { readHexString } from './tools';\n\nconst VIDEO = 1;\n\nconst getCodec = (element: ContentElement<'string' | 'utf-8'>): string | undefined => {\n switch (element.value) {\n case 'V_MPEG4/ISO/AVC':\n return 'avc1.';\n case 'V_VP9':\n return 'vp09.00.50.08';\n case 'V_VP8':\n return 'vp8'; // 'vp08.00.41.08';\n default:\n return undefined;\n }\n};\n\nexport default class VideoChunkGenerator extends TransformStream<Element, EncodedVideoChunk> {\n #config = new Deferred<VideoDecoderConfig>();\n\n readonly clusters: number[] = [];\n\n get config(): Promise<VideoDecoderConfig> {\n return this.#config.promise;\n }\n\n readonly id: string = Date.now().toString(16).slice(-6);\n\n constructor() {\n let inTrackEntry = false;\n let trackNumber: number | undefined;\n let trackType: number | undefined;\n let videoTrackNumber: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const createRequired = (): Record<string, keyof VideoDecoderConfig> => ({\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n });\n let trackConfig: Partial<VideoDecoderConfig> = {};\n let trackRequired = createRequired();\n\n const parseConfig = (\n meta: ContentElement,\n config: Partial<VideoDecoderConfig>,\n required: Record<string, keyof VideoDecoderConfig>,\n ): void => {\n switch (meta.name) {\n case 'CodecID': {\n const codec = getCodec(meta as ContentElement<'string'>);\n if (codec) {\n config.codec = codec;\n if (!codec.endsWith('.')) {\n delete required.CodecPrivate;\n }\n }\n break;\n }\n case 'CodecPrivate':\n if (config.codec === 'avc1.') {\n const codecPrivate = meta as ContentElement<'binary'>;\n config.codec += readHexString(codecPrivate.data.subarray(1, 4));\n config.description = codecPrivate.data.slice();\n }\n break;\n default: {\n const name = required[meta.name];\n if (name) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n config[name] = meta.value as any;\n }\n }\n }\n delete required[meta.name];\n };\n const isTrackNumber = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackNumber' && isContentElement(element);\n\n const isTrackType = (element: Element): element is ContentElement<'uinteger'> =>\n element.name === 'TrackType' && isContentElement(element);\n\n const isVideoBlock = (candidate: { track: number }): boolean =>\n videoTrackNumber != null && candidate.track === videoTrackNumber;\n\n super({\n transform: async (element, controller) => {\n try {\n if (isMasterElement(element) && element.name === 'TrackEntry') {\n if (element.isClosing) {\n if (trackType === VIDEO && trackNumber != null) {\n videoTrackNumber = trackNumber;\n if (Object.keys(trackRequired).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(trackConfig)}`);\n this.#config.resolve(trackConfig as VideoDecoderConfig);\n }\n }\n inTrackEntry = false;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n } else {\n inTrackEntry = true;\n trackNumber = undefined;\n trackType = undefined;\n trackConfig = {};\n trackRequired = createRequired();\n }\n return;\n }\n\n if (inTrackEntry && isTrackNumber(element)) {\n trackNumber = Number(element.value);\n return;\n }\n\n if (inTrackEntry && isTrackType(element)) {\n trackType = Number(element.value);\n return;\n }\n if (\n inTrackEntry &&\n isContentElement(element) &&\n trackRequired[element.name]\n ) {\n parseConfig(element, trackConfig, trackRequired);\n return;\n }\n\n if (isTimestamp(element)) {\n offset = element.value as number;\n return;\n }\n\n if (isDuration(element)) {\n postMessage({ duration: element.value / 1000 });\n return;\n }\n\n if (isSimpleBlockElement(element)) {\n if (isVideoBlock(element)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: element.keyframe ? 'key' : 'delta',\n timestamp: (offset + element.value) * 1000,\n data: element.payload,\n }),\n );\n total += 1;\n }\n return;\n }\n\n if (isBlockElement(element)) {\n block = element;\n return;\n }\n\n if (element.name === 'ReferenceBlock') {\n hasReference = true;\n return;\n }\n\n if (isBlockGroupElement(element) && element.isClosing) {\n if (block && isVideoBlock(block)) {\n controller.enqueue(\n new EncodedVideoChunk({\n type: hasReference ? 'delta' : 'key',\n timestamp: (block.value + offset) * 1000,\n data: block.payload,\n }),\n );\n total += 1;\n }\n block = undefined;\n hasReference = false;\n }\n if (isMasterElement(element) && element.name === 'Cluster' && !element.isClosing)\n this.clusters.push(total);\n } catch (err) {\n console.error(`VideoChunkGenerator#${this.id} error while ELEMENT: ${err}`);\n controller.error(err);\n this.#config.reject(err as Error);\n }\n },\n flush: (controller) => {\n // console.info(\n // `VideoChunkGenerator#${this.id}:flush total: ${total}, clusters: ${this.clusters.join()}`\n // );\n controller.terminate();\n },\n });\n }\n}\n"],"mappings":"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"}
|