@pexip/media-processor 19.1.0 → 20.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 (69) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/common/backends/webgl/blur.js +7 -5
  3. package/dist/common/backends/webgl/renderer.d.ts +1 -1
  4. package/dist/common/backends/webgl/renderer.js +27 -34
  5. package/dist/common/backends/webgl/smoothingMask.d.ts +1 -1
  6. package/dist/common/backends/webgl/webglUtils.d.ts +1 -1
  7. package/dist/common/backends/webgl/webglUtils.js +9 -10
  8. package/dist/common/backends/webgpu/blender.d.ts +8 -0
  9. package/dist/common/backends/webgpu/blender.js +78 -0
  10. package/dist/common/backends/webgpu/constants.d.ts +1 -0
  11. package/dist/common/backends/webgpu/constants.js +1 -0
  12. package/dist/common/backends/webgpu/dualFilterBlur.d.ts +5 -0
  13. package/dist/common/backends/webgpu/dualFilterBlur.js +201 -0
  14. package/dist/common/backends/webgpu/renderer.d.ts +2 -0
  15. package/dist/common/backends/webgpu/renderer.js +127 -0
  16. package/dist/common/backends/webgpu/smoothingMask.d.ts +5 -0
  17. package/dist/common/backends/webgpu/smoothingMask.js +66 -0
  18. package/dist/common/backends/webgpu/texture.d.ts +5 -0
  19. package/dist/common/backends/webgpu/texture.js +35 -0
  20. package/dist/common/backends/webgpu/textureToTexture.d.ts +4 -0
  21. package/dist/common/backends/webgpu/textureToTexture.js +35 -0
  22. package/dist/common/backends/webgpu/types.d.ts +5 -0
  23. package/dist/common/backends/webgpu/types.js +1 -0
  24. package/dist/common/backends/webgpu/webgpuUtils.d.ts +7 -0
  25. package/dist/common/backends/webgpu/webgpuUtils.js +14 -0
  26. package/dist/common/canvasRenderUtils.d.ts +1 -1
  27. package/dist/common/canvasRenderUtils.js +8 -10
  28. package/dist/common/constants.d.ts +5 -0
  29. package/dist/common/constants.js +4 -0
  30. package/dist/common/index.d.ts +0 -1
  31. package/dist/common/index.js +0 -1
  32. package/dist/common/m3.d.ts +14 -8
  33. package/dist/common/m3.js +0 -1
  34. package/dist/common/tsconfig.tsbuildinfo +1 -1
  35. package/dist/common/typeGuards.d.ts +2 -0
  36. package/dist/common/typeGuards.js +7 -0
  37. package/dist/common/types/render.d.ts +7 -4
  38. package/dist/common/types/segmentation.d.ts +5 -1
  39. package/dist/common/utils.d.ts +4 -1
  40. package/dist/common/utils.js +36 -1
  41. package/dist/main/audio.d.ts +1 -1
  42. package/dist/main/audio.js +7 -5
  43. package/dist/main/math.js +1 -1
  44. package/dist/main/process.d.ts +3 -3
  45. package/dist/main/process.js +2 -2
  46. package/dist/main/processor.d.ts +0 -1
  47. package/dist/main/transformer.d.ts +1 -1
  48. package/dist/main/tsconfig.tsbuildinfo +1 -1
  49. package/dist/main/typeGuards.d.ts +1 -1
  50. package/dist/main/types.d.ts +2 -1
  51. package/dist/main/utils.js +6 -6
  52. package/dist/main/video/canvasTransform.d.ts +1 -1
  53. package/dist/main/video/canvasTransform.js +66 -12
  54. package/dist/main/video/segmenters/mediapipe.d.ts +1 -11
  55. package/dist/main/video/segmenters/mediapipe.js +37 -22
  56. package/dist/main/video/transformer.d.ts +1 -1
  57. package/dist/main/video/typeGuards.d.ts +2 -1
  58. package/dist/main/video/types.d.ts +2 -12
  59. package/dist/main/video/utils.d.ts +2 -2
  60. package/dist/main/video/utils.js +2 -2
  61. package/dist/main/video/video.d.ts +3 -2
  62. package/dist/main/video/video.js +5 -3
  63. package/dist/main/video/videoStreamTrackProcessor.d.ts +1 -1
  64. package/dist/main/video/videoStreamTrackProcessor.js +2 -2
  65. package/dist/workers/mediaWorker.js +16 -16
  66. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  67. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  68. package/dist/worklets/types.d.ts +0 -2
  69. package/package.json +44 -8
