@webex/ladon-ts 5.9.0 → 5.9.2

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,20 @@
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
+ ### 5.9.2 (2025-07-23)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * return existing destroyPromise in destroyWorker ([#79](https://github.com/beyonddunbar/ladon-ts/issues/79)) ([2a3af1a](https://github.com/beyonddunbar/ladon-ts/commit/2a3af1aa60b9dce0e41814fe8e1d027427553e5e))
11
+
12
+ ### 5.9.1 (2025-07-02)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **pipeline:** resolve mask lag regression and clarify frame data paths ([#77](https://github.com/beyonddunbar/ladon-ts/issues/77)) ([5e7a3a8](https://github.com/beyonddunbar/ladon-ts/commit/5e7a3a840f8d478ee7d62bb19275653ffe152766))
18
+
5
19
  ## 5.9.0 (2025-07-01)
6
20
 
7
21
 
@@ -24,6 +24,8 @@ export declare class Pipeline implements IPipeline {
24
24
  private isProcessingFrame;
25
25
  /** Promise for the currently pending frame processing operation. */
26
26
  private pendingFramePromise;
27
+ /** Buffer for frame data copies to ensure they are not modified or garbage collected. */
28
+ private frameDataBuffer?;
27
29
  /**
28
30
  * Create a new pipeline instance.
29
31
  *
@@ -63,6 +65,10 @@ export declare class Pipeline implements IPipeline {
63
65
  * @returns The frame data as a Uint8ClampedArray.
64
66
  */
65
67
  private getInputFrameData;
68
+ /**
69
+ * Create a copy of frame data to prevent modifications during async operations.
70
+ */
71
+ private createFrameDataCopy;
66
72
  /**
67
73
  * Resize the image data if the model expects a different input size.
68
74
  * Handles scaling between input dimensions and model requirements.
@@ -1,9 +1,9 @@
1
- import { WorkerGenerator as g } from "./worker-generator.js";
2
- import { BlurRenderer as f } from "./renderer/blur-renderer.js";
3
- import { PassthroughRenderer as w } from "./renderer/passthrough-renderer.js";
1
+ import { WorkerGenerator as f } from "./worker-generator.js";
2
+ import { BlurRenderer as w } from "./renderer/blur-renderer.js";
3
+ import { PassthroughRenderer as F } from "./renderer/passthrough-renderer.js";
4
4
  import { ReplacementRenderer as k } from "./renderer/replacement-renderer.js";
5
- import { resizeImage as F } from "./util.js";
6
- class M {
5
+ import { resizeImage as y } from "./util.js";
6
+ class A {
7
7
  /**
8
8
  * Create a new pipeline instance.
9
9
  *
@@ -13,15 +13,15 @@ class M {
13
13
  * @param inputVideoElement - Video element serving as the input source.
14
14
  * @throws Error if the input canvas 2D context cannot be obtained.
15
15
  */
16
- constructor(t, e, s, n) {
17
- this.inputCanvas = t, this.outputCanvas = e, this.config = s, this.inputVideoElement = n, this.loaded = !1, this.isProcessingFrame = !1, this.pendingFramePromise = null;
18
- const r = this.inputCanvas.getContext("2d", {
16
+ constructor(e, t, r, n) {
17
+ this.inputCanvas = e, this.outputCanvas = t, this.config = r, this.inputVideoElement = n, this.loaded = !1, this.isProcessingFrame = !1, this.pendingFramePromise = null;
18
+ const s = this.inputCanvas.getContext("2d", {
19
19
  alpha: !1,
20
20
  willReadFrequently: !0
21
21
  });
22
- if (!r)
22
+ if (!s)
23
23
  throw new Error("[ladon-ts] Error getting input context 2d");
24
- this.inputCanvasContext = r;
24
+ this.inputCanvasContext = s;
25
25
  }
26
26
  /**
27
27
  * Process a single frame and return the inference result.
@@ -34,13 +34,13 @@ class M {
34
34
  * @returns Promise resolving to the frame processing result with timing statistics.
35
35
  * @throws Error if the pipeline (generator or renderer) is not loaded.
36
36
  */
37
- async onFrame(t, e = !0, s) {
37
+ async onFrame(e, t = !0, r) {
38
38
  this.isProcessingFrame && this.pendingFramePromise && await this.pendingFramePromise, this.isProcessingFrame = !0;
39
39
  try {
40
40
  return this.pendingFramePromise = this.processFrameInternal(
41
- t,
42
41
  e,
43
- s
42
+ t,
43
+ r
44
44
  ), await this.pendingFramePromise;
45
45
  } finally {
46
46
  this.isProcessingFrame = !1, this.pendingFramePromise = null;
@@ -56,29 +56,30 @@ class M {
56
56
  * @returns Promise resolving to the frame processing result with timing statistics.
57
57
  * @throws Error if the pipeline (generator or renderer) is not loaded.
58
58
  */
59
- async processFrameInternal(t, e = !0, s) {
59
+ async processFrameInternal(e, t = !0, r) {
60
60
  const n = performance.now();
61
61
  if (!this.generator || !this.renderer)
62
62
  throw new Error("[ladon-ts] Pipeline is not loaded.");
63
- let r = 0, a = 0;
64
- if (e || !this.lastMaskResult) {
65
- const c = performance.now();
66
- s ?? (s = this.getInputFrameData()), r = performance.now() - c;
67
- const m = performance.now(), u = s, p = this.getMaskImageData(u);
68
- await this.generator.process(p, t), this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(t), a = performance.now() - m;
69
- }
70
- const h = performance.now();
71
- await this.renderer.render(this.inputVideoElement, this.lastMaskResult);
72
- const d = performance.now() - h, i = performance.now(), l = i - n, o = this.createInferenceResult(t, e);
73
- return o.stats = {
74
- totalFrameTime: l,
75
- modelInferenceTime: a,
76
- preprocessingTime: r,
77
- postprocessingTime: d,
63
+ let s = 0, i = 0, a;
64
+ if (t || !this.lastMaskResult) {
65
+ const m = performance.now();
66
+ r ?? (r = this.getInputFrameData()), a = this.createFrameDataCopy(r), s = performance.now() - m;
67
+ const c = performance.now(), p = r, g = this.getMaskImageData(p);
68
+ await this.generator.process(g, e), this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(e), i = performance.now() - c;
69
+ } else
70
+ a = this.inputVideoElement;
71
+ const d = performance.now();
72
+ await this.renderer.render(a, this.lastMaskResult);
73
+ const l = performance.now() - d, o = performance.now(), u = o - n, h = this.createInferenceResult(e, t);
74
+ return h.stats = {
75
+ totalFrameTime: u,
76
+ modelInferenceTime: i,
77
+ preprocessingTime: s,
78
+ postprocessingTime: l,
78
79
  frameStartTime: n,
79
- frameEndTime: i,
80
- inferenceSkipped: !e
81
- }, o;
80
+ frameEndTime: o,
81
+ inferenceSkipped: !t
82
+ }, h;
82
83
  }
83
84
  /**
84
85
  * Get the raw image data from the input canvas context.
@@ -93,6 +94,13 @@ class M {
93
94
  this.config.input.height
94
95
  ).data;
95
96
  }
97
+ /**
98
+ * Create a copy of frame data to prevent modifications during async operations.
99
+ */
100
+ createFrameDataCopy(e) {
101
+ var t;
102
+ return e instanceof Uint8ClampedArray ? (((t = this.frameDataBuffer) == null ? void 0 : t.length) !== e.length && (this.frameDataBuffer = new Uint8ClampedArray(e.length)), this.frameDataBuffer.set(e), this.frameDataBuffer) : e;
103
+ }
96
104
  /**
97
105
  * Resize the image data if the model expects a different input size.
98
106
  * Handles scaling between input dimensions and model requirements.
@@ -100,14 +108,14 @@ class M {
100
108
  * @param rawImageData - The raw image data to potentially resize.
101
109
  * @returns The processed mask image data, resized if necessary.
102
110
  */
103
- getMaskImageData(t) {
104
- const e = this.config.mask.inputSize, s = this.config.input;
105
- return e.width === s.width && e.height === s.height ? t : F(
106
- t,
107
- s.width,
108
- s.height,
109
- e.width,
110
- e.height
111
+ getMaskImageData(e) {
112
+ const t = this.config.mask.inputSize, r = this.config.input;
113
+ return t.width === r.width && t.height === r.height ? e : y(
114
+ e,
115
+ r.width,
116
+ r.height,
117
+ t.width,
118
+ t.height
111
119
  );
112
120
  }
113
121
  /**
@@ -118,20 +126,20 @@ class M {
118
126
  * @param performedInference - Whether inference was actually performed for this frame.
119
127
  * @returns The inference result object containing mask data and metadata.
120
128
  */
121
- createInferenceResult(t, e) {
129
+ createInferenceResult(e, t) {
122
130
  return this.lastMaskResult ? {
123
131
  facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,
124
132
  gesture: this.lastMaskResult.gesture,
125
133
  motion: this.lastMaskResult.motion,
126
- skipped: !e,
127
- timestamp: t
134
+ skipped: !t,
135
+ timestamp: e
128
136
  // Stats will be added by the caller if needed
129
137
  } : {
130
138
  facesAndLandmarks: { data: new Float32Array(0), dims: [] },
131
139
  gesture: new Float32Array(0),
132
140
  motion: 0,
133
141
  skipped: !0,
134
- timestamp: t
142
+ timestamp: e
135
143
  // Stats will be added by the caller if needed
136
144
  };
137
145
  }
@@ -153,10 +161,10 @@ class M {
153
161
  async load() {
154
162
  switch (this.config.render.type) {
155
163
  case "blur":
156
- this.renderer = new f(this.outputCanvas, this.config);
164
+ this.renderer = new w(this.outputCanvas, this.config);
157
165
  break;
158
166
  case "passthrough":
159
- this.renderer = new w(this.outputCanvas, this.config);
167
+ this.renderer = new F(this.outputCanvas, this.config);
160
168
  break;
161
169
  case "replacement":
162
170
  this.renderer = new k(this.outputCanvas, this.config);
@@ -164,7 +172,7 @@ class M {
164
172
  default:
165
173
  throw new Error("[ladon-ts] Renderer not implemented.");
166
174
  }
167
- await this.renderer.load(), this.generator = new g(this.config), await this.generator.load(), this.loaded = !0;
175
+ await this.renderer.load(), this.generator = new f(this.config), await this.generator.load(), this.loaded = !0;
168
176
  }
169
177
  /**
170
178
  * Reset the pipeline state by clearing cached mask results.
@@ -180,11 +188,11 @@ class M {
180
188
  * @returns Promise that resolves when cleanup is complete.
181
189
  */
182
190
  async destroy() {
183
- var t, e;
184
- this.reset(), (t = this.renderer) == null || t.destroy(), await ((e = this.generator) == null ? void 0 : e.destroy()), this.loaded = !1;
191
+ var e, t;
192
+ this.reset(), (e = this.renderer) == null || e.destroy(), await ((t = this.generator) == null ? void 0 : t.destroy()), this.loaded = !1;
185
193
  }
186
194
  }
187
195
  export {
188
- M as Pipeline
196
+ A as Pipeline
189
197
  };
190
198
  //# sourceMappingURL=pipeline.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { FrameData, LadonOutput, MaskGenerator, WorkerGenerator } from './generator';\nimport { IFrameInferenceResult, IPipeline } from './pipeline-types';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\n\n/**\n * Core pipeline implementation for processing video frames with ML inference and rendering.\n * Handles frame processing, mask generation, and output rendering with proper timing controls.\n */\nexport class Pipeline implements IPipeline {\n /** The mask generator responsible for ML inference. */\n private generator?: MaskGenerator;\n\n /** The renderer responsible for compositing the final output. */\n private renderer?: LadonRenderer;\n\n /** Flag indicating whether the pipeline has been fully loaded and initialized. */\n private loaded = false;\n\n /** 2D rendering context for the input canvas used for frame processing. */\n private inputCanvasContext: CanvasRenderingContext2D;\n\n /** The most recent mask result from ML inference, used when skipping inference. */\n private lastMaskResult?: LadonOutput;\n\n /** Flag indicating whether a frame is currently being processed. */\n private isProcessingFrame = false;\n\n /** Promise for the currently pending frame processing operation. */\n private pendingFramePromise: Promise<IFrameInferenceResult> | null = null;\n\n /**\n * Create a new pipeline instance.\n *\n * @param inputCanvas - Canvas element for input frame processing.\n * @param outputCanvas - Canvas element where the final rendered output will be displayed.\n * @param config - Pipeline configuration containing processing and rendering settings.\n * @param inputVideoElement - Video element serving as the input source.\n * @throws Error if the input canvas 2D context cannot be obtained.\n */\n constructor(\n private inputCanvas: HTMLCanvasElement,\n private outputCanvas: HTMLCanvasElement,\n public readonly config: PipelineConfig,\n private readonly inputVideoElement: HTMLVideoElement\n ) {\n const inputCanvasContext = this.inputCanvas.getContext('2d', {\n alpha: false,\n willReadFrequently: true,\n });\n\n if (!inputCanvasContext) {\n throw new Error('[ladon-ts] Error getting input context 2d');\n }\n this.inputCanvasContext = inputCanvasContext;\n }\n\n /**\n * Process a single frame and return the inference result.\n * This method handles frame data, performs inference if needed, and renders the output.\n * Includes frame processing serialization to prevent overlapping operations.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n public async onFrame(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n // If already processing, wait for the current frame to finish\n if (this.isProcessingFrame && this.pendingFramePromise) {\n await this.pendingFramePromise;\n }\n\n // Set processing flag\n this.isProcessingFrame = true;\n\n try {\n this.pendingFramePromise = this.processFrameInternal(\n timestamp,\n performInference,\n frameData\n );\n return await this.pendingFramePromise;\n } finally {\n this.isProcessingFrame = false;\n this.pendingFramePromise = null;\n }\n }\n\n /**\n * Internal frame processing implementation.\n * Handles the actual frame processing logic including timing measurements.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n private async processFrameInternal(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n const frameStartTime = performance.now();\n\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n let preprocessingTime = 0;\n let modelInferenceTime = 0;\n\n if (performInference || !this.lastMaskResult) {\n // Pre-processing timing\n const preprocessStart = performance.now();\n\n // Get the current frame (raw image data or video element). Use the video element if skipping\n // inference to avoid unnecessary processing when calling canvas getImageData.\n frameData ??= this.getInputFrameData();\n\n preprocessingTime = performance.now() - preprocessStart;\n\n // Model inference timing\n const inferenceStart = performance.now();\n\n // Send frame for inference and wait for the mask for this timestamp\n const rawImageData = frameData as Uint8ClampedArray;\n const maskImageData = this.getMaskImageData(rawImageData);\n await this.generator.process(maskImageData, timestamp);\n this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(timestamp);\n\n modelInferenceTime = performance.now() - inferenceStart;\n }\n\n // Post-processing timing (rendering)\n const postprocessStart = performance.now();\n\n // Render the frame and mask together (mask may be stale if skipping inference)\n await this.renderer.render(this.inputVideoElement, this.lastMaskResult);\n\n const postprocessingTime = performance.now() - postprocessStart;\n\n const frameEndTime = performance.now();\n const totalFrameTime = frameEndTime - frameStartTime;\n\n const result = this.createInferenceResult(timestamp, performInference);\n\n // Add timing stats to the result\n result.stats = {\n totalFrameTime,\n modelInferenceTime,\n preprocessingTime,\n postprocessingTime,\n frameStartTime,\n frameEndTime,\n inferenceSkipped: !performInference,\n };\n\n return result;\n }\n\n /**\n * Get the raw image data from the input canvas context.\n *\n * @returns The frame data as a Uint8ClampedArray.\n */\n private getInputFrameData(): FrameData {\n return this.inputCanvasContext.getImageData(\n 0,\n 0,\n this.config.input.width,\n this.config.input.height\n ).data;\n }\n\n /**\n * Resize the image data if the model expects a different input size.\n * Handles scaling between input dimensions and model requirements.\n *\n * @param rawImageData - The raw image data to potentially resize.\n * @returns The processed mask image data, resized if necessary.\n */\n private getMaskImageData(rawImageData: Uint8ClampedArray): Uint8ClampedArray {\n const modelInput = this.config.mask.inputSize;\n const inputImage = this.config.input;\n\n // Check if resizing is needed based on configuration.\n if (modelInput.width === inputImage.width && modelInput.height === inputImage.height) {\n return rawImageData;\n }\n\n // Resize the image data.\n return resizeImage(\n rawImageData,\n inputImage.width,\n inputImage.height,\n modelInput.width,\n modelInput.height\n );\n }\n\n /**\n * Create the inference result object with appropriate data.\n * Handles both successful inference results and fallback cases.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performedInference - Whether inference was actually performed for this frame.\n * @returns The inference result object containing mask data and metadata.\n */\n private createInferenceResult(\n timestamp: number,\n performedInference: boolean\n ): IFrameInferenceResult {\n if (!this.lastMaskResult) {\n return {\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n skipped: true,\n timestamp,\n // Stats will be added by the caller if needed\n };\n }\n\n return {\n facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,\n gesture: this.lastMaskResult.gesture,\n motion: this.lastMaskResult.motion,\n skipped: !performedInference,\n timestamp,\n // Stats will be added by the caller if needed\n };\n }\n\n /**\n * Check if the pipeline is fully loaded and ready for processing.\n *\n * @returns True if the pipeline is loaded and ready, false otherwise.\n */\n public isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Load and initialize the pipeline components.\n * Creates the appropriate renderer and generator based on configuration.\n *\n * @returns Promise that resolves when all components are loaded and ready.\n * @throws Error if an unsupported renderer type is specified in the configuration.\n */\n public async load(): Promise<void> {\n switch (this.config.render.type) {\n case 'blur':\n this.renderer = new BlurRenderer(this.outputCanvas, this.config);\n break;\n case 'passthrough':\n this.renderer = new PassthroughRenderer(this.outputCanvas, this.config);\n break;\n case 'replacement':\n this.renderer = new ReplacementRenderer(this.outputCanvas, this.config);\n break;\n default:\n throw new Error('[ladon-ts] Renderer not implemented.');\n }\n\n await this.renderer.load();\n\n this.generator = new WorkerGenerator(this.config);\n await this.generator.load();\n\n this.loaded = true;\n }\n\n /**\n * Reset the pipeline state by clearing cached mask results.\n * Useful when switching between different processing modes or sources.\n */\n public reset(): void {\n this.lastMaskResult = undefined;\n }\n\n /**\n * Destroy the pipeline and clean up all resources.\n * Properly disposes of the renderer and generator to prevent memory leaks.\n *\n * @returns Promise that resolves when cleanup is complete.\n */\n public async destroy(): Promise<void> {\n this.reset();\n this.renderer?.destroy();\n await this.generator?.destroy();\n this.loaded = false;\n }\n}\n"],"names":["Pipeline","inputCanvas","outputCanvas","config","inputVideoElement","inputCanvasContext","timestamp","performInference","frameData","frameStartTime","preprocessingTime","modelInferenceTime","preprocessStart","inferenceStart","rawImageData","maskImageData","postprocessStart","postprocessingTime","frameEndTime","totalFrameTime","result","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_a","_b"],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.js","sources":["../../../src/lib/pipeline.ts"],"sourcesContent":["import { PipelineConfig } from './config-types';\nimport { FrameData, LadonOutput, MaskGenerator, WorkerGenerator } from './generator';\nimport { IFrameInferenceResult, IPipeline } from './pipeline-types';\nimport { BlurRenderer, LadonRenderer, PassthroughRenderer, ReplacementRenderer } from './renderer';\nimport { resizeImage } from './util';\n\n/**\n * Core pipeline implementation for processing video frames with ML inference and rendering.\n * Handles frame processing, mask generation, and output rendering with proper timing controls.\n */\nexport class Pipeline implements IPipeline {\n /** The mask generator responsible for ML inference. */\n private generator?: MaskGenerator;\n\n /** The renderer responsible for compositing the final output. */\n private renderer?: LadonRenderer;\n\n /** Flag indicating whether the pipeline has been fully loaded and initialized. */\n private loaded = false;\n\n /** 2D rendering context for the input canvas used for frame processing. */\n private inputCanvasContext: CanvasRenderingContext2D;\n\n /** The most recent mask result from ML inference, used when skipping inference. */\n private lastMaskResult?: LadonOutput;\n\n /** Flag indicating whether a frame is currently being processed. */\n private isProcessingFrame = false;\n\n /** Promise for the currently pending frame processing operation. */\n private pendingFramePromise: Promise<IFrameInferenceResult> | null = null;\n\n /** Buffer for frame data copies to ensure they are not modified or garbage collected. */\n private frameDataBuffer?: Uint8ClampedArray;\n\n /**\n * Create a new pipeline instance.\n *\n * @param inputCanvas - Canvas element for input frame processing.\n * @param outputCanvas - Canvas element where the final rendered output will be displayed.\n * @param config - Pipeline configuration containing processing and rendering settings.\n * @param inputVideoElement - Video element serving as the input source.\n * @throws Error if the input canvas 2D context cannot be obtained.\n */\n constructor(\n private inputCanvas: HTMLCanvasElement,\n private outputCanvas: HTMLCanvasElement,\n public readonly config: PipelineConfig,\n private readonly inputVideoElement: HTMLVideoElement\n ) {\n const inputCanvasContext = this.inputCanvas.getContext('2d', {\n alpha: false,\n willReadFrequently: true,\n });\n\n if (!inputCanvasContext) {\n throw new Error('[ladon-ts] Error getting input context 2d');\n }\n this.inputCanvasContext = inputCanvasContext;\n }\n\n /**\n * Process a single frame and return the inference result.\n * This method handles frame data, performs inference if needed, and renders the output.\n * Includes frame processing serialization to prevent overlapping operations.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n public async onFrame(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n // If already processing, wait for the current frame to finish\n if (this.isProcessingFrame && this.pendingFramePromise) {\n await this.pendingFramePromise;\n }\n\n // Set processing flag\n this.isProcessingFrame = true;\n\n try {\n this.pendingFramePromise = this.processFrameInternal(\n timestamp,\n performInference,\n frameData\n );\n return await this.pendingFramePromise;\n } finally {\n this.isProcessingFrame = false;\n this.pendingFramePromise = null;\n }\n }\n\n /**\n * Internal frame processing implementation.\n * Handles the actual frame processing logic including timing measurements.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performInference - Whether to perform mask inference or reuse the last result.\n * @param frameData - Optional frame data to process instead of capturing from input.\n * @returns Promise resolving to the frame processing result with timing statistics.\n * @throws Error if the pipeline (generator or renderer) is not loaded.\n */\n private async processFrameInternal(\n timestamp: number,\n performInference = true,\n frameData?: FrameData\n ): Promise<IFrameInferenceResult> {\n const frameStartTime = performance.now();\n\n if (!this.generator || !this.renderer) {\n throw new Error('[ladon-ts] Pipeline is not loaded.');\n }\n\n let preprocessingTime = 0;\n let modelInferenceTime = 0;\n let renderFrameData: FrameData;\n\n if (performInference || !this.lastMaskResult) {\n // INFERENCE PATH: Capture canvas data, generate mask, render with same data\n const preprocessStart = performance.now();\n\n // Get canvas data for inference\n frameData ??= this.getInputFrameData();\n\n // Create stable copy for rendering synchronization\n renderFrameData = this.createFrameDataCopy(frameData);\n\n preprocessingTime = performance.now() - preprocessStart;\n\n // Generate mask\n const inferenceStart = performance.now();\n const rawImageData = frameData as Uint8ClampedArray;\n const maskImageData = this.getMaskImageData(rawImageData);\n await this.generator.process(maskImageData, timestamp);\n this.lastMaskResult = await this.generator.getLadonOutputForTimestamp(timestamp);\n modelInferenceTime = performance.now() - inferenceStart;\n } else {\n // SKIP INFERENCE PATH: Use video element directly for GPU optimization\n renderFrameData = this.inputVideoElement;\n }\n\n // Render with appropriate frame data\n const postprocessStart = performance.now();\n await this.renderer.render(renderFrameData, this.lastMaskResult);\n const postprocessingTime = performance.now() - postprocessStart;\n\n const frameEndTime = performance.now();\n const totalFrameTime = frameEndTime - frameStartTime;\n\n const result = this.createInferenceResult(timestamp, performInference);\n result.stats = {\n totalFrameTime,\n modelInferenceTime,\n preprocessingTime,\n postprocessingTime,\n frameStartTime,\n frameEndTime,\n inferenceSkipped: !performInference,\n };\n\n return result;\n }\n\n /**\n * Get the raw image data from the input canvas context.\n *\n * @returns The frame data as a Uint8ClampedArray.\n */\n private getInputFrameData(): FrameData {\n return this.inputCanvasContext.getImageData(\n 0,\n 0,\n this.config.input.width,\n this.config.input.height\n ).data;\n }\n\n /**\n * Create a copy of frame data to prevent modifications during async operations.\n */\n private createFrameDataCopy(frameData: FrameData): FrameData {\n if (frameData instanceof Uint8ClampedArray) {\n if (this.frameDataBuffer?.length !== frameData.length) {\n this.frameDataBuffer = new Uint8ClampedArray(frameData.length);\n }\n this.frameDataBuffer.set(frameData);\n return this.frameDataBuffer;\n }\n return frameData;\n }\n\n /**\n * Resize the image data if the model expects a different input size.\n * Handles scaling between input dimensions and model requirements.\n *\n * @param rawImageData - The raw image data to potentially resize.\n * @returns The processed mask image data, resized if necessary.\n */\n private getMaskImageData(rawImageData: Uint8ClampedArray): Uint8ClampedArray {\n const modelInput = this.config.mask.inputSize;\n const inputImage = this.config.input;\n\n // Check if resizing is needed based on configuration.\n if (modelInput.width === inputImage.width && modelInput.height === inputImage.height) {\n return rawImageData;\n }\n\n // Resize the image data.\n return resizeImage(\n rawImageData,\n inputImage.width,\n inputImage.height,\n modelInput.width,\n modelInput.height\n );\n }\n\n /**\n * Create the inference result object with appropriate data.\n * Handles both successful inference results and fallback cases.\n *\n * @param timestamp - The timestamp of the frame in milliseconds.\n * @param performedInference - Whether inference was actually performed for this frame.\n * @returns The inference result object containing mask data and metadata.\n */\n private createInferenceResult(\n timestamp: number,\n performedInference: boolean\n ): IFrameInferenceResult {\n if (!this.lastMaskResult) {\n return {\n facesAndLandmarks: { data: new Float32Array(0), dims: [] },\n gesture: new Float32Array(0),\n motion: 0,\n skipped: true,\n timestamp,\n // Stats will be added by the caller if needed\n };\n }\n\n return {\n facesAndLandmarks: this.lastMaskResult.facesAndLandmarks,\n gesture: this.lastMaskResult.gesture,\n motion: this.lastMaskResult.motion,\n skipped: !performedInference,\n timestamp,\n // Stats will be added by the caller if needed\n };\n }\n\n /**\n * Check if the pipeline is fully loaded and ready for processing.\n *\n * @returns True if the pipeline is loaded and ready, false otherwise.\n */\n public isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Load and initialize the pipeline components.\n * Creates the appropriate renderer and generator based on configuration.\n *\n * @returns Promise that resolves when all components are loaded and ready.\n * @throws Error if an unsupported renderer type is specified in the configuration.\n */\n public async load(): Promise<void> {\n switch (this.config.render.type) {\n case 'blur':\n this.renderer = new BlurRenderer(this.outputCanvas, this.config);\n break;\n case 'passthrough':\n this.renderer = new PassthroughRenderer(this.outputCanvas, this.config);\n break;\n case 'replacement':\n this.renderer = new ReplacementRenderer(this.outputCanvas, this.config);\n break;\n default:\n throw new Error('[ladon-ts] Renderer not implemented.');\n }\n\n await this.renderer.load();\n\n this.generator = new WorkerGenerator(this.config);\n await this.generator.load();\n\n this.loaded = true;\n }\n\n /**\n * Reset the pipeline state by clearing cached mask results.\n * Useful when switching between different processing modes or sources.\n */\n public reset(): void {\n this.lastMaskResult = undefined;\n }\n\n /**\n * Destroy the pipeline and clean up all resources.\n * Properly disposes of the renderer and generator to prevent memory leaks.\n *\n * @returns Promise that resolves when cleanup is complete.\n */\n public async destroy(): Promise<void> {\n this.reset();\n this.renderer?.destroy();\n await this.generator?.destroy();\n this.loaded = false;\n }\n}\n"],"names":["Pipeline","inputCanvas","outputCanvas","config","inputVideoElement","inputCanvasContext","timestamp","performInference","frameData","frameStartTime","preprocessingTime","modelInferenceTime","renderFrameData","preprocessStart","inferenceStart","rawImageData","maskImageData","postprocessStart","postprocessingTime","frameEndTime","totalFrameTime","result","_a","modelInput","inputImage","resizeImage","performedInference","BlurRenderer","PassthroughRenderer","ReplacementRenderer","WorkerGenerator","_b"],"mappings":"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"}
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "5.9.0";
1
+ export declare const LIB_VERSION = "5.9.2";
@@ -1,4 +1,4 @@
1
- const o = "5.9.0";
1
+ const o = "5.9.2";
2
2
  export {
3
3
  o as LIB_VERSION
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.9.0';\n"],"names":["LIB_VERSION"],"mappings":"AAAO,MAAMA,IAAc;"}
1
+ {"version":3,"file":"version.js","sources":["../../../src/lib/version.ts"],"sourcesContent":["export const LIB_VERSION = '5.9.2';\n"],"names":["LIB_VERSION"],"mappings":"AAAO,MAAMA,IAAc;"}
@@ -1,5 +1,5 @@
1
1
  import { resourceLoader as a } from "./resource-loader.js";
2
- class d {
2
+ class k {
3
3
  constructor() {
4
4
  this.destroyPromise = null, this.onInitializedCallbacks = [], this.onGeneratedCallbacks = [], this.onDroppedCallbacks = [], this.onLogCallbacks = [];
5
5
  }
@@ -20,7 +20,7 @@ class d {
20
20
  async initializeWorker(e) {
21
21
  if (typeof Worker > "u")
22
22
  throw new Error("[ladon-ts] web workers are not supported in this environment");
23
- this.destroyPromise && (await this.destroyPromise, this.destroyPromise = null), this.worker && (this.destroyPromise = this.destroyWorker(), await this.destroyPromise, this.destroyPromise = null);
23
+ this.worker && await this.destroyWorker();
24
24
  const r = await a.resolveUri(e, e.mask.workerUri), o = await (await fetch(r, { cache: "force-cache" })).text();
25
25
  return this.workerUrl = URL.createObjectURL(new Blob([o], { type: "text/javascript" })), this.worker = new Worker(this.workerUrl, { type: "module" }), this.worker.addEventListener("message", (t) => {
26
26
  if (t.data.type === "log") {
@@ -36,17 +36,17 @@ class d {
36
36
  * If no worker is available, it resolves immediately.
37
37
  */
38
38
  destroyWorker() {
39
- return new Promise((e) => {
39
+ return this.destroyPromise || (this.destroyPromise = new Promise((e) => {
40
40
  const r = (i) => {
41
41
  var o;
42
- i.data.status === "destroyed" && ((o = this.worker) == null || o.removeEventListener("message", r), this.terminateWorker(), e());
42
+ i.data.status === "destroyed" && ((o = this.worker) == null || o.removeEventListener("message", r), this.terminateWorker(), this.destroyPromise = null, e());
43
43
  };
44
44
  if (!this.worker) {
45
45
  console.warn("[ladon-ts] No worker to destroy"), e();
46
46
  return;
47
47
  }
48
48
  this.worker.addEventListener("message", r), this.worker.postMessage({ type: "destroy" });
49
- });
49
+ })), this.destroyPromise;
50
50
  }
51
51
  /**
52
52
  * Terminate the worker.
@@ -153,9 +153,9 @@ class d {
153
153
  );
154
154
  }
155
155
  }
156
- const w = new d();
156
+ const w = new k();
157
157
  export {
158
- d as WorkerManager,
158
+ k as WorkerManager,
159
159
  w as workerManager
160
160
  };
161
161
  //# sourceMappingURL=worker-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-manager.js","sources":["../../../src/lib/worker-manager.ts"],"sourcesContent":["import { PipelineConfig, PreloadConfig, WorkerConfig } from './config-types';\nimport { WorkerGeneratedResult } from './generator-types';\nimport { resourceLoader } from './resource-loader';\n\n/**\n * Worker manager class to handle the initialization and termination of the segmenation\n * web worker. This class is responsible for loading the worker script, resolving the\n * configuration, and managing the worker lifecycle. It also provides methods to\n * preload the worker and terminate it when no longer needed.\n */\nexport class WorkerManager {\n private worker?: Worker;\n private workerUrl?: string;\n private destroyPromise: Promise<void> | null = null;\n\n // Event callback registries\n private onInitializedCallbacks: Array<() => void> = [];\n private onGeneratedCallbacks: Array<(data: WorkerGeneratedResult) => void> = [];\n private onDroppedCallbacks: Array<(timestamp: number) => void> = [];\n private onLogCallbacks: Array<(message: string, severity: string) => void> = [];\n\n /**\n * Get the current worker instance.\n *\n * @returns The current worker instance or undefined if not initialized.\n */\n public getWorker() {\n return this.worker;\n }\n\n /**\n * Initialize the worker using the provided configuration.\n *\n * @param config - The configuration object.\n * @returns A promise that resolves when the worker is initialized.\n */\n public async initializeWorker(config: WorkerConfig): Promise<Worker> {\n if (typeof Worker === 'undefined') {\n throw new Error('[ladon-ts] web workers are not supported in this environment');\n }\n\n // Wait for any ongoing destroy operation to finish\n if (this.destroyPromise) {\n await this.destroyPromise;\n this.destroyPromise = null;\n }\n\n if (this.worker) {\n this.destroyPromise = this.destroyWorker();\n await this.destroyPromise;\n this.destroyPromise = null;\n }\n\n const scriptUri = await resourceLoader.resolveUri(config, config.mask.workerUri);\n const response = await fetch(scriptUri, { cache: 'force-cache' });\n const workerSource = await response.text();\n\n this.workerUrl = URL.createObjectURL(new Blob([workerSource], { type: 'text/javascript' }));\n this.worker = new Worker(this.workerUrl, { type: 'module' });\n\n // Attach a single message handler for all events\n this.worker.addEventListener('message', (event: MessageEvent) => {\n if (event.data.type === 'log') {\n const { message, severity } = event.data;\n this.onLogCallbacks.forEach((cb) => cb(message, severity));\n } else if (event.data.status === 'generated') {\n this.onGeneratedCallbacks.forEach((cb) => cb(event.data));\n } else if (event.data.status === 'dropped') {\n this.onDroppedCallbacks.forEach((cb) => cb(event.data.timestamp));\n } else if (event.data.status === 'initialized') {\n this.onInitializedCallbacks.forEach((cb) => cb());\n }\n // 'destroyed' is handled in destroyWorker\n });\n\n return this.worker;\n }\n\n /**\n * Destroys the worker gracefully by sending a destroy message and waiting for confirmation.\n *\n * @returns A promise that resolves when the worker is destroyed.\n * If no worker is available, it resolves immediately.\n */\n public destroyWorker(): Promise<void> {\n return new Promise<void>((resolve) => {\n const handleMessage = (event: MessageEvent) => {\n if (event.data.status === 'destroyed') {\n this.worker?.removeEventListener('message', handleMessage);\n this.terminateWorker();\n resolve();\n }\n };\n\n if (!this.worker) {\n console.warn('[ladon-ts] No worker to destroy');\n resolve();\n return;\n }\n\n this.worker.addEventListener('message', handleMessage);\n this.worker.postMessage({ type: 'destroy' });\n });\n }\n\n /**\n * Terminate the worker.\n */\n public terminateWorker() {\n if (this.worker) {\n this.worker.terminate();\n this.worker = undefined;\n }\n if (this.workerUrl) {\n URL.revokeObjectURL(this.workerUrl);\n this.workerUrl = undefined;\n }\n }\n\n /**\n * Preload the worker using the provided configuration.\n *\n * @param config - The configuration object.\n * @returns A promise that resolves when the worker is preloaded.\n */\n public async preloadWorker(config: PreloadConfig) {\n const workerConfig = await this.prepareWorkerConfig(config);\n this.worker = await this.initializeWorker(workerConfig);\n this.worker.postMessage({ type: 'preload', config: workerConfig });\n }\n\n /**\n * Prepare the worker configuration by resolving the final URLs and paths to send to the\n * worker. This is necessary because we can't send functions to the worker. We also need to\n * remove the assetUrlResolver function from the configuration.\n *\n * @param config - The preload configuration.\n * @returns The worker configuration.\n */\n public async prepareWorkerConfig(config: PreloadConfig) {\n // Resolve and cache the URIs before sending the config to the worker.\n // Future calls to the resolveUri method will use the cache.\n const modelUri = await resourceLoader.resolveUri(config, config.mask.modelUri);\n const workerUri = await resourceLoader.resolveUri(config, config.mask.workerUri);\n const wasmUri = await resourceLoader.resolveUri(config, config.wasmUri);\n const resolvedConfig: PreloadConfig = {\n ...config,\n mask: { ...config.mask, modelUri, workerUri },\n wasmUri,\n };\n\n // We can't send the assetUrlResolver function to the worker so we remove it from the config.\n const workerConfig = { ...resolvedConfig, assetUrlResolver: undefined };\n\n // This is a workaround to ensure the worker configuration can be sent to the worker.\n return JSON.parse(JSON.stringify(workerConfig));\n }\n\n /**\n * Register a callback for when the worker is initialized.\n */\n public onInitialized(callback: () => void) {\n this.onInitializedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for when the worker generates output.\n *\n * @param callback - The callback function to be called with the generated data.\n * @param data - The generated data from the worker.\n * @param data.mask - The generated mask.\n * @param data.facesAndLandmarks - The detected faces and landmarks.\n * @param data.gesture - The detected gesture.\n * @param data.motion - The detected motion.\n */\n public onGenerated(callback: (data: WorkerGeneratedResult) => void) {\n this.onGeneratedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for when the worker drops a frame.\n * This is useful for handling cases where the worker cannot keep up with the input frame rate.\n * The callback will be called with the timestamp of the dropped frame.\n *\n * @param callback - The callback function to be called with the timestamp of the dropped frame.\n */\n public onDropped(callback: (timestamp: number) => void) {\n this.onDroppedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for log messages from the worker.\n *\n * @param callback - The callback function to be called with the log message and severity.\n * @param severity - The severity of the log message (e.g., 'info', 'warn', 'error').\n */\n public onLog(callback: (message: string, severity: string) => void) {\n this.onLogCallbacks.push(callback);\n }\n\n /**\n * Send an init message to the worker.\n *\n * @param config - The configuration object to initialize the worker.\n * @param config.baseUrl - The base URL for loading assets.\n * @param config.input - The input configuration for the worker.\n * @param config.mask - The mask configuration for the worker.\n * @param config.wasmUri - The URI for the WebAssembly module.\n * @param config.workerUri - The URI for the worker script.\n * @param config.assetUrlResolver - The function to resolve asset URLs.\n */\n public sendInit(config: PipelineConfig) {\n if (!this.worker) {\n throw new Error('[ladon-ts] Worker is not initialized, cannot send init');\n }\n this.worker.postMessage({ type: 'init', config });\n }\n\n /**\n * Send a generate message to the worker.\n *\n * @param input - The input image data as a Uint8ClampedArray.\n * @param timestamp - The timestamp of the input image.\n */\n public sendGenerate(input: Uint8ClampedArray, timestamp: number) {\n if (!this.worker) {\n throw new Error('[ladon-ts] Worker is not initialized, cannot send generate');\n }\n this.worker.postMessage(\n {\n type: 'generate',\n image: input,\n timestamp,\n },\n [input.buffer]\n );\n }\n}\n\n// Global instance of the shared resource manager.\nexport const workerManager = new WorkerManager();\n"],"names":["WorkerManager","config","scriptUri","resourceLoader","workerSource","event","message","severity","cb","resolve","handleMessage","_a","workerConfig","modelUri","workerUri","wasmUri","callback","input","timestamp","workerManager"],"mappings":"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"}
1
+ {"version":3,"file":"worker-manager.js","sources":["../../../src/lib/worker-manager.ts"],"sourcesContent":["import { PipelineConfig, PreloadConfig, WorkerConfig } from './config-types';\nimport { WorkerGeneratedResult } from './generator-types';\nimport { resourceLoader } from './resource-loader';\n\n/**\n * Worker manager class to handle the initialization and termination of the segmenation\n * web worker. This class is responsible for loading the worker script, resolving the\n * configuration, and managing the worker lifecycle. It also provides methods to\n * preload the worker and terminate it when no longer needed.\n */\nexport class WorkerManager {\n private worker?: Worker;\n private workerUrl?: string;\n private destroyPromise: Promise<void> | null = null;\n\n // Event callback registries\n private onInitializedCallbacks: Array<() => void> = [];\n private onGeneratedCallbacks: Array<(data: WorkerGeneratedResult) => void> = [];\n private onDroppedCallbacks: Array<(timestamp: number) => void> = [];\n private onLogCallbacks: Array<(message: string, severity: string) => void> = [];\n\n /**\n * Get the current worker instance.\n *\n * @returns The current worker instance or undefined if not initialized.\n */\n public getWorker() {\n return this.worker;\n }\n\n /**\n * Initialize the worker using the provided configuration.\n *\n * @param config - The configuration object.\n * @returns A promise that resolves when the worker is initialized.\n */\n public async initializeWorker(config: WorkerConfig): Promise<Worker> {\n if (typeof Worker === 'undefined') {\n throw new Error('[ladon-ts] web workers are not supported in this environment');\n }\n\n if (this.worker) {\n await this.destroyWorker();\n }\n\n const scriptUri = await resourceLoader.resolveUri(config, config.mask.workerUri);\n const response = await fetch(scriptUri, { cache: 'force-cache' });\n const workerSource = await response.text();\n\n this.workerUrl = URL.createObjectURL(new Blob([workerSource], { type: 'text/javascript' }));\n this.worker = new Worker(this.workerUrl, { type: 'module' });\n\n // Attach a single message handler for all events\n this.worker.addEventListener('message', (event: MessageEvent) => {\n if (event.data.type === 'log') {\n const { message, severity } = event.data;\n this.onLogCallbacks.forEach((cb) => cb(message, severity));\n } else if (event.data.status === 'generated') {\n this.onGeneratedCallbacks.forEach((cb) => cb(event.data));\n } else if (event.data.status === 'dropped') {\n this.onDroppedCallbacks.forEach((cb) => cb(event.data.timestamp));\n } else if (event.data.status === 'initialized') {\n this.onInitializedCallbacks.forEach((cb) => cb());\n }\n // 'destroyed' is handled in destroyWorker\n });\n\n return this.worker;\n }\n\n /**\n * Destroys the worker gracefully by sending a destroy message and waiting for confirmation.\n *\n * @returns A promise that resolves when the worker is destroyed.\n * If no worker is available, it resolves immediately.\n */\n public destroyWorker(): Promise<void> {\n // Wait for any ongoing destroy operation to finish\n if (!this.destroyPromise) {\n this.destroyPromise = new Promise<void>((resolve) => {\n const handleMessage = (event: MessageEvent) => {\n if (event.data.status === 'destroyed') {\n this.worker?.removeEventListener('message', handleMessage);\n this.terminateWorker();\n this.destroyPromise = null;\n resolve();\n }\n };\n\n if (!this.worker) {\n console.warn('[ladon-ts] No worker to destroy');\n resolve();\n return;\n }\n\n this.worker.addEventListener('message', handleMessage);\n this.worker.postMessage({ type: 'destroy' });\n });\n }\n\n return this.destroyPromise;\n }\n\n /**\n * Terminate the worker.\n */\n public terminateWorker() {\n if (this.worker) {\n this.worker.terminate();\n this.worker = undefined;\n }\n if (this.workerUrl) {\n URL.revokeObjectURL(this.workerUrl);\n this.workerUrl = undefined;\n }\n }\n\n /**\n * Preload the worker using the provided configuration.\n *\n * @param config - The configuration object.\n * @returns A promise that resolves when the worker is preloaded.\n */\n public async preloadWorker(config: PreloadConfig) {\n const workerConfig = await this.prepareWorkerConfig(config);\n this.worker = await this.initializeWorker(workerConfig);\n this.worker.postMessage({ type: 'preload', config: workerConfig });\n }\n\n /**\n * Prepare the worker configuration by resolving the final URLs and paths to send to the\n * worker. This is necessary because we can't send functions to the worker. We also need to\n * remove the assetUrlResolver function from the configuration.\n *\n * @param config - The preload configuration.\n * @returns The worker configuration.\n */\n public async prepareWorkerConfig(config: PreloadConfig) {\n // Resolve and cache the URIs before sending the config to the worker.\n // Future calls to the resolveUri method will use the cache.\n const modelUri = await resourceLoader.resolveUri(config, config.mask.modelUri);\n const workerUri = await resourceLoader.resolveUri(config, config.mask.workerUri);\n const wasmUri = await resourceLoader.resolveUri(config, config.wasmUri);\n const resolvedConfig: PreloadConfig = {\n ...config,\n mask: { ...config.mask, modelUri, workerUri },\n wasmUri,\n };\n\n // We can't send the assetUrlResolver function to the worker so we remove it from the config.\n const workerConfig = { ...resolvedConfig, assetUrlResolver: undefined };\n\n // This is a workaround to ensure the worker configuration can be sent to the worker.\n return JSON.parse(JSON.stringify(workerConfig));\n }\n\n /**\n * Register a callback for when the worker is initialized.\n */\n public onInitialized(callback: () => void) {\n this.onInitializedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for when the worker generates output.\n *\n * @param callback - The callback function to be called with the generated data.\n * @param data - The generated data from the worker.\n * @param data.mask - The generated mask.\n * @param data.facesAndLandmarks - The detected faces and landmarks.\n * @param data.gesture - The detected gesture.\n * @param data.motion - The detected motion.\n */\n public onGenerated(callback: (data: WorkerGeneratedResult) => void) {\n this.onGeneratedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for when the worker drops a frame.\n * This is useful for handling cases where the worker cannot keep up with the input frame rate.\n * The callback will be called with the timestamp of the dropped frame.\n *\n * @param callback - The callback function to be called with the timestamp of the dropped frame.\n */\n public onDropped(callback: (timestamp: number) => void) {\n this.onDroppedCallbacks.push(callback);\n }\n\n /**\n * Register a callback for log messages from the worker.\n *\n * @param callback - The callback function to be called with the log message and severity.\n * @param severity - The severity of the log message (e.g., 'info', 'warn', 'error').\n */\n public onLog(callback: (message: string, severity: string) => void) {\n this.onLogCallbacks.push(callback);\n }\n\n /**\n * Send an init message to the worker.\n *\n * @param config - The configuration object to initialize the worker.\n * @param config.baseUrl - The base URL for loading assets.\n * @param config.input - The input configuration for the worker.\n * @param config.mask - The mask configuration for the worker.\n * @param config.wasmUri - The URI for the WebAssembly module.\n * @param config.workerUri - The URI for the worker script.\n * @param config.assetUrlResolver - The function to resolve asset URLs.\n */\n public sendInit(config: PipelineConfig) {\n if (!this.worker) {\n throw new Error('[ladon-ts] Worker is not initialized, cannot send init');\n }\n this.worker.postMessage({ type: 'init', config });\n }\n\n /**\n * Send a generate message to the worker.\n *\n * @param input - The input image data as a Uint8ClampedArray.\n * @param timestamp - The timestamp of the input image.\n */\n public sendGenerate(input: Uint8ClampedArray, timestamp: number) {\n if (!this.worker) {\n throw new Error('[ladon-ts] Worker is not initialized, cannot send generate');\n }\n this.worker.postMessage(\n {\n type: 'generate',\n image: input,\n timestamp,\n },\n [input.buffer]\n );\n }\n}\n\n// Global instance of the shared resource manager.\nexport const workerManager = new WorkerManager();\n"],"names":["WorkerManager","config","scriptUri","resourceLoader","workerSource","event","message","severity","cb","resolve","handleMessage","_a","workerConfig","modelUri","workerUri","wasmUri","callback","input","timestamp","workerManager"],"mappings":";AAUO,MAAMA,EAAc;AAAA,EAApB,cAAA;AAGH,SAAQ,iBAAuC,MAG/C,KAAQ,yBAA4C,CAAC,GACrD,KAAQ,uBAAqE,CAAC,GAC9E,KAAQ,qBAAyD,CAAC,GAClE,KAAQ,iBAAqE,CAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvE,YAAY;AACf,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAShB,MAAa,iBAAiBC,GAAuC;AAC7D,QAAA,OAAO,SAAW;AACZ,YAAA,IAAI,MAAM,8DAA8D;AAGlF,IAAI,KAAK,UACL,MAAM,KAAK,cAAc;AAG7B,UAAMC,IAAY,MAAMC,EAAe,WAAWF,GAAQA,EAAO,KAAK,SAAS,GAEzEG,IAAe,OADJ,MAAM,MAAMF,GAAW,EAAE,OAAO,eAAe,GAC5B,KAAK;AAEzC,gBAAK,YAAY,IAAI,gBAAgB,IAAI,KAAK,CAACE,CAAY,GAAG,EAAE,MAAM,kBAAmB,CAAA,CAAC,GACrF,KAAA,SAAS,IAAI,OAAO,KAAK,WAAW,EAAE,MAAM,UAAU,GAG3D,KAAK,OAAO,iBAAiB,WAAW,CAACC,MAAwB;AACzD,UAAAA,EAAM,KAAK,SAAS,OAAO;AAC3B,cAAM,EAAE,SAAAC,GAAS,UAAAC,EAAS,IAAIF,EAAM;AACpC,aAAK,eAAe,QAAQ,CAACG,MAAOA,EAAGF,GAASC,CAAQ,CAAC;AAAA,MAClD,MAAA,CAAAF,EAAM,KAAK,WAAW,cAC7B,KAAK,qBAAqB,QAAQ,CAACG,MAAOA,EAAGH,EAAM,IAAI,CAAC,IACjDA,EAAM,KAAK,WAAW,YACxB,KAAA,mBAAmB,QAAQ,CAACG,MAAOA,EAAGH,EAAM,KAAK,SAAS,CAAC,IACzDA,EAAM,KAAK,WAAW,iBAC7B,KAAK,uBAAuB,QAAQ,CAACG,MAAOA,GAAI;AAAA,IACpD,CAEH,GAEM,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST,gBAA+B;AAE9B,WAAC,KAAK,mBACN,KAAK,iBAAiB,IAAI,QAAc,CAACC,MAAY;AAC3C,YAAAC,IAAgB,CAACL,MAAwB;;AACvC,QAAAA,EAAM,KAAK,WAAW,iBACjBM,IAAA,KAAA,WAAA,QAAAA,EAAQ,oBAAoB,WAAWD,IAC5C,KAAK,gBAAgB,GACrB,KAAK,iBAAiB,MACdD,EAAA;AAAA,MAEhB;AAEI,UAAA,CAAC,KAAK,QAAQ;AACd,gBAAQ,KAAK,iCAAiC,GACtCA,EAAA;AACR;AAAA,MAAA;AAGC,WAAA,OAAO,iBAAiB,WAAWC,CAAa,GACrD,KAAK,OAAO,YAAY,EAAE,MAAM,WAAW;AAAA,IAAA,CAC9C,IAGE,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMT,kBAAkB;AACrB,IAAI,KAAK,WACL,KAAK,OAAO,UAAU,GACtB,KAAK,SAAS,SAEd,KAAK,cACD,IAAA,gBAAgB,KAAK,SAAS,GAClC,KAAK,YAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASJ,MAAa,cAAcT,GAAuB;AAC9C,UAAMW,IAAe,MAAM,KAAK,oBAAoBX,CAAM;AAC1D,SAAK,SAAS,MAAM,KAAK,iBAAiBW,CAAY,GACtD,KAAK,OAAO,YAAY,EAAE,MAAM,WAAW,QAAQA,GAAc;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWrE,MAAa,oBAAoBX,GAAuB;AAGpD,UAAMY,IAAW,MAAMV,EAAe,WAAWF,GAAQA,EAAO,KAAK,QAAQ,GACvEa,IAAY,MAAMX,EAAe,WAAWF,GAAQA,EAAO,KAAK,SAAS,GACzEc,IAAU,MAAMZ,EAAe,WAAWF,GAAQA,EAAO,OAAO,GAQhEW,IAAe,EAAE,GAPe;AAAA,MAClC,GAAGX;AAAA,MACH,MAAM,EAAE,GAAGA,EAAO,MAAM,UAAAY,GAAU,WAAAC,EAAU;AAAA,MAC5C,SAAAC;AAAA,IACJ,GAG0C,kBAAkB,OAAU;AAGtE,WAAO,KAAK,MAAM,KAAK,UAAUH,CAAY,CAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,cAAcI,GAAsB;AAClC,SAAA,uBAAuB,KAAKA,CAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAatC,YAAYA,GAAiD;AAC3D,SAAA,qBAAqB,KAAKA,CAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUpC,UAAUA,GAAuC;AAC/C,SAAA,mBAAmB,KAAKA,CAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASlC,MAAMA,GAAuD;AAC3D,SAAA,eAAe,KAAKA,CAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc9B,SAASf,GAAwB;AAChC,QAAA,CAAC,KAAK;AACA,YAAA,IAAI,MAAM,wDAAwD;AAE5E,SAAK,OAAO,YAAY,EAAE,MAAM,QAAQ,QAAAA,GAAQ;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS7C,aAAagB,GAA0BC,GAAmB;AACzD,QAAA,CAAC,KAAK;AACA,YAAA,IAAI,MAAM,4DAA4D;AAEhF,SAAK,OAAO;AAAA,MACR;AAAA,QACI,MAAM;AAAA,QACN,OAAOD;AAAA,QACP,WAAAC;AAAA,MACJ;AAAA,MACA,CAACD,EAAM,MAAM;AAAA,IACjB;AAAA,EAAA;AAER;AAGa,MAAAE,IAAgB,IAAInB,EAAc;"}