@sarakusha/ebml 0.0.6 → 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,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.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
+
5
13
  ### [0.0.6](https://github.com/sarakusha/ebml/compare/v0.0.5...v0.0.6) (2026-06-25)
6
14
 
7
15
 
@@ -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":";;;;;AAEA,IAAqB,YAArB,MAA+B;CAKR;CAJrB,WAAW;CAEX,WAA6B,CAAC;CAE9B,YAAY,MAAe,GAAG;EAAT,KAAA,MAAA;CAAU;CAE/B,OAA0B;EACxB,MAAM,UAAU,KAAKA,SAAS,MAAM;EACpC,IAAI,SAAS;GACX,QAAQ,QAAQ;GAChB,OAAO;EACT;EACA,OAAO;CACT;CAEA,UAAyB;EACvB,IAAI,KAAKC,WAAW,KAAK,KAAK;GAC5B,KAAKA,YAAY;GACjB,OAAO,QAAQ,QAAQ;EACzB;EACA,OAAO,IAAI,SAAe,SAAS,WAAW;GAC5C,KAAKD,SAAS,KAAK;IACjB;IACA;GACF,CAAC;EACH,CAAC;CACH;CAEA,UAAgB;EACd,IAAI,KAAK,KAAK,GAAG,KAAKC,YAAY;CACpC;CAEA,QAAQ;EACN,MAAM,aAAa,KAAKD,SAAS,OAAO,CAAC;EACzC,WAAW,SAAS,EAAE,aAAa;GACjC,uBAAO,IAAI,MAAM,uBAAuB,CAAC;EAC3C,CAAC;EACD,KAAKC,WAAW;EAChB,OAAO,WAAW;CACpB;AACF;;;ACzCA,MAAM,qBAAqB;AAE3B,IAAqB,sBAArB,MAAmG;CAMtF;CALX;CAEA;CAEA,YACE,QACA,mBAA2B,oBAC3B;EAFS,KAAA,SAAA;EAGT,MAAM,gBAA8B,CAAC;EACrC,MAAM,WAAW,IAAI,UAAU,gBAAgB;EAC/C,IAAI;EACJ,IAAI;EACJ,IAAI,WAAW;EAEf,MAAM,qBAAqB;GACzB,IAAI,WAAW,QAAQ,UAAU,UAAU,QAAQ,MAAM;EAC3D;EAEA,MAAM,cAAc;GAClB,cAAc,OAAO,CAAC,EAAE,SAAS,UAAU,MAAM,MAAM,CAAC;GACxD,SAAS,MAAM;EACjB;EAEA,MAAM,cAAc;GAClB,IAAI,CAAC,oBAAoB;GACzB,OAAO,cAAc,SAAS,MAAM,mBAAmB,eAAe,KAAK,GAAG;IAC5E,MAAM,QAAQ,cAAc,MAAM;IAClC,IAAI,OAAO;KACT,mBAAmB,QAAQ,KAAK;KAChC,SAAS,QAAQ;IACnB;GACF;GACA,IAAI,YAAY,cAAc,WAAW,GAAG;IAC1C,mBAAmB,MAAM;IACzB,qBAAqB,KAAA;GACvB;EACF;EAEA,MAAM,QAAQ,WAAoB;GAChC,MAAM;GACN,aAAa;GACb,oBAAoB,MAAM,MAAM;GAChC,qBAAqB,KAAA;EACvB;EAEA,KAAK,WAAW,IAAI,eAClB;GACE,QAAQ,eAAe;IACrB,qBAAqB;GACvB;GACA,YAAY;IACV,MAAM;GACR;GACA,SAAS,WAAW;IAClB,KAAK,MAAM;GACb;EACF,GACA,IAAI,qBAAqB,EAAE,eAAe,EAAE,CAAC,CAC/C;EACA,KAAK,WAAW,IAAI,eAAe;GACjC,OAAO,OAAO,eAAe;IAC3B,UAAU,IAAI,aAAa;KACzB,SAAS,UAAU;MACjB,cAAc,KAAK,KAAK;MACxB,MAAM;KACR;KACA,QAAQ,QAAQ;MACd,QAAQ,MAAM,sBAAsB,GAAG;MACvC,WAAW,MAAM,GAAG;MACpB,KAAK,GAAG;KACV;IACF,CAAC;IACD,IAAI;KACF,QAAQ,UAAU,MAAM,KAAK,MAAM;IACrC,SAAS,KAAK;KACZ,WAAW,MAAM,GAAG;KACpB,KAAK,GAAG;KACR,QAAQ,MAAM,yBAAyB,GAAG;IAC5C;GACF;GACA,OAAO,OAAO,OAAO,eAAe;IAClC,IAAI;KACF,MAAM,SAAS,QAAQ;KACvB,IAAI,CAAC,WAAW,QAAQ,UAAU,UAAU;MAC1C,WAAW,sBAAM,IAAI,MAAM,yBAAyB,CAAC;MACrD;KACF;KACA,QAAQ,OAAO,KAAK;IACtB,SAAS,GAAG;KACV,IAAI,SAAS,UAAU,UAAU;MAC/B,QAAQ,MAAM,4BAA4B,CAAC;MAC3C,WAAW,MAAM,CAAC;KACpB;IACF;GACF;GACA,OAAO,YAAY;IACjB,IAAI;KACF,MAAM,SAAS,MAAM;KACrB,aAAa;KACb,WAAW;KACX,MAAM;IACR,SAAS,KAAK;KACZ,KAAK,GAAG;IACV;GACF;GACA,QAAQ,WAAW;IACjB,KAAK,MAAM;GACb;EACF,CAAC;CACH;AACF"}
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.6",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "main": "./build/index.cjs",
6
6
  "types": "./build/index.d.mts",