@@ -1,16 +1,6 @@
1
1
  import type { Segmenter } from '../types';
2
2
  import type { Delegate, SegmenterOptions, ImageSegmenterOptions, SegmentationModelAsset } from '../../../common/types/segmentation';
3
3
  interface Options extends Omit<ImageSegmenterOptions, 'delegate'> {
4
- /**
5
- * Processing width for the processing canvas.
6
- * @defaultValue 768
7
- */
8
- processingWidth?: number;
9
- /**
10
- * Processing height for the processing canvas.
11
- * @defaultValue 432
12
- */
13
- processingHeight?: number;
14
4
  /**
15
5
  * The URL for the worker script.
16
6
  */
@@ -28,5 +18,5 @@ interface Options extends Omit<ImageSegmenterOptions, 'delegate'> {
28
18
  delegate?: () => Delegate;
29
19
  log?: (message: string, meta?: unknown) => void;
30
20
  }
31
- export declare const createSegmenter: (basePath: SegmenterOptions['basePath'], { processingWidth, processingHeight, workerScriptUrl, credentials, delegate, log, ...options }: Options) => Segmenter;
21
+ export declare const createSegmenter: (basePath?: SegmenterOptions["basePath"], { workerScriptUrl, credentials, delegate, log, ...options }?: Options) => Segmenter;
32
22
  export {};
@@ -1,5 +1,5 @@
1
1
  import { assert } from '@pexip/utils';
2
- import { PROCESS_STATUS, PROCESSING_WIDTH, PROCESSING_HEIGHT, RENDERING_EVENTS, } from '../../../common/constants';
2
+ import { PROCESS_STATUS, RENDERING_EVENTS } from '../../../common/constants';
3
3
  const cloneImageRecord = async (image) => {
4
4
  const cloned = await createImageBitmap(image.image);
5
5
  return { image: cloned, key: image.key };
@@ -12,7 +12,7 @@ export const createSegmenter = (
12
12
  * @see {@link
13
13
  * https://ai.google.dev/edge/api/mediapipe/js/tasks-vision.filesetresolver#filesetresolverforvisiontasks | FileResolver}
14
14
  */
15
- basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESSING_HEIGHT, workerScriptUrl = new URL('../../../workers/mediaWorker.js', import.meta.url), credentials = 'same-origin', delegate, log, ...options }) => {
15
+ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', import.meta.url), credentials = 'same-origin', delegate, log, ...options } = { modelAsset: { modelName: 'selfie', path: '' } }) => {
16
16
  const props = {
17
17
  status: PROCESS_STATUS.New,
18
18
  modelAsset: options.modelAsset,
@@ -34,11 +34,9 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
34
34
  }
35
35
  if (type === 'event') {
36
36
  log?.(event.data.data.type, event.data.data.message);
37
- props.eventHandlers
38
- .get(event.data.data.type)
39
- ?.forEach(cb => {
37
+ for (const cb of props.eventHandlers.get(event.data.data.type) ?? []) {
40
38
  cb();
41
- });
39
+ }
42
40
  switch (event.data.data.type) {
43
41
  case RENDERING_EVENTS.ProcessorRestarted:
44
42
  break;
@@ -55,14 +53,15 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
55
53
  };
56
54
  const handleContextRestored = async () => {
57
55
  try {
56
+ props.status = PROCESS_STATUS.Updating;
58
57
  await postMsg('updated', {
59
58
  type: 'update',
60
59
  data: { restart: true },
61
60
  });
62
61
  props.status = PROCESS_STATUS.Updated;
63
- props.eventHandlers.get('ProcessorRestarted')?.forEach(cb => {
62
+ for (const cb of props.eventHandlers.get('ContextRestored') ?? []) {
64
63
  cb();
65
- });
64
+ }
66
65
  }
67
66
  catch (err) {
68
67
  log?.('Failed to restart process', {
@@ -114,22 +113,22 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
114
113
  props.mediaWorker?.postMessage(message, transfer ?? []);
115
114
  });
116
115
  }
117
- const open = async (processorOptions) => {
116
+ const open = async ({ processingWidth, processingHeight, output, imageSegmenterOptions, ...restOptions }) => {
118
117
  props.status = PROCESS_STATUS.Opening;
119
118
  props.mediaWorker = initWorker();
120
- props.output = processorOptions?.output;
121
- const backgroundImage = processorOptions?.backgroundImage &&
122
- (await cloneImageRecord(processorOptions.backgroundImage));
119
+ props.output = output;
120
+ const backgroundImage = restOptions?.backgroundImage &&
121
+ (await cloneImageRecord(restOptions.backgroundImage));
123
122
  try {
124
123
  const processorSettings = {
125
124
  basePath,
126
125
  processingWidth,
127
126
  processingHeight,
128
- ...processorOptions,
127
+ ...restOptions,
129
128
  imageSegmenterOptions: {
130
129
  ...options,
131
130
  delegate: delegate?.(),
132
- ...processorOptions?.imageSegmenterOptions,
131
+ ...imageSegmenterOptions,
133
132
  },
134
133
  backgroundImage,
135
134
  };
@@ -137,7 +136,7 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
137
136
  await postMsg('opened', {
138
137
  type: 'open',
139
138
  data: processorSettings,
140
- }, [processorOptions?.output, backgroundImage?.image].filter(Boolean));
139
+ }, [output, backgroundImage?.image].filter(Boolean));
141
140
  props.status = PROCESS_STATUS.Opened;
142
141
  }
143
142
  catch (error) {
@@ -148,7 +147,8 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
148
147
  }
149
148
  };
150
149
  const process = async (input, options) => {
151
- if (props.status === PROCESS_STATUS.Error) {
150
+ if (props.status === PROCESS_STATUS.Error ||
151
+ props.status === PROCESS_STATUS.Updating) {
152
152
  return input;
153
153
  }
154
154
  props.status = PROCESS_STATUS.Processing;
@@ -172,6 +172,26 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
172
172
  };
173
173
  const update = async (options) => {
174
174
  try {
175
+ if (props.status === PROCESS_STATUS.Processing ||
176
+ props.status === PROCESS_STATUS.Opening ||
177
+ props.status === PROCESS_STATUS.Updating) {
178
+ const waitForStatus = new Promise((resolve, reject) => {
179
+ const handleMessage = (ev) => {
180
+ if (ev.data.type === 'processed' ||
181
+ ev.data.type === 'opened' ||
182
+ ev.data.type === 'updated') {
183
+ props.mediaWorker?.removeEventListener('message', handleMessage);
184
+ resolve(undefined);
185
+ }
186
+ else {
187
+ reject(new Error(`UnhandledEvent: ${ev.data.type}`));
188
+ }
189
+ };
190
+ props.mediaWorker?.addEventListener('message', handleMessage);
191
+ });
192
+ await waitForStatus;
193
+ }
194
+ props.status = PROCESS_STATUS.Updating;
175
195
  const backgroundImage = options.backgroundImage &&
176
196
  (await cloneImageRecord(options.backgroundImage));
177
197
  log?.('Update processor config', { config: options });
@@ -183,6 +203,7 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
183
203
  backgroundImage,
184
204
  },
185
205
  }, transfer);
206
+ props.status = PROCESS_STATUS.Updated;
186
207
  }
187
208
  catch (error) {
188
209
  props.status = PROCESS_STATUS.Error;
@@ -217,12 +238,6 @@ basePath = '/', { processingWidth = PROCESSING_WIDTH, processingHeight = PROCESS
217
238
  get status() {
218
239
  return props.status;
219
240
  },
220
- get width() {
221
- return processingWidth;
222
- },
223
- get height() {
224
- return processingHeight;
225
- },
226
241
  subscribe: (event, callback) => {
227
242
  let handlers = props.eventHandlers.get(event);
228
243
  if (!handlers) {
@@ -1,5 +1,5 @@
1
1
  import type { InputFrame } from './types';
2
- export declare const nullTransformController: (controller: Pick<TransformStreamDefaultController<CanvasImageSource>, 'enqueue' | 'terminate'>) => TransformStreamDefaultController<CanvasImageSource>;
2
+ export declare const nullTransformController: (controller: Pick<TransformStreamDefaultController<CanvasImageSource>, "enqueue" | "terminate">) => TransformStreamDefaultController<CanvasImageSource>;
3
3
  export declare const adaptInputFrameTransformer: (transformer: Transformer<InputFrame, InputFrame>) => {
4
4
  transform: (frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) => void | PromiseLike<void> | undefined;
5
5
  flush?: TransformerFlushCallback<InputFrame> | undefined;
@@ -1 +1,2 @@
1
- export declare const isRenderEffects: (t: unknown) => t is "blur" | "none" | "overlay";
1
+ import { type RenderEffects } from '../../common/constants';
2
+ export declare const isRenderEffects: (t: unknown) => t is RenderEffects;
@@ -11,16 +11,6 @@ export type Color = {
11
11
  a: number;
12
12
  };
13
13
  export type InputFrame = HTMLCanvasElement | OffscreenCanvas | VideoFrame | ImageBitmap;
14
- interface ProcessingSize {
15
- /**
16
- * Processing Width size
17
- */
18
- width: number;
19
- /**
20
- * Processing height size
21
- */
22
- height: number;
23
- }
24
14
  export interface AsyncAssets {
25
15
  tfjsCoreLoaded: boolean;
26
16
  tfjsBackendLoaded: boolean;
@@ -38,7 +28,7 @@ export interface BackgroundImage {
38
28
  }
39
29
  type Unsubscribe = () => void;
40
30
  type Callback<T> = (args: T) => void;
41
- export interface Segmenter extends Process, ProcessingSize {
31
+ export interface Segmenter extends Process {
42
32
  modelAsset: SegmentationModelAsset;
43
33
  backgroundImage?: BackgroundImage;
44
34
  open(options?: ProcessorOptions): Promise<void>;
@@ -54,5 +44,5 @@ export interface SegmentationParams extends RendererOptions {
54
44
  width: number;
55
45
  height: number;
56
46
  }
57
- export type SegmentationTransform = Transform<InputFrame, InputFrame> & SegmentationParams & Omit<Process, 'open'>;
47
+ export type SegmentationTransform = Transform<InputFrame, InputFrame> & SegmentationParams & Pick<Segmenter, 'update'> & Omit<Process, 'open'>;
58
48
  export {};
@@ -56,8 +56,8 @@ export declare const createVideoElement: (width: number, height: number) => HTML
56
56
  export declare const setVideoElementSrc: (video: HTMLVideoElement, src: MediaProvider) => void;
57
57
  export declare const toVideoElement: (input: MediaStream, width: number, height: number) => HTMLVideoElement;
58
58
  export declare const playVideo: (video: HTMLVideoElement, { setTimeout, clearTimeout }?: {
59
- setTimeout?: (((handler: TimerHandler, timeout?: number | undefined, ...arguments: any[]) => number) & typeof setTimeout) | undefined;
60
- clearTimeout?: (((id: number | undefined) => void) & typeof clearTimeout) | undefined;
59
+ setTimeout?: (((handler: TimerHandler, timeout?: number, ...arguments: any[]) => number) & typeof globalThis.setTimeout) | undefined;
60
+ clearTimeout?: (((id: number | undefined) => void) & typeof globalThis.clearTimeout) | undefined;
61
61
  }) => Promise<void>;
62
62
  /**
63
63
  * Load image with provided image element
@@ -81,9 +81,9 @@ export const setVideoElementSrc = (video, src) => {
81
81
  }
82
82
  };
83
83
  if (src instanceof MediaStream) {
84
- src.getVideoTracks().forEach(track => {
84
+ for (const track of src.getVideoTracks()) {
85
85
  track.addEventListener('ended', revokeObjectURL);
86
- });
86
+ }
87
87
  }
88
88
  if ('MediaSource' in window && src instanceof MediaSource) {
89
89
  src.addEventListener('sourceended', revokeObjectURL);
@@ -1,8 +1,9 @@
1
1
  import type { Transform } from '../types';
2
2
  import type { InputFrame } from './types';
3
3
  import type { ProcessVideoTrack } from './videoStreamTrackProcessor';
4
+ import type { ProcessorOptions } from '../../common';
4
5
  export interface VideoProcessor {
5
- open(): Promise<void>;
6
+ open(options?: ProcessorOptions): Promise<void>;
6
7
  /**
7
8
  * Process the video and return a MediaStream
8
9
  */
@@ -16,4 +17,4 @@ export interface VideoProcessor {
16
17
  */
17
18
  destroy: () => Promise<void>;
18
19
  }
19
- export declare const createVideoProcessor: (transformers: Array<Transform<InputFrame, InputFrame>>, processTrack: ProcessVideoTrack) => VideoProcessor;
20
+ export declare const createVideoProcessor: (transformers: Transform<InputFrame, InputFrame>[], processTrack: ProcessVideoTrack) => VideoProcessor;
@@ -4,8 +4,8 @@ export const createVideoProcessor = (transformers, processTrack) => {
4
4
  const props = {
5
5
  processing: false,
6
6
  };
7
- const open = async () => {
8
- await Promise.all(transformers.map(transformer => transformer.init()));
7
+ const open = async (options) => {
8
+ await Promise.all(transformers.map(transformer => transformer.init(options)));
9
9
  };
10
10
  const process = async (source) => {
11
11
  const [track] = source.getVideoTracks();
@@ -34,7 +34,9 @@ export const createVideoProcessor = (transformers, processTrack) => {
34
34
  stopStreamTracks(props.outputStream);
35
35
  props.outputStream = undefined;
36
36
  props.processing = false;
37
- transformers.forEach(transformer => transformer.close());
37
+ for (const transformer of transformers) {
38
+ transformer.close();
39
+ }
38
40
  };
39
41
  const destroy = async () => {
40
42
  close();
@@ -3,7 +3,7 @@ type Track = MediaStreamVideoTrack | MediaStreamVideoTrackGenerator;
3
3
  interface Options {
4
4
  signal?: AbortSignal;
5
5
  }
6
- export type ProcessVideoTrack = (track: MediaStreamVideoTrack, transformers: Array<Transformer<InputFrame, InputFrame>>, options?: Options) => Promise<Track>;
6
+ export type ProcessVideoTrack = (track: MediaStreamVideoTrack, transformers: Transformer<InputFrame, InputFrame>[], options?: Options) => Promise<Track>;
7
7
  /**
8
8
  * Video track processor using MediaStreamTrackProcessor API
9
9
  */
@@ -18,9 +18,9 @@ export const createVideoTrackProcessor = () => (track, transformers, { signal }
18
18
  const processor = createMediaStreamTrackProcessor({ track });
19
19
  const trackGenerator = createMediaStreamTrackGenerator({ kind: 'video' });
20
20
  let readable = processor.readable;
21
- transformers.forEach(transformer => {
21
+ for (const transformer of transformers) {
22
22
  readable = readable.pipeThrough(createStreamTransformer(adaptInputFrameTransformer(transformer)), { signal });
23
- });
23
+ }
24
24
  // Promise returned from `ReadableStream['pipeTo']` is only resolved
25
25
  // when the streaming is finished
26
26
  readable