@pexip/media-processor 22.0.0 → 22.1.0

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/api-docs/README.mdx +3 -1
  3. package/api-docs/functions/avg.mdx +1 -1
  4. package/api-docs/functions/createAnalyzerGraphNode.mdx +3 -1
  5. package/api-docs/functions/createAnalyzerSubscribableGraphNode.mdx +3 -1
  6. package/api-docs/functions/createAsyncCallbackLoop.mdx +6 -6
  7. package/api-docs/functions/createAudioDestinationGraphNode.mdx +1 -1
  8. package/api-docs/functions/createAudioSignalDetector.mdx +4 -4
  9. package/api-docs/functions/createAudioStats.mdx +1 -1
  10. package/api-docs/functions/createChannelMergerGraphNode.mdx +1 -1
  11. package/api-docs/functions/createChannelSplitterGraphNode.mdx +1 -1
  12. package/api-docs/functions/createDelayGraphNode.mdx +1 -1
  13. package/api-docs/functions/createDenoiseWorkletGraphNode.mdx +2 -2
  14. package/api-docs/functions/createFrameCallbackRequest.mdx +3 -3
  15. package/api-docs/functions/createGainGraphNode.mdx +3 -1
  16. package/api-docs/functions/createMediaElementSourceGraphNode.mdx +1 -1
  17. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +1 -1
  18. package/api-docs/functions/createSegmenter.mdx +1 -1
  19. package/api-docs/functions/createStreamDestinationGraphNode.mdx +1 -1
  20. package/api-docs/functions/createStreamSourceGraphNode.mdx +1 -1
  21. package/api-docs/functions/createVADetector.mdx +3 -3
  22. package/api-docs/functions/createVoiceDetectorFromProbability.mdx +1 -1
  23. package/api-docs/functions/createVoiceDetectorFromTimeData.mdx +3 -1
  24. package/api-docs/functions/createWindowedStats.mdx +2 -2
  25. package/api-docs/functions/getBezierCurveControlPoints.mdx +3 -1
  26. package/api-docs/functions/labelComponents.mdx +3 -1
  27. package/api-docs/functions/loadScript.mdx +1 -1
  28. package/api-docs/functions/loadWasms.mdx +1 -1
  29. package/api-docs/functions/nearestPowerOfTwo.mdx +1 -1
  30. package/api-docs/functions/playVideo.mdx +6 -4
  31. package/api-docs/functions/processAverageVolume.mdx +1 -1
  32. package/api-docs/functions/rms.mdx +1 -1
  33. package/api-docs/functions/subscribeWorkletNode.mdx +1 -1
  34. package/api-docs/functions/sum.mdx +1 -1
  35. package/api-docs/interfaces/AnalyzerSubscribableOptions.mdx +5 -3
  36. package/api-docs/interfaces/AudioGraph.mdx +2 -2
  37. package/api-docs/interfaces/AudioNodeInit.mdx +10 -6
  38. package/api-docs/interfaces/AudioNodeProps.mdx +3 -3
  39. package/api-docs/interfaces/Benchmark.mdx +3 -1
  40. package/api-docs/interfaces/Clock.mdx +1 -1
  41. package/api-docs/interfaces/Denoise.mdx +2 -2
  42. package/api-docs/interfaces/InputProcessAdaptor.mdx +6 -2
  43. package/api-docs/interfaces/MPMask.mdx +1 -1
  44. package/api-docs/interfaces/Process.mdx +5 -5
  45. package/api-docs/interfaces/ProcessWorkerDebugMessage.mdx +1 -1
  46. package/api-docs/interfaces/ProcessWorkerEventClosed.mdx +5 -0
  47. package/api-docs/interfaces/ProcessorEvent.mdx +2 -1
  48. package/api-docs/interfaces/ProcessorProcessOptions.mdx +2 -2
  49. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +3 -1
  50. package/api-docs/interfaces/ReadWritableStream.mdx +2 -2
  51. package/api-docs/interfaces/RenderEventHandlers.mdx +3 -3
  52. package/api-docs/interfaces/Renderer.mdx +3 -1
  53. package/api-docs/interfaces/RendererOptions.mdx +5 -5
  54. package/api-docs/interfaces/Runner.mdx +3 -3
  55. package/api-docs/interfaces/Segmentation.mdx +1 -1
  56. package/api-docs/interfaces/Segmenter.mdx +39 -19
  57. package/api-docs/interfaces/VideoFrameLike.mdx +1 -1
  58. package/api-docs/interfaces/VideoProcessor.mdx +7 -7
  59. package/api-docs/interfaces/WorkletMessagePortOptions.mdx +2 -2
  60. package/api-docs/type-aliases/AsyncCallback.mdx +1 -1
  61. package/api-docs/type-aliases/Callback.mdx +2 -2
  62. package/api-docs/type-aliases/ExtractMessageEventType.mdx +1 -1
  63. package/api-docs/type-aliases/IncludeMessageEventDataType.mdx +1 -1
  64. package/api-docs/type-aliases/Logger.mdx +12 -0
  65. package/api-docs/type-aliases/OptionalKeys.mdx +1 -1
  66. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +1 -0
  67. package/api-docs/type-aliases/RunnerCreator.mdx +3 -3
  68. package/api-docs/type-aliases/TupleOf.mdx +1 -1
  69. package/api-docs/variables/cloneImageRecord.mdx +3 -1
  70. package/api-docs/variables/createLazyProps.mdx +1 -1
  71. package/api-docs/variables/createObjectUpdater.mdx +2 -2
  72. package/api-docs/variables/createRemoteImageBitmap.mdx +5 -5
  73. package/api-docs/variables/handleWebGLContextLoss.mdx +1 -1
  74. package/api-docs/variables/urls.mdx +2 -2
  75. package/dist/common/tsconfig.tsbuildinfo +1 -1
  76. package/dist/common/types/messageEvents.d.ts +4 -1
  77. package/dist/common/types/processor.d.ts +1 -0
  78. package/dist/main/tsconfig.tsbuildinfo +1 -1
  79. package/dist/main/video/segmenter.d.ts +8 -1
  80. package/dist/main/video/segmenter.js +44 -29
  81. package/dist/main/video/types.d.ts +1 -1
  82. package/dist/main/video/video.d.ts +1 -1
  83. package/dist/main/video/video.js +22 -11
  84. package/dist/workers/mediaWorker.js +23 -23
  85. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +3 -3
