@pexip/media-processor 21.0.0 → 22.0.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 (115) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +17 -10
  3. package/api-docs/README.mdx +37 -18
  4. package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
  5. package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
  6. package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
  7. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
  8. package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
  9. package/api-docs/functions/createVideoProcessor.mdx +3 -3
  10. package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
  11. package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
  12. package/api-docs/functions/playVideo.mdx +2 -2
  13. package/api-docs/interfaces/Analyzer.mdx +4 -0
  14. package/api-docs/interfaces/Benchmark.mdx +14 -2
  15. package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
  16. package/api-docs/interfaces/Gain.mdx +4 -0
  17. package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
  18. package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
  19. package/api-docs/interfaces/Process.mdx +8 -3
  20. package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
  21. package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
  22. package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
  23. package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
  24. package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
  25. package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
  26. package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
  27. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
  28. package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
  29. package/api-docs/interfaces/Renderer.mdx +2 -2
  30. package/api-docs/interfaces/RendererOptions.mdx +0 -4
  31. package/api-docs/interfaces/Segmenter.mdx +89 -10
  32. package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
  33. package/api-docs/interfaces/VideoProcessor.mdx +49 -5
  34. package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
  35. package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
  36. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
  37. package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
  38. package/api-docs/type-aliases/Struct.mdx +9 -0
  39. package/api-docs/variables/FRAME_RATE.mdx +1 -1
  40. package/api-docs/variables/ImageFetchError.mdx +3 -0
  41. package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
  42. package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
  43. package/api-docs/variables/cloneImageRecord.mdx +19 -0
  44. package/api-docs/variables/compareImageRecords.mdx +18 -0
  45. package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
  46. package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
  47. package/api-docs/variables/isValidGpuAPI.mdx +13 -0
  48. package/dist/common/backends/webgl/renderer.js +2 -12
  49. package/dist/common/backends/webgl/texture.d.ts +1 -2
  50. package/dist/common/backends/webgl/texture.js +2 -2
  51. package/dist/common/backends/webgl/webglUtils.js +8 -4
  52. package/dist/common/backends/webgpu/renderer.js +2 -12
  53. package/dist/common/backends/webgpu/tentBlur.js +1 -1
  54. package/dist/common/backends/webgpu/texture.d.ts +1 -2
  55. package/dist/common/backends/webgpu/texture.js +6 -2
  56. package/dist/common/constants.d.ts +3 -0
  57. package/dist/common/constants.js +4 -0
  58. package/dist/common/index.d.ts +1 -0
  59. package/dist/common/index.js +1 -0
  60. package/dist/common/tsconfig.tsbuildinfo +1 -1
  61. package/dist/common/typeGuards.d.ts +2 -0
  62. package/dist/common/typeGuards.js +7 -0
  63. package/dist/common/types/media.d.ts +5 -1
  64. package/dist/common/types/messageEvents.d.ts +31 -4
  65. package/dist/common/types/processor.d.ts +6 -4
  66. package/dist/common/types/render.d.ts +5 -2
  67. package/dist/common/types/render.js +23 -1
  68. package/dist/common/types/segmentation.d.ts +0 -12
  69. package/dist/common/utils.d.ts +13 -2
  70. package/dist/common/utils.js +54 -10
  71. package/dist/main/audio.js +3 -3
  72. package/dist/main/benchUtils.d.ts +2 -2
  73. package/dist/main/benchUtils.js +4 -4
  74. package/dist/main/index.d.ts +1 -1
  75. package/dist/main/index.js +1 -1
  76. package/dist/main/tsconfig.tsbuildinfo +1 -1
  77. package/dist/main/types.d.ts +0 -6
  78. package/dist/main/utils.d.ts +4 -0
  79. package/dist/main/utils.js +31 -0
  80. package/dist/main/video/constants.d.ts +1 -4
  81. package/dist/main/video/constants.js +1 -5
  82. package/dist/main/video/index.d.ts +2 -3
  83. package/dist/main/video/index.js +2 -3
  84. package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
  85. package/dist/main/video/inputProcessAdaptor.js +198 -0
  86. package/dist/main/video/segmenter.d.ts +35 -0
  87. package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
  88. package/dist/main/video/typeGuards.d.ts +2 -0
  89. package/dist/main/video/typeGuards.js +8 -1
  90. package/dist/main/video/types.d.ts +36 -16
  91. package/dist/main/video/utils.d.ts +5 -4
  92. package/dist/main/video/utils.js +49 -44
  93. package/dist/main/video/video.d.ts +29 -7
  94. package/dist/main/video/video.js +323 -28
  95. package/dist/workers/mediaWorker.js +23 -23
  96. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  97. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +7 -7
  99. package/api-docs/functions/createCanvasTransform.mdx +0 -14
  100. package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
  101. package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
  102. package/api-docs/interfaces/Detector.mdx +0 -81
  103. package/api-docs/interfaces/SegmentationParams.mdx +0 -35
  104. package/api-docs/interfaces/Transform.mdx +0 -62
  105. package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
  106. package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
  107. package/dist/main/transformer.d.ts +0 -1
  108. package/dist/main/transformer.js +0 -4
  109. package/dist/main/video/canvasTransform.d.ts +0 -17
  110. package/dist/main/video/canvasTransform.js +0 -341
  111. package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
  112. package/dist/main/video/transformer.d.ts +0 -9
  113. package/dist/main/video/transformer.js +0 -68
  114. package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
  115. package/dist/main/video/videoStreamTrackProcessor.js +0 -86
