@sarakusha/ebml 0.0.4 → 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.
@@ -52,77 +52,96 @@ var VideoFrameGenerator = class {
52
52
  constructor(config, maxPreloadFrames = MAX_PRELOAD_FRAMES) {
53
53
  this.config = config;
54
54
  const pendingFrames = [];
55
- const push = new Semaphore(maxPreloadFrames);
56
- const pull = new Semaphore(0);
55
+ const capacity = new Semaphore(maxPreloadFrames);
56
+ let readableController;
57
57
  let decoder;
58
- let cancel;
59
- let abort;
60
58
  let finished = false;
61
- let clearing = false;
59
+ const closeDecoder = () => {
60
+ if (decoder && decoder.state !== "closed") decoder.close();
61
+ };
62
62
  const clear = () => {
63
- clearing = true;
64
63
  pendingFrames.splice(0).forEach((frame) => frame.close());
65
- push.purge();
66
- pull.purge();
64
+ capacity.purge();
67
65
  };
68
- this.readable = new ReadableStream({
69
- pull: async (controller) => {
70
- if (abort || finished && pendingFrames.length === 0) controller.close();
71
- else try {
72
- await pull.acquire();
73
- const frame = pendingFrames.shift();
74
- if (frame) controller.enqueue(frame);
75
- push.release();
76
- } catch (e) {
77
- controller.error(e);
66
+ const drain = () => {
67
+ if (!readableController) return;
68
+ while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {
69
+ const frame = pendingFrames.shift();
70
+ if (frame) {
71
+ readableController.enqueue(frame);
72
+ capacity.release();
78
73
  }
74
+ }
75
+ if (finished && pendingFrames.length === 0) {
76
+ readableController.close();
77
+ readableController = void 0;
78
+ }
79
+ };
80
+ const fail = (reason) => {
81
+ clear();
82
+ closeDecoder();
83
+ readableController?.error(reason);
84
+ readableController = void 0;
85
+ };
86
+ this.readable = new ReadableStream({
87
+ start: (controller) => {
88
+ readableController = controller;
89
+ },
90
+ pull: () => {
91
+ drain();
79
92
  },
80
93
  cancel: (reason) => {
81
- cancel = reason;
82
- clear();
94
+ fail(reason);
83
95
  }
84
96
  }, new CountQueuingStrategy({ highWaterMark: 1 }));
85
97
  this.writable = new WritableStream({
86
98
  start: async (controller) => {
87
99
  decoder = new VideoDecoder({
88
- output: async (frame) => {
100
+ output: (frame) => {
89
101
  pendingFrames.push(frame);
90
- pull.release();
102
+ drain();
91
103
  },
92
104
  error: (err) => {
93
105
  console.error("error while decode", err);
94
106
  controller.error(err);
107
+ fail(err);
95
108
  }
96
109
  });
97
110
  try {
98
111
  decoder.configure(await this.config);
99
112
  } catch (err) {
100
113
  controller.error(err);
114
+ fail(err);
101
115
  console.error("error while configure", err);
102
116
  }
103
117
  },
104
118
  write: async (chunk, controller) => {
105
- if (cancel) controller.error(cancel);
106
- else try {
107
- await push.acquire();
119
+ try {
120
+ await capacity.acquire();
121
+ if (!decoder || decoder.state === "closed") {
122
+ controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
123
+ return;
124
+ }
108
125
  decoder.decode(chunk);
109
126
  } catch (e) {
110
- if (!clearing) {
127
+ if (decoder?.state !== "closed") {
111
128
  console.error("error while decode chunk", e);
112
129
  controller.error(e);
113
130
  }
114
131
  }
115
132
  },
116
133
  close: async () => {
117
- await decoder.flush();
118
- decoder.close();
119
- finished = true;
120
- clear();
134
+ try {
135
+ await decoder?.flush();
136
+ closeDecoder();
137
+ finished = true;
138
+ drain();
139
+ } catch (err) {
140
+ fail(err);
141
+ }
121
142
  },
122
143
  abort: (reason) => {
123
- abort = reason;
124
- decoder.close();
125
- clear();
144
+ fail(reason);
126
145
  }
127
146
  });
128
147
  }
@@ -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 push = new Semaphore(maxPreloadFrames);\n const pull = new Semaphore(0);\n let decoder: VideoDecoder;\n let cancel: unknown;\n let abort: unknown;\n let finished = false;\n let clearing = false;\n const clear = () => {\n clearing = true;\n pendingFrames.splice(0).forEach((frame) => frame.close());\n push.purge();\n pull.purge();\n };\n this.readable = new ReadableStream<VideoFrame>(\n {\n pull: async (controller) => {\n if (abort || (finished && pendingFrames.length === 0)) controller.close();\n else {\n try {\n await pull.acquire();\n const frame = pendingFrames.shift();\n if (frame) {\n controller.enqueue(frame);\n }\n push.release();\n } catch (e) {\n controller.error(e);\n }\n }\n },\n cancel: (reason) => {\n cancel = reason;\n clear();\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: async (frame) => {\n pendingFrames.push(frame);\n pull.release();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n if (cancel) controller.error(cancel);\n else {\n try {\n await push.acquire();\n decoder.decode(chunk);\n } catch (e) {\n if (!clearing) {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n }\n },\n close: async () => {\n await decoder.flush();\n decoder.close();\n finished = true;\n clear();\n },\n abort: (reason) => {\n abort = reason;\n decoder.close();\n clear();\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,OAAO,IAAI,UAAU,gBAAgB;EAC3C,MAAM,OAAO,IAAI,UAAU,CAAC;EAC5B,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,WAAW;EACf,IAAI,WAAW;EACf,MAAM,cAAc;GAClB,WAAW;GACX,cAAc,OAAO,CAAC,EAAE,SAAS,UAAU,MAAM,MAAM,CAAC;GACxD,KAAK,MAAM;GACX,KAAK,MAAM;EACb;EACA,KAAK,WAAW,IAAI,eAClB;GACE,MAAM,OAAO,eAAe;IAC1B,IAAI,SAAU,YAAY,cAAc,WAAW,GAAI,WAAW,MAAM;SAEtE,IAAI;KACF,MAAM,KAAK,QAAQ;KACnB,MAAM,QAAQ,cAAc,MAAM;KAClC,IAAI,OACF,WAAW,QAAQ,KAAK;KAE1B,KAAK,QAAQ;IACf,SAAS,GAAG;KACV,WAAW,MAAM,CAAC;IACpB;GAEJ;GACA,SAAS,WAAW;IAClB,SAAS;IACT,MAAM;GACR;EACF,GACA,IAAI,qBAAqB,EAAE,eAAe,EAAE,CAAC,CAC/C;EACA,KAAK,WAAW,IAAI,eAAe;GACjC,OAAO,OAAO,eAAe;IAC3B,UAAU,IAAI,aAAa;KACzB,QAAQ,OAAO,UAAU;MACvB,cAAc,KAAK,KAAK;MACxB,KAAK,QAAQ;KACf;KACA,QAAQ,QAAQ;MACd,QAAQ,MAAM,sBAAsB,GAAG;MACvC,WAAW,MAAM,GAAG;KACtB;IACF,CAAC;IACD,IAAI;KACF,QAAQ,UAAU,MAAM,KAAK,MAAM;IACrC,SAAS,KAAK;KACZ,WAAW,MAAM,GAAG;KACpB,QAAQ,MAAM,yBAAyB,GAAG;IAC5C;GACF;GACA,OAAO,OAAO,OAAO,eAAe;IAClC,IAAI,QAAQ,WAAW,MAAM,MAAM;SAEjC,IAAI;KACF,MAAM,KAAK,QAAQ;KACnB,QAAQ,OAAO,KAAK;IACtB,SAAS,GAAG;KACV,IAAI,CAAC,UAAU;MACb,QAAQ,MAAM,4BAA4B,CAAC;MAC3C,WAAW,MAAM,CAAC;KACpB;IACF;GAEJ;GACA,OAAO,YAAY;IACjB,MAAM,QAAQ,MAAM;IACpB,QAAQ,MAAM;IACd,WAAW;IACX,MAAM;GACR;GACA,QAAQ,WAAW;IACjB,QAAQ;IACR,QAAQ,MAAM;IACd,MAAM;GACR;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\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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"}
@@ -48,77 +48,96 @@ var VideoFrameGenerator = class {
48
48
  constructor(config, maxPreloadFrames = MAX_PRELOAD_FRAMES) {
49
49
  this.config = config;
50
50
  const pendingFrames = [];
51
- const push = new Semaphore(maxPreloadFrames);
52
- const pull = new Semaphore(0);
51
+ const capacity = new Semaphore(maxPreloadFrames);
52
+ let readableController;
53
53
  let decoder;
54
- let cancel;
55
- let abort;
56
54
  let finished = false;
57
- let clearing = false;
55
+ const closeDecoder = () => {
56
+ if (decoder && decoder.state !== "closed") decoder.close();
57
+ };
58
58
  const clear = () => {
59
- clearing = true;
60
59
  pendingFrames.splice(0).forEach((frame) => frame.close());
61
- push.purge();
62
- pull.purge();
60
+ capacity.purge();
63
61
  };
64
- this.readable = new ReadableStream({
65
- pull: async (controller) => {
66
- if (abort || finished && pendingFrames.length === 0) controller.close();
67
- else try {
68
- await pull.acquire();
69
- const frame = pendingFrames.shift();
70
- if (frame) controller.enqueue(frame);
71
- push.release();
72
- } catch (e) {
73
- controller.error(e);
62
+ const drain = () => {
63
+ if (!readableController) return;
64
+ while (pendingFrames.length > 0 && (readableController.desiredSize ?? 0) > 0) {
65
+ const frame = pendingFrames.shift();
66
+ if (frame) {
67
+ readableController.enqueue(frame);
68
+ capacity.release();
74
69
  }
70
+ }
71
+ if (finished && pendingFrames.length === 0) {
72
+ readableController.close();
73
+ readableController = void 0;
74
+ }
75
+ };
76
+ const fail = (reason) => {
77
+ clear();
78
+ closeDecoder();
79
+ readableController?.error(reason);
80
+ readableController = void 0;
81
+ };
82
+ this.readable = new ReadableStream({
83
+ start: (controller) => {
84
+ readableController = controller;
85
+ },
86
+ pull: () => {
87
+ drain();
75
88
  },
76
89
  cancel: (reason) => {
77
- cancel = reason;
78
- clear();
90
+ fail(reason);
79
91
  }
80
92
  }, new CountQueuingStrategy({ highWaterMark: 1 }));
81
93
  this.writable = new WritableStream({
82
94
  start: async (controller) => {
83
95
  decoder = new VideoDecoder({
84
- output: async (frame) => {
96
+ output: (frame) => {
85
97
  pendingFrames.push(frame);
86
- pull.release();
98
+ drain();
87
99
  },
88
100
  error: (err) => {
89
101
  console.error("error while decode", err);
90
102
  controller.error(err);
103
+ fail(err);
91
104
  }
92
105
  });
93
106
  try {
94
107
  decoder.configure(await this.config);
95
108
  } catch (err) {
96
109
  controller.error(err);
110
+ fail(err);
97
111
  console.error("error while configure", err);
98
112
  }
99
113
  },
100
114
  write: async (chunk, controller) => {
101
- if (cancel) controller.error(cancel);
102
- else try {
103
- await push.acquire();
115
+ try {
116
+ await capacity.acquire();
117
+ if (!decoder || decoder.state === "closed") {
118
+ controller.error(/* @__PURE__ */ new Error("VideoDecoder is closed."));
119
+ return;
120
+ }
104
121
  decoder.decode(chunk);
105
122
  } catch (e) {
106
- if (!clearing) {
123
+ if (decoder?.state !== "closed") {
107
124
  console.error("error while decode chunk", e);
108
125
  controller.error(e);
109
126
  }
110
127
  }
111
128
  },
112
129
  close: async () => {
113
- await decoder.flush();
114
- decoder.close();
115
- finished = true;
116
- clear();
130
+ try {
131
+ await decoder?.flush();
132
+ closeDecoder();
133
+ finished = true;
134
+ drain();
135
+ } catch (err) {
136
+ fail(err);
137
+ }
117
138
  },
118
139
  abort: (reason) => {
119
- abort = reason;
120
- decoder.close();
121
- clear();
140
+ fail(reason);
122
141
  }
123
142
  });
124
143
  }
@@ -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 push = new Semaphore(maxPreloadFrames);\n const pull = new Semaphore(0);\n let decoder: VideoDecoder;\n let cancel: unknown;\n let abort: unknown;\n let finished = false;\n let clearing = false;\n const clear = () => {\n clearing = true;\n pendingFrames.splice(0).forEach((frame) => frame.close());\n push.purge();\n pull.purge();\n };\n this.readable = new ReadableStream<VideoFrame>(\n {\n pull: async (controller) => {\n if (abort || (finished && pendingFrames.length === 0)) controller.close();\n else {\n try {\n await pull.acquire();\n const frame = pendingFrames.shift();\n if (frame) {\n controller.enqueue(frame);\n }\n push.release();\n } catch (e) {\n controller.error(e);\n }\n }\n },\n cancel: (reason) => {\n cancel = reason;\n clear();\n },\n },\n new CountQueuingStrategy({ highWaterMark: 1 }),\n );\n this.writable = new WritableStream({\n start: async (controller) => {\n decoder = new VideoDecoder({\n output: async (frame) => {\n pendingFrames.push(frame);\n pull.release();\n },\n error: (err) => {\n console.error('error while decode', err);\n controller.error(err);\n },\n });\n try {\n decoder.configure(await this.config);\n } catch (err) {\n controller.error(err);\n console.error('error while configure', err);\n }\n },\n write: async (chunk, controller) => {\n if (cancel) controller.error(cancel);\n else {\n try {\n await push.acquire();\n decoder.decode(chunk);\n } catch (e) {\n if (!clearing) {\n console.error('error while decode chunk', e);\n controller.error(e);\n }\n }\n }\n },\n close: async () => {\n await decoder.flush();\n decoder.close();\n finished = true;\n clear();\n },\n abort: (reason) => {\n abort = reason;\n decoder.close();\n clear();\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,OAAO,IAAI,UAAU,gBAAgB;EAC3C,MAAM,OAAO,IAAI,UAAU,CAAC;EAC5B,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI,WAAW;EACf,IAAI,WAAW;EACf,MAAM,cAAc;GAClB,WAAW;GACX,cAAc,OAAO,CAAC,EAAE,SAAS,UAAU,MAAM,MAAM,CAAC;GACxD,KAAK,MAAM;GACX,KAAK,MAAM;EACb;EACA,KAAK,WAAW,IAAI,eAClB;GACE,MAAM,OAAO,eAAe;IAC1B,IAAI,SAAU,YAAY,cAAc,WAAW,GAAI,WAAW,MAAM;SAEtE,IAAI;KACF,MAAM,KAAK,QAAQ;KACnB,MAAM,QAAQ,cAAc,MAAM;KAClC,IAAI,OACF,WAAW,QAAQ,KAAK;KAE1B,KAAK,QAAQ;IACf,SAAS,GAAG;KACV,WAAW,MAAM,CAAC;IACpB;GAEJ;GACA,SAAS,WAAW;IAClB,SAAS;IACT,MAAM;GACR;EACF,GACA,IAAI,qBAAqB,EAAE,eAAe,EAAE,CAAC,CAC/C;EACA,KAAK,WAAW,IAAI,eAAe;GACjC,OAAO,OAAO,eAAe;IAC3B,UAAU,IAAI,aAAa;KACzB,QAAQ,OAAO,UAAU;MACvB,cAAc,KAAK,KAAK;MACxB,KAAK,QAAQ;KACf;KACA,QAAQ,QAAQ;MACd,QAAQ,MAAM,sBAAsB,GAAG;MACvC,WAAW,MAAM,GAAG;KACtB;IACF,CAAC;IACD,IAAI;KACF,QAAQ,UAAU,MAAM,KAAK,MAAM;IACrC,SAAS,KAAK;KACZ,WAAW,MAAM,GAAG;KACpB,QAAQ,MAAM,yBAAyB,GAAG;IAC5C;GACF;GACA,OAAO,OAAO,OAAO,eAAe;IAClC,IAAI,QAAQ,WAAW,MAAM,MAAM;SAEjC,IAAI;KACF,MAAM,KAAK,QAAQ;KACnB,QAAQ,OAAO,KAAK;IACtB,SAAS,GAAG;KACV,IAAI,CAAC,UAAU;MACb,QAAQ,MAAM,4BAA4B,CAAC;MAC3C,WAAW,MAAM,CAAC;KACpB;IACF;GAEJ;GACA,OAAO,YAAY;IACjB,MAAM,QAAQ,MAAM;IACpB,QAAQ,MAAM;IACd,WAAW;IACX,MAAM;GACR;GACA,QAAQ,WAAW;IACjB,QAAQ;IACR,QAAQ,MAAM;IACd,MAAM;GACR;EACF,CAAC;CACH;AACF"}
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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"}