@@ -21,17 +21,31 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
21
21
  credentials,
22
22
  });
23
23
  mediaWorker.addEventListener('error', error => {
24
- log?.(`${error.message}`, error);
24
+ log?.error({ event: error, error: error.error }, `${error.message}`);
25
25
  error.preventDefault();
26
26
  });
27
27
  mediaWorker.addEventListener('message', (event) => {
28
28
  switch (event.data.type) {
29
29
  case 'debug': {
30
- log?.(event.data.data.message, event.data.data.context);
30
+ log?.debug(event.data.data.context, event.data.data.message);
31
31
  break;
32
32
  }
33
33
  case 'event': {
34
- log?.(event.data.data.type, event.data.data.message);
34
+ switch (event.data.data.type) {
35
+ case 'Error':
36
+ case 'ContextCreationError':
37
+ case 'SamplingError':
38
+ log?.error(event.data.data, event.data.data.message);
39
+ break;
40
+ case 'ContextLost':
41
+ case 'ContextRestored':
42
+ case 'ProcessorRestarted':
43
+ log?.info(event.data.data, event.data.data.message);
44
+ break;
45
+ default:
46
+ log?.debug(event.data.data, event.data.data.message);
47
+ break;
48
+ }
35
49
  for (const cb of props.eventHandlers.get(event.data.data.type) ?? []) {
36
50
  cb();
37
51
  }
@@ -72,9 +86,7 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
72
86
  }
73
87
  }
74
88
  catch (err) {
75
- log?.('Failed to restart process', {
76
- error: err,
77
- });
89
+ log?.error({ error: err }, 'Failed to restart process');
78
90
  props.status = PROCESS_STATUS.Error;
79
91
  }
80
92
  };
@@ -100,20 +112,26 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
100
112
  }
101
113
  case 'event': {
102
114
  removeListener();
103
- reject(new Error(ev.data.data.type, {
104
- cause: ev.data.data.message,
105
- }));
115
+ reject(ev.data.data.error ??
116
+ new Error(ev.data.data.type, {
117
+ cause: ev.data.data.message,
118
+ }));
106
119
  break;
107
120
  }