@@ -1,51 +1,346 @@
1
- import { stopStreamTracks } from '../utils';
2
- import { AbortReason } from './constants';
3
- export const createVideoProcessor = (transformers, processTrack) => {
1
+ import { assert, isEmpty } from '@pexip/utils';
2
+ import { AbortReason } from '../../common/constants';
3
+ 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
+ import { clamping, createObjectUpdater, compareImageRecords, } from '../../common/utils';
5
+ import { assertMediaStreamVideoTrack, isRenderEffects } from './typeGuards';
6
+ const clampInRangeFrom0To1 = clamping(0, 1);
7
+ const clampInRangeFrom0To9 = clamping(0, 9);
8
+ const createRenderOptions = ({ foregroundThreshold = FOREGROUND_THRESHOLD, backgroundThreshold = BACKGROUND_THRESHOLD, backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, edgeBlurAmount = EDGE_BLUR_AMOUNT, maskCombineRatio = MASK_COMBINE_RATIO, backgroundImageUrl = '', sigmaSpace = SIGMA_SPACE, sigmaRangeLo = SIGMA_RANGE[0], sigmaRangeHi = SIGMA_RANGE[1], effects = 'none', backend = 'webgl', personCenter = PERSON_CENTER, excludeBystanders = EXCLUDE_BYSTANDERS, morphPass = MORPH_PASS, morphErodeRadiusPx = MORPH_ERODE_RADIUS_PX, morphDilateRadiusPx = MORPH_DILATE_RADIUS_PX, lightWrapIntensity = LIGHT_WRAP_INTENSITY, downSampleFactor = DOWN_SAMPLE_FACTOR, lightWrapTightness = LIGHT_WRAP_TIGHTNESS, lightWrapBlurAmount = LIGHT_WRAP_BLUR_AMOUNT, lightWrapEdgeBand = LIGHT_WRAP_EDGE_BAND, } = {}, onChange) => {
9
+ const props = {
10
+ foregroundThreshold,
11
+ backgroundThreshold,
12
+ backgroundBlurAmount,
13
+ edgeBlurAmount,
14
+ maskCombineRatio,
15
+ backgroundImageUrl,
16
+ sigmaSpace,
17
+ sigmaRangeLo,
18
+ sigmaRangeHi,
19
+ effects,
20
+ backend,
21
+ personCenter,
22
+ excludeBystanders,
23
+ morphPass,
24
+ morphErodeRadiusPx,
25
+ morphDilateRadiusPx,
26
+ lightWrapIntensity,
27
+ downSampleFactor,
28
+ lightWrapTightness,
29
+ lightWrapBlurAmount,
30
+ lightWrapEdgeBand,
31
+ };
32
+ return {
33
+ original: props,
34
+ proxy: new Proxy(props, {
35
+ get(target, p, receiver) {
36
+ return Reflect.get(target, p, receiver);
37
+ },
38
+ set(target, p, newValue, receiver) {
39
+ switch (p) {
40
+ case 'backgroundThreshold': {
41
+ const value = clamping(0, target.foregroundThreshold)(newValue);
42
+ if (value !== target[p]) {
43
+ Reflect.set(target, p, value, receiver);
44
+ onChange(p, value);
45
+ }
46
+ break;
47
+ }
48
+ case 'foregroundThreshold': {
49
+ const value = clamping(target.backgroundThreshold, 1)(newValue);
50
+ if (value !== target[p]) {
51
+ Reflect.set(target, p, value, receiver);
52
+ onChange(p, value);
53
+ }
54
+ break;
55
+ }
56
+ case 'maskCombineRatio': {
57
+ const value = clampInRangeFrom0To1(newValue);
58
+ if (value !== target[p]) {
59
+ Reflect.set(target, p, value, receiver);
60
+ onChange(p, value);
61
+ }
62
+ break;
63
+ }
64
+ case 'lightWrapBlurAmount':
65
+ case 'backgroundBlurAmount':
66
+ case 'edgeBlurAmount': {
67
+ // Expecting integer number
68
+ const value = Math.trunc(clampInRangeFrom0To9(newValue));
69
+ if (value !== target[p]) {
70
+ Reflect.set(target, p, value, receiver);
71
+ onChange(p, value);
72
+ }
73
+ break;
74
+ }
75
+ case 'backgroundImageUrl': {
76
+ if (newValue !== target[p]) {
77
+ Reflect.set(target, p, newValue, receiver);
78
+ onChange(p, newValue);
79
+ }
80
+ break;
81
+ }
82
+ case 'effects': {
83
+ if (isRenderEffects(newValue) &&
84
+ newValue !== target[p]) {
85
+ Reflect.set(target, p, newValue, receiver);
86
+ onChange(p, newValue);
87
+ }
88
+ break;
89
+ }
90
+ case 'backend': {
91
+ if ((newValue === 'webgl' || newValue === 'webgpu') &&
92
+ newValue !== target[p]) {
93
+ Reflect.set(target, p, newValue, receiver);
94
+ onChange(p, newValue);
95
+ }
96
+ break;
97
+ }
98
+ case 'sigmaSpace': {
99
+ // less than 0 is unknown behavior, more than 12 is too much
100
+ const value = clamping(0, 12)(newValue);
101
+ if (value !== target.sigmaSpace) {
102
+ Reflect.set(target, p, value, receiver);
103
+ onChange(p, value);
104
+ }
105
+ break;
106
+ }
107
+ case 'sigmaRangeLo': {
108
+ const value = clamping(0, target.sigmaRangeHi)(newValue);
109
+ if (value !== target.sigmaRangeLo) {
110
+ Reflect.set(target, p, value, receiver);
111
+ onChange(p, value);
112
+ }
113
+ break;
114
+ }
115
+ case 'sigmaRangeHi': {
116
+ const value = clamping(target.sigmaRangeLo, 1)(newValue);
117
+ if (value !== target.sigmaRangeHi) {
118
+ Reflect.set(target, p, value, receiver);
119
+ onChange(p, value);
120
+ }
121
+ break;
122
+ }
123
+ case 'personCenter': {
124
+ if (Array.isArray(newValue)) {
125
+ const [x, y] = newValue.map(clampInRangeFrom0To1);
126
+ if (x !== undefined &&
127
+ y !== undefined &&
128
+ (x !== target.personCenter[0] ||
129
+ y !== target.personCenter[1])) {
130
+ Reflect.set(target, p, [x, y], receiver);
131
+ onChange(p, [x, y]);
132
+ }
133
+ }
134
+ break;
135
+ }
136
+ case 'excludeBystanders': {
137
+ if (newValue !== target[p]) {
138
+ Reflect.set(target, p, newValue, receiver);
139
+ onChange(p, newValue);
140
+ }
141
+ break;
142
+ }
143
+ case 'morphDilateRadiusPx':
144
+ case 'morphErodeRadiusPx': {
145
+ const value = clamping(0, 12)(newValue);
146
+ if (value !== target[p]) {
147
+ Reflect.set(target, p, value, receiver);
148
+ onChange(p, value);
149
+ }
150
+ break;
151
+ }
152
+ case 'morphPass': {
153
+ // Expecting integer number
154
+ const value = Math.trunc(clamping(0, 10)(newValue));
155
+ if (value !== target[p]) {
156
+ Reflect.set(target, p, value, receiver);
157
+ onChange(p, value);
158
+ }
159
+ break;
160
+ }
161
+ case 'lightWrapIntensity': {
162
+ const value = clampInRangeFrom0To1(newValue);
163
+ if (value !== target[p]) {
164
+ Reflect.set(target, p, value, receiver);
165
+ onChange(p, value);
166
+ }
167
+ break;
168
+ }
169
+ case 'lightWrapTightness': {
170
+ // According to the doc
171
+ const value = clamping(0.5, 4)(newValue);
172
+ if (value !== target[p]) {
173
+ Reflect.set(target, p, value, receiver);
174
+ onChange(p, value);
175
+ }
176
+ break;
177
+ }
178
+ case 'lightWrapEdgeBand': {
179
+ const value = clamping(0, (target.foregroundThreshold -
180
+ target.backgroundThreshold) /
181
+ 2)(newValue);
182
+ if (value !== target[p]) {
183
+ Reflect.set(target, p, value, receiver);
184
+ onChange(p, value);
185
+ }
186
+ break;
187
+ }
188
+ case 'downSampleFactor': {
189
+ // NOTE: There is no impl other than 2 and 4, so we just hardcode it here
190
+ if ((newValue === 2 || newValue === 4) &&
191
+ newValue !== target[p]) {
192
+ Reflect.set(target, p, newValue, receiver);
193
+ onChange(p, newValue);
194
+ }
195
+ break;
196
+ }
197
+ default: {
198
+ // Catch all the unknown keys, and just ignore the set operation
199
+ return false;
200
+ }
201
+ }
202
+ // No throw
203
+ return true;
204
+ },
205
+ }),
206
+ };
207
+ };
208
+ export const createVideoProcessor = (segmenter, inputProcessAdaptor) => {
209
+ const canUpdate = () => segmenter.status !== 'new';
4
210
  const props = {
5
211
  processing: false,
6
212
  };
213
+ let changedRenderOptions = {};
214
+ const initRenderOptions = () => createRenderOptions({}, (k, v) => {
215
+ changedRenderOptions[k] = v;
216
+ });
217
+ let renderOptions = initRenderOptions();
218
+ let processorOptions = {
219
+ renderOptions: renderOptions.original,
220
+ processingWidth: PROCESSING_WIDTH,
221
+ processingHeight: PROCESSING_HEIGHT,
222
+ };
223
+ const updateRendererOptions = createObjectUpdater(renderOptions.proxy);
224
+ const updateChangedRendererOptions = (changed) => {
225
+ for (const [k, v] of Object.entries(changed)) {
226
+ //@ts-expect-error
227
+ processorOptions.renderOptions[k] = v;
228
+ }
229
+ };
230
+ const updateProcessorOptions = async (options) => {
231
+ const _processorOptions = {};
232
+ if (options?.renderOptions) {
233
+ updateRendererOptions(options.renderOptions);
234
+ if (!isEmpty(changedRenderOptions)) {
235
+ changedRenderOptions = {};
236
+ _processorOptions.renderOptions =
237
+ processorOptions.renderOptions;
238
+ }
239
+ }
240
+ if (options?.backgroundImage &&
241
+ !compareImageRecords(options.backgroundImage, processorOptions.backgroundImage)) {
242
+ processorOptions.backgroundImage = options.backgroundImage;
243
+ _processorOptions.backgroundImage =
244
+ processorOptions.backgroundImage;
245
+ }
246
+ if (options.processingWidth) {
247
+ processorOptions.processingWidth = options.processingWidth;
248
+ _processorOptions.processingWidth =
249
+ processorOptions.processingWidth;
250
+ }
251
+ if (options.processingHeight) {
252
+ processorOptions.processingHeight = options.processingHeight;
253
+ _processorOptions.processingHeight =
254
+ processorOptions.processingHeight;
255
+ }
256
+ if (options.imageSegmenterOptions) {
257
+ processorOptions.imageSegmenterOptions = {
258
+ ...processorOptions.imageSegmenterOptions,
259
+ ...options.imageSegmenterOptions,
260
+ };
261
+ _processorOptions.imageSegmenterOptions =
262
+ processorOptions.imageSegmenterOptions;
263
+ }
264
+ if (canUpdate() && !isEmpty(_processorOptions)) {
265
+ const changed = await segmenter.update(_processorOptions);
266
+ updateChangedRendererOptions(changed);
267
+ }
268
+ };
7
269
  const open = async (options) => {
8
- await Promise.all(transformers.map(transformer => transformer.init(options)));
270
+ if (options) {
271
+ await updateProcessorOptions(options);
272
+ }
273
+ if (segmenter.status === 'new') {
274
+ const changed = await segmenter.open(processorOptions);
275
+ updateChangedRendererOptions(changed);
276
+ }
277
+ };
278
+ const update = async (options) => {
279
+ await updateProcessorOptions(options);
9
280
  };
10
281
  const process = async (source) => {
11
- const [track] = source.getVideoTracks();
12
- if (!track) {
13
- return source;
14
- }
15
282
  if (props.processing) {
16
- throw new Error('Cannot process when it is already processing');
17
- }
18
- props.abortController = new AbortController();
19
- const trackGenerated = await processTrack(track, transformers, {
20
- signal: props.abortController.signal,
21
- });
22
- props.outputStream = new MediaStream([
23
- trackGenerated,
24
- ...source.getAudioTracks().map(track => track.clone()),
25
- ]);
283
+ close();
284
+ }
285
+ assertMediaStreamVideoTrack(source);
286
+ if (segmenter.status === 'new') {
287
+ await segmenter.open(processorOptions);
288
+ // @ts-expect-error -- Segmenter status could be changed to Error if anything went wrong
289
+ if (segmenter.status === 'Error') {
290
+ return source;
291
+ }
292
+ }
293
+ const processed = await segmenter.process(inputProcessAdaptor.adapt(source), processorOptions.renderOptions);
294
+ props.outputTrack = inputProcessAdaptor.outputTrack;
295
+ if (processed instanceof MediaStreamTrack) {
296
+ props.outputTrack = processed;
297
+ }
298
+ else if (!props.outputTrack) {
299
+ props.outputTrack = source;
300
+ }
301
+ assert(props.outputTrack, 'Segmenter failed to generate output track');
26
302
  props.processing = true;
27
- return props.outputStream;
303
+ return props.outputTrack;
28
304
  };
29
305
  const close = () => {
30
- if (!props.processing) {
31
- return;
32
- }
306
+ segmenter.close();
33
307
  props.abortController?.abort(AbortReason.Close);
34
- stopStreamTracks(props.outputStream);
35
- props.outputStream = undefined;
308
+ props.outputTrack?.stop();
309
+ props.outputTrack = undefined;
36
310
  props.processing = false;
37
- for (const transformer of transformers) {
38
- transformer.close();
39
- }
40
311
  };
41
312
  const destroy = async () => {
42
313
  close();
43
- await Promise.all(transformers.map(transformer => transformer.destroy()));
314
+ processorOptions?.backgroundImage?.image?.close();
315
+ renderOptions = initRenderOptions();
316
+ changedRenderOptions = {};
317
+ processorOptions = {
318
+ renderOptions: renderOptions.original,
319
+ processingWidth: PROCESSING_WIDTH,
320
+ processingHeight: PROCESSING_HEIGHT,
321
+ };
322
+ await segmenter.destroy();
44
323
  };
45
324
  return {
46
325
  open,
326
+ update,
47
327
  process,
328
+ mute(muted) {
329
+ segmenter.mute(muted);
330
+ },
48
331
  close,
49
332
  destroy,
333
+ get status() {
334
+ return segmenter.status;
335
+ },
336
+ get renderOptions() {
337
+ return processorOptions.renderOptions;
338
+ },
339
+ get processingWidth() {
340
+ return processorOptions.processingWidth;
341
+ },
342
+ get processingHeight() {
343
+ return processorOptions.processingHeight;
344
+ },
50
345
  };
51
346
  };