@sarakusha/ebml 0.0.5 → 0.0.7

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,22 @@
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.7](https://github.com/sarakusha/ebml/compare/v0.0.6...v0.0.7) (2026-06-26)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * log recoverable video decode errors ([e1e5e85](https://github.com/sarakusha/ebml/commit/e1e5e8512a537ec8f2774aaaff4ed58aea11369a))
11
+ * tolerate decoder frame failures ([9b1f65c](https://github.com/sarakusha/ebml/commit/9b1f65c2d0df88f5c5a004a5e94994e4a6618fb5))
12
+
13
+ ### [0.0.6](https://github.com/sarakusha/ebml/compare/v0.0.5...v0.0.6) (2026-06-25)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * не работал на смешанных (аудио+видео) данных ([c85d24f](https://github.com/sarakusha/ebml/commit/c85d24fa0424785af35849c430a57c238f2fd07e))
19
+ * parse video track by TrackNumber in VideoChunkGenerator ([ba83550](https://github.com/sarakusha/ebml/commit/ba8355026467ed8b3e1a2d5d29b68ff32bfc8b16))
20
+
5
21
  ### [0.0.5](https://github.com/sarakusha/ebml/compare/v0.0.4...v0.0.5) (2026-05-26)
6
22
 
7
23
 
@@ -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 config = {};
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
- const parseConfig = (meta) => {
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.isTrackType(element)) {
67
- trackType = element.value;
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) && required[element.name] && (trackType == null || trackType === VIDEO)) {
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 (trackType === VIDEO) {
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","isTrackType","isContentElement","isTimestamp","isDuration","isSimpleBlockElement","isBlockElement","isBlockGroupElement","isMasterElement"],"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 isTrackType,\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 trackType: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const config: Partial<VideoDecoderConfig> = {};\n const elements: Record<string, number> = {};\n const required: Record<string, keyof VideoDecoderConfig> = {\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n };\n\n const parseConfig = (meta: ContentElement): 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 };\n super({\n transform: async (element, controller) => {\n elements[element.name] = (elements[element.name] ?? 0) + 1;\n try {\n if (isTrackType(element)) {\n trackType = element.value as number;\n return;\n }\n if (\n isContentElement(element) &&\n required[element.name] &&\n (trackType == null || trackType === VIDEO)\n ) {\n parseConfig(element);\n delete required[element.name];\n if (Object.keys(required).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(config)}`);\n this.#config.resolve(config as VideoDecoderConfig);\n }\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 (trackType === VIDEO) {\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) {\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
+ {"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":";;;cA6BqB,mBAAA,SAA4B,eAAA,CAAgB,OAAA,EAAS,iBAAA;EAAA;WAG/D,QAAA;EAAA,IAEL,MAAA,CAAA,GAAU,OAAA,CAAQ,kBAAA;EAAA,SAIb,EAAA"}
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, l as isTrackType, n as isBlockElement, o as isMasterElement, r as isBlockGroupElement, s as isSimpleBlockElement } from "./Element-ChEar-fo.mjs";
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 config = {};
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
- const parseConfig = (meta) => {
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 (isTrackType(element)) {
63
- trackType = element.value;
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) && required[element.name] && (trackType == null || trackType === VIDEO)) {
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 (trackType === VIDEO) {
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 isTrackType,\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 trackType: number | undefined;\n let offset = 0;\n let block: BlockElement | undefined;\n let hasReference = false;\n let total = 0;\n const config: Partial<VideoDecoderConfig> = {};\n const elements: Record<string, number> = {};\n const required: Record<string, keyof VideoDecoderConfig> = {\n PixelWidth: 'codedWidth',\n PixelHeight: 'codedHeight',\n CodecPrivate: 'codec',\n CodecID: 'codec',\n };\n\n const parseConfig = (meta: ContentElement): 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 };\n super({\n transform: async (element, controller) => {\n elements[element.name] = (elements[element.name] ?? 0) + 1;\n try {\n if (isTrackType(element)) {\n trackType = element.value as number;\n return;\n }\n if (\n isContentElement(element) &&\n required[element.name] &&\n (trackType == null || trackType === VIDEO)\n ) {\n parseConfig(element);\n delete required[element.name];\n if (Object.keys(required).length === 0) {\n // console.info(`VideoChunkGenerator#${this.id}:config ${JSON.stringify(config)}`);\n this.#config.resolve(config as VideoDecoderConfig);\n }\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 (trackType === VIDEO) {\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) {\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
+ {"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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"}
@@ -56,6 +56,11 @@ var VideoFrameGenerator = class {
56
56
  let readableController;
57
57
  let decoder;
58
58
  let finished = false;
59
+ let acceptChunks = true;
60
+ const getErrorMessage = (err) => err instanceof Error ? err.message : String(err);
61
+ const reportRecoverableError = (message, err) => {
62
+ postMessage({ debug: `${message}: ${getErrorMessage(err)}` });
63
+ };
59
64
  const closeDecoder = () => {
60
65
  if (decoder && decoder.state !== "closed") decoder.close();
61
66
  };
@@ -77,6 +82,12 @@ var VideoFrameGenerator = class {
77
82
  readableController = void 0;
78
83
  }
79
84
  };
85
+ const finish = () => {
86
+ acceptChunks = false;
87
+ closeDecoder();
88
+ finished = true;
89
+ drain();
90
+ };
80
91
  const fail = (reason) => {
81
92
  clear();
82
93
  closeDecoder();
@@ -103,8 +114,9 @@ var VideoFrameGenerator = class {
103
114
  },
104
115
  error: (err) => {
105
116
  console.error("error while decode", err);
117
+ reportRecoverableError("recoverable decoder error, ending video source", err);
106
118
  controller.error(err);
107
- fail(err);
119
+ finish();
108
120
  }
109
121
  });
110
122
  try {
@@ -115,29 +127,31 @@ var VideoFrameGenerator = class {
115
127
  console.error("error while configure", err);
116
128
  }
117
129
  },
118
- write: async (chunk, controller) => {
130
+ write: async (chunk) => {
119
131
  try {
132
+ if (!acceptChunks) return;
120
133
  await capacity.acquire();
121
134
  if (!decoder || decoder.state === "closed") {
122
- controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
135
+ capacity.release();
123
136
  return;
124
137
  }
125
138
  decoder.decode(chunk);
126
139
  } catch (e) {
127
140
  if (decoder?.state !== "closed") {
128
141
  console.error("error while decode chunk", e);
129
- controller.error(e);
142
+ reportRecoverableError("dropping encoded video chunk after decode error", e);
143
+ capacity.release();
130
144
  }
131
145
  }
132
146
  },
133
147
  close: async () => {
134
148
  try {
135
149
  await decoder?.flush();
136
- closeDecoder();
137
- finished = true;
138
- drain();
150
+ finish();
139
151
  } catch (err) {
140
- fail(err);
152
+ console.error("error while flush decoder", err);
153
+ reportRecoverableError("recoverable decoder flush error, ending video source", err);
154
+ finish();
141
155
  }
142
156
  },
143
157
  abort: (reason) => {
@@ -1 +1 @@
1
- {"version":3,"file":"VideoFrameGenerator.cjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n fail(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n try {\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n controller.error(new Error('VideoDecoder is closed.'));\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n closeDecoder();\n finished = true;\n drain();\n } catch (err) {\n fail(err);\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VideoFrameGenerator.cjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n let acceptChunks = true;\n\n const getErrorMessage = (err: unknown): string =>\n err instanceof Error ? err.message : String(err);\n\n const reportRecoverableError = (message: string, err: unknown) => {\n postMessage({ debug: `${message}: ${getErrorMessage(err)}` });\n };\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const finish = () => {\n acceptChunks = false;\n closeDecoder();\n finished = true;\n drain();\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n reportRecoverableError('recoverable decoder error, ending video source', err);\n controller.error(err);\n finish();\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk) => {\n try {\n if (!acceptChunks) return;\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n capacity.release();\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n reportRecoverableError('dropping encoded video chunk after decode error', e);\n capacity.release();\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n finish();\n } catch (err) {\n console.error('error while flush decoder', err);\n reportRecoverableError('recoverable decoder flush error, ending video source', err);\n finish();\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -52,6 +52,11 @@ var VideoFrameGenerator = class {
52
52
  let readableController;
53
53
  let decoder;
54
54
  let finished = false;
55
+ let acceptChunks = true;
56
+ const getErrorMessage = (err) => err instanceof Error ? err.message : String(err);
57
+ const reportRecoverableError = (message, err) => {
58
+ postMessage({ debug: `${message}: ${getErrorMessage(err)}` });
59
+ };
55
60
  const closeDecoder = () => {
56
61
  if (decoder && decoder.state !== "closed") decoder.close();
57
62
  };
@@ -73,6 +78,12 @@ var VideoFrameGenerator = class {
73
78
  readableController = void 0;
74
79
  }
75
80
  };
81
+ const finish = () => {
82
+ acceptChunks = false;
83
+ closeDecoder();
84
+ finished = true;
85
+ drain();
86
+ };
76
87
  const fail = (reason) => {
77
88
  clear();
78
89
  closeDecoder();
@@ -99,8 +110,9 @@ var VideoFrameGenerator = class {
99
110
  },
100
111
  error: (err) => {
101
112
  console.error("error while decode", err);
113
+ reportRecoverableError("recoverable decoder error, ending video source", err);
102
114
  controller.error(err);
103
- fail(err);
115
+ finish();
104
116
  }
105
117
  });
106
118
  try {
@@ -111,29 +123,31 @@ var VideoFrameGenerator = class {
111
123
  console.error("error while configure", err);
112
124
  }
113
125
  },
114
- write: async (chunk, controller) => {
126
+ write: async (chunk) => {
115
127
  try {
128
+ if (!acceptChunks) return;
116
129
  await capacity.acquire();
117
130
  if (!decoder || decoder.state === "closed") {
118
- controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
131
+ capacity.release();
119
132
  return;
120
133
  }
121
134
  decoder.decode(chunk);
122
135
  } catch (e) {
123
136
  if (decoder?.state !== "closed") {
124
137
  console.error("error while decode chunk", e);
125
- controller.error(e);
138
+ reportRecoverableError("dropping encoded video chunk after decode error", e);
139
+ capacity.release();
126
140
  }
127
141
  }
128
142
  },
129
143
  close: async () => {
130
144
  try {
131
145
  await decoder?.flush();
132
- closeDecoder();
133
- finished = true;
134
- drain();
146
+ finish();
135
147
  } catch (err) {
136
- fail(err);
148
+ console.error("error while flush decoder", err);
149
+ reportRecoverableError("recoverable decoder flush error, ending video source", err);
150
+ finish();
137
151
  }
138
152
  },
139
153
  abort: (reason) => {
@@ -1 +1 @@
1
- {"version":3,"file":"VideoFrameGenerator.mjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n fail(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n try {\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n controller.error(new Error('VideoDecoder is closed.'));\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n closeDecoder();\n finished = true;\n drain();\n } catch (err) {\n fail(err);\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"VideoFrameGenerator.mjs","names":["#waiting","#counter"],"sources":["../src/Semaphore.ts","../src/VideoFrameGenerator.ts"],"sourcesContent":["type WaitingPromise = { resolve: () => void; reject: (err: Error) => void };\n\nexport default class Semaphore {\n #counter = 0;\n\n #waiting: WaitingPromise[] = [];\n\n constructor(readonly max = 1) {}\n\n protected take(): boolean {\n const promise = this.#waiting.shift();\n if (promise) {\n promise.resolve();\n return false;\n }\n return true;\n }\n\n acquire(): Promise<void> {\n if (this.#counter < this.max) {\n this.#counter += 1;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.#waiting.push({\n resolve,\n reject,\n });\n });\n }\n\n release(): void {\n if (this.take()) this.#counter -= 1;\n }\n\n purge() {\n const unresolved = this.#waiting.splice(0);\n unresolved.forEach(({ reject }) => {\n reject(new Error('Task has been purged.'));\n });\n this.#counter = 0;\n return unresolved.length;\n }\n}\n","import Semaphore from './Semaphore';\n\nconst MAX_PRELOAD_FRAMES = 10;\n\nexport default class VideoFrameGenerator implements TransformStream<EncodedVideoChunk, VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly writable: WritableStream<EncodedVideoChunk>;\n\n constructor(\n readonly config: Promise<VideoDecoderConfig>,\n maxPreloadFrames: number = MAX_PRELOAD_FRAMES,\n ) {\n const pendingFrames: VideoFrame[] = [];\n const capacity = new Semaphore(maxPreloadFrames);\n let readableController: ReadableStreamDefaultController<VideoFrame> | undefined;\n let decoder: VideoDecoder | undefined;\n let finished = false;\n let acceptChunks = true;\n\n const getErrorMessage = (err: unknown): string =>\n err instanceof Error ? err.message : String(err);\n\n const reportRecoverableError = (message: string, err: unknown) => {\n postMessage({ debug: `${message}: ${getErrorMessage(err)}` });\n };\n\n const closeDecoder = () => {\n if (decoder && decoder.state !== 'closed') decoder.close();\n };\n\n const clear = () => {\n pendingFrames.splice(0).forEach((frame) => frame.close());\n capacity.purge();\n };\n\n const drain = () => {\n if (!readableController) return;\n while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {\n const frame = pendingFrames.shift();\n if (frame) {\n readableController.enqueue(frame);\n capacity.release();\n }\n }\n if (finished && pendingFrames.length === 0) {\n readableController.close();\n readableController = undefined;\n }\n };\n\n const finish = () => {\n acceptChunks = false;\n closeDecoder();\n finished = true;\n drain();\n };\n\n const fail = (reason: unknown) => {\n clear();\n closeDecoder();\n readableController?.error(reason);\n readableController = undefined;\n };\n\n this.readable = new ReadableStream<VideoFrame>(\n {\n start: (controller) => {\n readableController = controller;\n },\n pull: () => {\n drain();\n },\n cancel: (reason) => {\n fail(reason);\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: (frame) => {\n pendingFrames.push(frame);\n drain();\n },\n error: (err) => {\n console.error('error while decode', err);\n reportRecoverableError('recoverable decoder error, ending video source', err);\n controller.error(err);\n finish();\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n fail(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk) => {\n try {\n if (!acceptChunks) return;\n await capacity.acquire();\n if (!decoder || decoder.state === 'closed') {\n capacity.release();\n return;\n }\n decoder.decode(chunk);\n } catch (e) {\n if (decoder?.state !== 'closed') {\n console.error('error while decode chunk', e);\n reportRecoverableError('dropping encoded video chunk after decode error', e);\n capacity.release();\n }\n }\n },\n close: async () => {\n try {\n await decoder?.flush();\n finish();\n } catch (err) {\n console.error('error while flush decoder', err);\n reportRecoverableError('recoverable decoder flush error, ending video source', err);\n finish();\n }\n },\n abort: (reason) => {\n fail(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarakusha/ebml",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "main": "./build/index.cjs",
6
6
  "types": "./build/index.d.mts",