108
121
  default: {
109
- removeListener();
110
- reject(new Error(`UnhandledEvent: ${ev.data.type}`));
122
+ // Ignore messages that don't match the expected
123
+ // response type. Other concurrent requests (e.g.
124
+ // a `close` arriving while a `processed` is in
125
+ // flight) post their own responses on the same
126
+ // worker; rejecting here would cross-poison
127
+ // listeners and surface false errors.
128
+ break;
111
129
  }
112
130
  }
113
131
  };
114
132
  const handleError = (error) => {
115
133
  removeListener();
116
- reject(error.message);
134
+ reject(error.error ?? new Error(error.message));
117
135
  };
118
136
  const removeListener = () => {
119
137
  props.mediaWorker?.removeEventListener('message', handleMessage);
@@ -142,7 +160,9 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
142
160
  },
143
161
  backgroundImage,
144
162
  };
145
- log?.('Init processor with config', { config: processorSettings });
163
+ log?.debug({
164
+ config: processorSettings,
165
+ }, 'Init processor with config');
146
166
  const result = await postMsg('opened', {
147
167
  type: 'open',
148
168
  data: processorSettings,
@@ -152,9 +172,7 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
152
172
  }
153
173
  catch (error) {
154
174
  props.status = PROCESS_STATUS.Error;
155
- log?.('Failed to open process', {
156
- error: error,
157
- });
175
+ log?.warn({ error: error }, 'Failed to open process');
158
176
  return {};
159
177
  }
160
178
  };
@@ -178,7 +196,7 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
178
196
  }
179
197
  catch (error) {
180
198
  props.status = PROCESS_STATUS.Error;
181
- log?.('Error during the processing, return original track instead.', { error });
199
+ log?.warn({ error }, 'Error during the processing, return original track instead.');
182
200
  return input;
183
201
  }
184
202
  }
@@ -196,9 +214,7 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
196
214
  }
197
215
  catch (error) {
198
216
  props.status = PROCESS_STATUS.Error;
199
- log?.('Error during the processing, return original ReadWritableStream instead.', {
200
- error,
201
- });
217
+ log?.warn({ error }, 'Error during the processing, return original ReadWritableStream instead.');
202
218
  return input;
203
219
  }
204
220
  }
@@ -212,7 +228,8 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
212
228
  const handleMessage = (ev) => {
213
229
  if (ev.data.type === 'processed' ||
214
230
  ev.data.type === 'opened' ||
215
- ev.data.type === 'updated') {
231
+ ev.data.type === 'updated' ||
232
+ ev.data.type === 'closed') {
216
233
  props.mediaWorker?.removeEventListener('message', handleMessage);
217
234
  resolve(undefined);
218
235
  }
@@ -228,7 +245,7 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
228
245
  props.status = PROCESS_STATUS.Updating;
229
246
  const backgroundImage = options.backgroundImage &&
230
247
  (await cloneImageRecord(options.backgroundImage));
231
- log?.('Update processor config', { config: options });
248
+ log?.debug({ config: options }, 'Update processor config');
232
249
  const transfer = backgroundImage ? [backgroundImage.image] : [];
233
250
  const changed = await postMsg('updated', {
234
251
  type: 'update',
@@ -242,24 +259,22 @@ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', impo
242
259
  }
243
260
  catch (error) {
244
261
  props.status = PROCESS_STATUS.Error;
245
- log?.('Failed to update process', {
246
- error: error,
247
- });
262
+ log?.warn({ error: error }, 'Failed to update process');
248
263
  return {};
249
264
  }
250
265
  };
251
266
  const mute = (muted) => {
252
267
  postMsg(undefined, { type: 'mute', data: muted });
253
268
  };
254
- const close = () => {
269
+ const close = async () => {
255
270
  props.status = PROCESS_STATUS.Closing;
256
- log?.('Close processor');
257
- postMsg(undefined, { type: 'close' });
271
+ log?.info('Close processor');
272
+ await postMsg('closed', { type: 'close' });
258
273
  props.status = PROCESS_STATUS.Closed;
259
274
  };
260
275
  const destroy = async () => {
261
276
  props.status = PROCESS_STATUS.Destroying;
262
- log?.('Destroy processor');
277
+ log?.info('Destroy processor');
263
278
  props.eventHandlers.clear();
264
279
  postMsg(undefined, { type: 'destroy' });
265
280
  props.mediaWorker?.terminate();
@@ -18,7 +18,7 @@ export interface AsyncAssets {
18
18
  export interface Process<T> {
19
19
  status: ProcessStatus;
20
20
  open(): Promise<T>;
21
- close(): void;
21
+ close(): Promise<void>;
22
22
  destroy(): Promise<void>;
23
23
  }
24
24
  export interface BackgroundImage {
@@ -21,7 +21,7 @@ export interface VideoProcessor {
21
21
  /**
22
22
  * Stop the process of video segmentation
23
23
  */
24
- close(): void;
24
+ close(): Promise<void>;
25
25
  /**
26
26
  * Destroy the segmentation wasm instance
27
27
  */
@@ -1,5 +1,4 @@
1
1
  import { assert, isEmpty } from '@pexip/utils';
2
- import { AbortReason } from '../../common/constants';
3
2
  import { BACKGROUND_BLUR_AMOUNT, BACKGROUND_THRESHOLD, DOWN_SAMPLE_FACTOR, EDGE_BLUR_AMOUNT, EXCLUDE_BYSTANDERS, FOREGROUND_THRESHOLD, LIGHT_WRAP_BLUR_AMOUNT, LIGHT_WRAP_EDGE_BAND, LIGHT_WRAP_INTENSITY, LIGHT_WRAP_TIGHTNESS, MASK_COMBINE_RATIO, MORPH_DILATE_RADIUS_PX, MORPH_ERODE_RADIUS_PX, MORPH_PASS, PERSON_CENTER, PROCESSING_HEIGHT, PROCESSING_WIDTH, SIGMA_RANGE, SIGMA_SPACE, } from '../../common/constants';
4
3
  import { clamping, createObjectUpdater, compareImageRecords, } from '../../common/utils';
5
4
  import { assertMediaStreamVideoTrack, isRenderEffects } from './typeGuards';
@@ -278,9 +277,26 @@ export const createVideoProcessor = (segmenter, inputProcessAdaptor) => {
278
277
  const update = async (options) => {
279
278
  await updateProcessorOptions(options);
280
279
  };
281
- const process = async (source) => {
280
+ // Serialization lock: `process()` and `close()` mutate the same worker
281
+ // pipeline (segmenter + transferred tracks). If they overlap, the worker
282
+ // can tear down a writable stream while the main thread is still feeding
283
+ // it, surfacing as `InvalidStateError: Stream closed`. Chain every
284
+ // public mutation through this promise so they always run end-to-end.
285
+ let serialized = Promise.resolve();
286
+ const runSerialized = (fn) => {
287
+ const next = serialized.then(fn, fn);
288
+ serialized = next.catch(() => undefined);
289
+ return next;
290
+ };
291
+ const closeImpl = async () => {
292
+ await segmenter.close();
293
+ props.outputTrack?.stop();
294
+ props.outputTrack = undefined;
295
+ props.processing = false;
296
+ };
297
+ const processImpl = async (source) => {
282
298
  if (props.processing) {
283
- close();
299
+ await closeImpl();
284
300
  }
285
301
  assertMediaStreamVideoTrack(source);
286
302
  if (segmenter.status === 'new') {
@@ -302,15 +318,10 @@ export const createVideoProcessor = (segmenter, inputProcessAdaptor) => {
302
318
  props.processing = true;
303
319
  return props.outputTrack;
304
320
  };
305
- const close = () => {
306
- segmenter.close();
307
- props.abortController?.abort(AbortReason.Close);
308
- props.outputTrack?.stop();
309
- props.outputTrack = undefined;
310
- props.processing = false;
311
- };
321
+ const process = source => runSerialized(() => processImpl(source));
322
+ const close = () => runSerialized(closeImpl);
312
323
  const destroy = async () => {
313
- close();
324
+ await close();
314
325
  processorOptions?.backgroundImage?.image?.close();
315
326
  renderOptions = initRenderOptions();
316
327
  changedRenderOptions = {};