@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,9 +1,6 @@
1
1
  import { assert } from '@pexip/utils';
2
- import { PROCESS_STATUS, RENDERING_EVENTS } from '../../../common/constants';
3
- const cloneImageRecord = async (image) => {
4
- const cloned = await createImageBitmap(image.image);
5
- return { image: cloned, key: image.key };
6
- };
2
+ import { PROCESS_STATUS, RENDERING_EVENTS } from '../../common/constants';
3
+ import { cloneImageRecord } from '../../common/utils';
7
4
  export const createSegmenter = (
8
5
  /**
9
6
  * A base path to specify the directory the Wasm files should be loaded
@@ -12,7 +9,7 @@ export const createSegmenter = (
12
9
  * @see {@link
13
10
  * https://ai.google.dev/edge/api/mediapipe/js/tasks-vision.filesetresolver#filesetresolverforvisiontasks | FileResolver}
14
11
  */
15
- basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', import.meta.url), credentials = 'same-origin', delegate, log, ...options } = { modelAsset: { modelName: 'selfie', path: '' } }) => {
12
+ basePath = '/', { workerScriptUrl = new URL('../../workers/mediaWorker.js', import.meta.url), credentials = 'same-origin', delegate, log, stats, ...options } = { modelAsset: { modelName: 'selfie', path: '' } }) => {
16
13
  const props = {
17
14
  status: PROCESS_STATUS.New,
18
15
  modelAsset: options.modelAsset,
@@ -28,24 +25,35 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
28
25
  error.preventDefault();
29
26
  });
30
27
  mediaWorker.addEventListener('message', (event) => {
31
- const { type } = event.data;
32
- if (type === 'debug') {
33
- log?.(event.data.data.message, event.data.data.context);
34
- }
35
- if (type === 'event') {
36
- log?.(event.data.data.type, event.data.data.message);
37
- for (const cb of props.eventHandlers.get(event.data.data.type) ?? []) {
38
- cb();
28
+ switch (event.data.type) {
29
+ case 'debug': {
30
+ log?.(event.data.data.message, event.data.data.context);
31
+ break;
39
32
  }
40
- switch (event.data.data.type) {
41
- case RENDERING_EVENTS.ProcessorRestarted:
42
- break;
43
- case RENDERING_EVENTS.ContextRestored:
44
- void handleContextRestored();
45
- break;
46
- default:
47
- props.status = PROCESS_STATUS.Error;
48
- break;
33
+ case 'event': {
34
+ log?.(event.data.data.type, event.data.data.message);
35
+ for (const cb of props.eventHandlers.get(event.data.data.type) ?? []) {
36
+ cb();
37
+ }
38
+ switch (event.data.data.type) {
39
+ case RENDERING_EVENTS.ProcessorRestarted:
40
+ break;
41
+ case RENDERING_EVENTS.ContextRestored:
42
+ void handleContextRestored();
43
+ break;
44
+ default:
45
+ props.status = PROCESS_STATUS.Error;
46
+ break;
47
+ }
48
+ break;
49
+ }
50
+ case 'stats': {
51
+ stats?.(event.data.data);
52
+ break;
53
+ }
54
+ default: {
55
+ // Do nothing
56
+ break;
49
57
  }
50
58
  }
51
59
  });
@@ -72,7 +80,8 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
72
80
  };
73
81
  async function postMsg(expectedResponseType, message, transfer) {
74
82
  if (!expectedResponseType) {
75
- return props.mediaWorker?.postMessage(message, transfer ?? []);
83
+ props.mediaWorker?.postMessage(message, transfer ?? []);
84
+ return;
76
85
  }
77
86
  return await new Promise((resolve, reject) => {
78
87
  const handleMessage = (ev) => {
@@ -83,8 +92,10 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
83
92
  resolve(ev.data.data);
84
93
  break;
85
94
  }
86
- case 'debug': {
87
- // Nothing to handle
95
+ case 'debug':
96
+ case 'stats': {
97
+ // Nothing to handle -- these are continues
98
+ // messages handled by the global event listener
88
99
  break;
89
100
  }
90
101
  case 'event': {
@@ -113,17 +124,16 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
113
124
  props.mediaWorker?.postMessage(message, transfer ?? []);
114
125
  });
115
126
  }
116
- const open = async ({ processingWidth, processingHeight, output, imageSegmenterOptions, ...restOptions }) => {
127
+ const open = async ({ imageSegmenterOptions, ...restOptions }) => {
117
128
  props.status = PROCESS_STATUS.Opening;
118
- props.mediaWorker = initWorker();
119
- props.output = output;
129
+ if (!props.mediaWorker) {
130
+ props.mediaWorker = initWorker();
131
+ }
120
132
  const backgroundImage = restOptions?.backgroundImage &&
121
133
  (await cloneImageRecord(restOptions.backgroundImage));
122
134
  try {
123
135
  const processorSettings = {
124
136
  basePath,
125
- processingWidth,
126
- processingHeight,
127
137
  ...restOptions,
128
138
  imageSegmenterOptions: {
129
139
  ...options,
@@ -133,43 +143,66 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
133
143
  backgroundImage,
134
144
  };
135
145
  log?.('Init processor with config', { config: processorSettings });
136
- await postMsg('opened', {
146
+ const result = await postMsg('opened', {
137
147
  type: 'open',
138
148
  data: processorSettings,
139
- }, [output, backgroundImage?.image].filter(Boolean));
149
+ }, backgroundImage?.image ? [backgroundImage.image] : []);
140
150
  props.status = PROCESS_STATUS.Opened;
151
+ return result;
141
152
  }
142
153
  catch (error) {
143
154
  props.status = PROCESS_STATUS.Error;
144
155
  log?.('Failed to open process', {
145
156
  error: error,
146
157
  });
158
+ return {};
147
159
  }
148
160
  };
149
- const process = async (input, options) => {
161
+ async function process(input, options) {
150
162
  if (props.status === PROCESS_STATUS.Error ||
151
163
  props.status === PROCESS_STATUS.Updating) {
152
164
  return input;
153
165
  }
154
166
  props.status = PROCESS_STATUS.Processing;
155
- try {
156
- const resultFrame = await postMsg('processed', {
157
- type: 'process',
158
- data: { videoFrame: input, renderOptions: options },
159
- }, [input.frame]);
160
- if (props.status === PROCESS_STATUS.Processing) {
167
+ if (input instanceof MediaStreamTrack) {
168
+ try {
169
+ // Clone the track and keep the original in main
170
+ const clonedTrack = input.clone();
171
+ const output = await postMsg('processed', {
172
+ type: 'process',
173
+ data: { track: clonedTrack, renderOptions: options },
174
+ }, [clonedTrack]);
175
+ assert(output);
161
176
  props.status = PROCESS_STATUS.Idle;
177
+ return output;
178
+ }
179
+ catch (error) {
180
+ props.status = PROCESS_STATUS.Error;
181
+ log?.('Error during the processing, return original track instead.', { error });
182
+ return input;
162
183
  }
163
- return resultFrame;
164
184
  }
165
- catch (error) {
166
- props.status = PROCESS_STATUS.Error;
167
- log?.('Error during the processing, return original instead.', {
168
- error,
169
- });
170
- return input;
185
+ else {
186
+ try {
187
+ await postMsg('processed', {
188
+ type: 'process',
189
+ data: {
190
+ readable: input.readable,
191
+ writable: input.writable,
192
+ renderOptions: options,
193
+ },
194
+ }, [input.readable, input.writable]);
195
+ props.status = PROCESS_STATUS.Idle;
196
+ }
197
+ catch (error) {
198
+ props.status = PROCESS_STATUS.Error;
199
+ log?.('Error during the processing, return original ReadWritableStream instead.', {
200
+ error,
201
+ });
202
+ return input;
203
+ }
171
204
  }
172
- };
205
+ }
173
206
  const update = async (options) => {
174
207
  try {
175
208
  if (props.status === PROCESS_STATUS.Processing ||
@@ -183,7 +216,8 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
183
216
  props.mediaWorker?.removeEventListener('message', handleMessage);
184
217
  resolve(undefined);
185
218
  }
186
- else {
219
+ else if (ev.data.type !== 'debug' &&
220
+ ev.data.type !== 'stats') {
187
221
  reject(new Error(`UnhandledEvent: ${ev.data.type}`));
188
222
  }
189
223
  };
@@ -196,7 +230,7 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
196
230
  (await cloneImageRecord(options.backgroundImage));
197
231
  log?.('Update processor config', { config: options });
198
232
  const transfer = backgroundImage ? [backgroundImage.image] : [];
199
- await postMsg('updated', {
233
+ const changed = await postMsg('updated', {
200
234
  type: 'update',
201
235
  data: {
202
236
  ...options,
@@ -204,25 +238,30 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
204
238
  },
205
239
  }, transfer);
206
240
  props.status = PROCESS_STATUS.Updated;
241
+ return changed;
207
242
  }
208
243
  catch (error) {
209
244
  props.status = PROCESS_STATUS.Error;
210
245
  log?.('Failed to update process', {
211
246
  error: error,
212
247
  });
248
+ return {};
213
249
  }
214
250
  };
251
+ const mute = (muted) => {
252
+ postMsg(undefined, { type: 'mute', data: muted });
253
+ };
215
254
  const close = () => {
216
255
  props.status = PROCESS_STATUS.Closing;
217
256
  log?.('Close processor');
218
- void postMsg(undefined, { type: 'close' });
219
- props.eventHandlers.clear();
257
+ postMsg(undefined, { type: 'close' });
220
258
  props.status = PROCESS_STATUS.Closed;
221
259
  };
222
260
  const destroy = async () => {
223
261
  props.status = PROCESS_STATUS.Destroying;
224
262
  log?.('Destroy processor');
225
- await postMsg(undefined, { type: 'destroy' });
263
+ props.eventHandlers.clear();
264
+ postMsg(undefined, { type: 'destroy' });
226
265
  props.mediaWorker?.terminate();
227
266
  props.mediaWorker = undefined;
228
267
  props.status = PROCESS_STATUS.Destroyed;
@@ -251,6 +290,7 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
251
290
  open,
252
291
  update,
253
292
  process,
293
+ mute,
254
294
  close,
255
295
  destroy,
256
296
  };
@@ -1,2 +1,4 @@
1
1
  import { type RenderEffects } from '../../common/constants';
2
2
  export declare const isRenderEffects: (t: unknown) => t is RenderEffects;
3
+ export declare const isMediaStreamVideoTrack: (t: MediaStreamTrack) => t is MediaStreamVideoTrack;
4
+ export declare function assertMediaStreamVideoTrack(t: MediaStreamTrack): asserts t is MediaStreamVideoTrack;
@@ -1,7 +1,14 @@
1
1
  import { RENDER_EFFECT } from '../../common/constants';
2
+ const EFFECTS = Object.values(RENDER_EFFECT);
2
3
  export const isRenderEffects = (t) => {
3
4
  if (typeof t !== 'string') {
4
5
  return false;
5
6
  }
6
- return Object.values(RENDER_EFFECT).some(effect => effect === t);
7
+ return EFFECTS.some(effect => effect === t);
7
8
  };
9
+ export const isMediaStreamVideoTrack = (t) => t.kind === 'video';
10
+ export function assertMediaStreamVideoTrack(t) {
11
+ if (!isMediaStreamVideoTrack(t)) {
12
+ throw new TypeError('Not a video track', { cause: t });
13
+ }
14
+ }
@@ -1,9 +1,8 @@
1
1
  import type { ProcessStatus, RenderingEvents } from '../../common/constants';
2
- import type { ProcessInputType, VideoFrameLike } from '../../common/types/media';
2
+ import type { ReadWritableStream } from '../../common/types/media';
3
3
  import type { RendererOptions } from '../../common/types/render';
4
4
  import type { SegmentationModelAsset } from '../../common/types/segmentation';
5
5
  import type { ProcessorOptions } from '../../common/types/processor';
6
- import type { Transform } from '../types';
7
6
  export type Color = {
8
7
  r: number;
9
8
  g: number;
@@ -16,9 +15,9 @@ export interface AsyncAssets {
16
15
  tfjsBackendLoaded: boolean;
17
16
  glueLoaded: boolean;
18
17
  }
19
- export interface Process {
18
+ export interface Process<T> {
20
19
  status: ProcessStatus;
21
- open(): Promise<void>;
20
+ open(): Promise<T>;
22
21
  close(): void;
23
22
  destroy(): Promise<void>;
24
23
  }
@@ -28,21 +27,42 @@ export interface BackgroundImage {
28
27
  }
29
28
  type Unsubscribe = () => void;
30
29
  type Callback<T> = (args: T) => void;
31
- export interface Segmenter extends Process {
30
+ export interface Segmenter extends Process<Partial<RendererOptions>> {
32
31
  modelAsset: SegmentationModelAsset;
33
32
  backgroundImage?: BackgroundImage;
34
- open(options?: ProcessorOptions): Promise<void>;
35
- update(options: ProcessorOptions): Promise<void>;
36
- process(input: VideoFrameLike, options?: RendererOptions): Promise<VideoFrameLike | undefined>;
33
+ /**
34
+ * Initialize the Segmenter as well as the underlying worker.
35
+ * It is expected to be called once.
36
+ */
37
+ open(options?: ProcessorOptions): Promise<Partial<RendererOptions>>;
38
+ /**
39
+ * Update the processor options
40
+ */
41
+ update(options: ProcessorOptions): Promise<Partial<RendererOptions>>;
42
+ /**
43
+ * Mute/unmute the track in the worker
44
+ */
45
+ mute(muted: boolean): void;
46
+ /**
47
+ * Process provided input
48
+ */
49
+ process(input: MediaStreamTrack, options?: RendererOptions): Promise<MediaStreamTrack>;
50
+ process(input: ReadWritableStream<VideoFrame>, options?: RendererOptions): Promise<ReadWritableStream<VideoFrame> | undefined>;
51
+ process(input: MediaStreamTrack | ReadWritableStream<VideoFrame>, options?: RendererOptions): Promise<MediaStreamTrack | ReadWritableStream<VideoFrame> | undefined>;
52
+ /**
53
+ * Subscribe rendering events, @see RenderingEvents
54
+ */
37
55
  subscribe(event: RenderingEvents, callback: Callback<void>): Unsubscribe;
38
56
  }
39
- export interface Detector<T> extends Process {
40
- detect(input: ProcessInputType): Promise<T>;
57
+ export interface InputProcessAdaptor {
58
+ /**
59
+ * Adapt the provided input track based on browser capabilities.
60
+ */
61
+ adapt(track: MediaStreamTrack): MediaStreamTrack | ReadWritableStream<VideoFrame>;
62
+ /**
63
+ * The generated track from the `adapt` function, and only available
64
+ * after calling the `adapt` method otherwise `undefined`
65
+ */
66
+ outputTrack?: MediaStreamTrack;
41
67
  }
42
- export interface SegmentationParams extends RendererOptions {
43
- segmenter: Segmenter;
44
- width: number;
45
- height: number;
46
- }
47
- export type SegmentationTransform = Transform<InputFrame, InputFrame> & SegmentationParams & Pick<Segmenter, 'update'> & Omit<Process, 'open'>;
48
68
  export {};
@@ -58,13 +58,14 @@ export declare const toVideoElement: (input: MediaStream, width: number, height:
58
58
  export declare const playVideo: (video: HTMLVideoElement, { setTimeout, clearTimeout }?: {
59
59
  setTimeout?: (((handler: TimerHandler, timeout?: number, ...arguments: any[]) => number) & typeof globalThis.setTimeout) | undefined;
60
60
  clearTimeout?: (((id: number | undefined) => void) & typeof globalThis.clearTimeout) | undefined;
61
- }) => Promise<void>;
61
+ }) => Promise<[void, void]> | undefined;
62
62
  /**
63
63
  * Load image with provided image element
64
64
  */
65
65
  export declare const loadImage: (image: HTMLImageElement, src: string) => Promise<void>;
66
66
  export declare const toNumber: (value: number | SVGAnimatedLength) => number;
67
- export declare const getCanvasRenderingContext2D: (canvas: Canvas, options?: CanvasRenderingContext2DSettings) => CanvasRenderingContext2D;
67
+ export declare function getCanvasRenderingContext2D(canvas: HTMLCanvasElement, options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
68
+ export declare function getCanvasRenderingContext2D(canvas: OffscreenCanvas, options?: CanvasRenderingContext2DSettings): OffscreenCanvasRenderingContext2D;
68
69
  export declare const hasVideoFrameSupport: () => boolean;
69
70
  export declare const isVideoFrame: (t: unknown) => t is VideoFrame;
70
71
  export declare const flipCanvasHorizontal: (canvas: Canvas) => void;
@@ -89,8 +90,8 @@ interface FrameCallbackRequestOptions {
89
90
  * @param frameRate - A fallback frame rate when we are not able to get the rate
90
91
  * from API
91
92
  */
92
- export declare const createFrameCallbackRequest: (callback: Callback<Promise<void>, [ProcessInputType]>, frameRate: number, { subscribeVisibilityChange, }?: FrameCallbackRequestOptions) => {
93
- start: (input: ProcessInputType) => Promise<void>;
93
+ export declare const createFrameCallbackRequest: <T>(callback: Callback<Promise<void>, [T]>, frameRate: number, { subscribeVisibilityChange, }?: FrameCallbackRequestOptions) => {
94
+ start: (input: T) => Promise<void>;
94
95
  stop: () => void;
95
96
  frameRate: number;
96
97
  };
@@ -126,52 +126,57 @@ var HTMLMediaElementReadyState;
126
126
  HTMLMediaElementReadyState[HTMLMediaElementReadyState["HaveFutureData"] = 3] = "HaveFutureData";
127
127
  HTMLMediaElementReadyState[HTMLMediaElementReadyState["HaveEnoughData"] = 4] = "HaveEnoughData";
128
128
  })(HTMLMediaElementReadyState || (HTMLMediaElementReadyState = {}));
129
- export const playVideo = async (video, { setTimeout = window.setTimeout, clearTimeout = window.clearTimeout } = {}) => {
129
+ export const playVideo = (video, { setTimeout = window.setTimeout, clearTimeout = window.clearTimeout } = {}) => {
130
130
  if (video.autoplay) {
131
131
  return;
132
132
  }
133
- if (video.readyState <
134
- HTMLMediaElementReadyState.HaveCurrentData) {
135
- // Not enough data, wait for the data and then play
136
- await new Promise((resolve, reject) => {
137
- const clearTimeoutTimer = () => {
138
- clearTimeout(timeoutId);
139
- timeoutId = 0;
140
- };
141
- const unsubscribe = () => {
142
- video.removeEventListener('loadeddata', waitForEvent);
143
- video.removeEventListener('error', handleError);
144
- video.removeEventListener('stalled', handleStalled);
145
- video.removeEventListener('ended', handleEnded);
146
- clearTimeoutTimer();
147
- };
148
- const handleEnded = () => {
149
- unsubscribe();
150
- reject(new Error('VideoEnded', { cause: video }));
151
- };
152
- const handleStalled = () => {
153
- unsubscribe();
154
- reject(new Error('VideoStalled', { cause: video }));
155
- };
156
- const handleError = (error) => {
157
- unsubscribe();
158
- reject(error.error);
159
- };
160
- const waitForEvent = () => {
161
- unsubscribe();
133
+ return Promise.all([
134
+ video.play(),
135
+ new Promise((resolve, reject) => {
136
+ if (video.readyState <
137
+ HTMLMediaElementReadyState.HaveCurrentData) {
138
+ // Not enough data, wait for the data and then play
139
+ const clearTimeoutTimer = () => {
140
+ clearTimeout(timeoutId);
141
+ timeoutId = 0;
142
+ };
143
+ const unsubscribe = () => {
144
+ video.removeEventListener('loadeddata', waitForEvent);
145
+ video.removeEventListener('error', handleError);
146
+ video.removeEventListener('stalled', handleStalled);
147
+ video.removeEventListener('ended', handleEnded);
148
+ clearTimeoutTimer();
149
+ };
150
+ const handleEnded = () => {
151
+ unsubscribe();
152
+ reject(new Error('VideoEnded', { cause: video }));
153
+ };
154
+ const handleStalled = () => {
155
+ unsubscribe();
156
+ reject(new Error('VideoStalled', { cause: video }));
157
+ };
158
+ const handleError = (error) => {
159
+ unsubscribe();
160
+ reject(error.error);
161
+ };
162
+ const waitForEvent = () => {
163
+ unsubscribe();
164
+ resolve();
165
+ };
166
+ // Use setTimeout to prevent a race to `loadeddata` event
167
+ video.addEventListener('loadeddata', waitForEvent);
168
+ video.addEventListener('error', handleError);
169
+ video.addEventListener('stalled', handleStalled);
170
+ video.addEventListener('ended', handleEnded);
171
+ let timeoutId = setTimeout(() => {
172
+ reject(new Error('PlayVideoTimeout', { cause: video }));
173
+ }, PLAY_VIDEO_TIMEOUT);
174
+ }
175
+ else {
162
176
  resolve();
163
- };
164
- // Use setTimeout to prevent a race to `loadeddata` event
165
- video.addEventListener('loadeddata', waitForEvent);
166
- video.addEventListener('error', handleError);
167
- video.addEventListener('stalled', handleStalled);
168
- video.addEventListener('ended', handleEnded);
169
- let timeoutId = setTimeout(() => {
170
- reject(new Error('PlayVideoTimeout', { cause: video }));
171
- }, PLAY_VIDEO_TIMEOUT);
172
- });
173
- }
174
- return await video.play();
177
+ }
178
+ }),
179
+ ]);
175
180
  };
176
181
  /**
177
182
  * Load image with provided image element
@@ -184,13 +189,13 @@ export const loadImage = async (image, src) => {
184
189
  });
185
190
  };
186
191
  export const toNumber = (value) => value instanceof SVGAnimatedLength ? value.baseVal.value : value;
187
- export const getCanvasRenderingContext2D = (canvas, options) => {
192
+ export function getCanvasRenderingContext2D(canvas, options) {
188
193
  const context = canvas.getContext('2d', options);
189
194
  if (!context) {
190
195
  throw new Error('Cannot get CanvasRenderingContext2D');
191
196
  }
192
197
  return context;
193
- };
198
+ }
194
199
  export const hasVideoFrameSupport = () => 'VideoFrame' in window;
195
200
  export const isVideoFrame = (t) => {
196
201
  if (hasVideoFrameSupport() && t instanceof VideoFrame) {
@@ -1,13 +1,23 @@
1
- import type { Transform } from '../types';
2
- import type { InputFrame } from './types';
3
- import type { ProcessVideoTrack } from './videoStreamTrackProcessor';
4
- import type { ProcessorOptions } from '../../common';
1
+ import type { ProcessorOptions, ProcessStatus, RendererOptions } from '../../common';
2
+ import type { Segmenter, InputProcessAdaptor } from './types';
3
+ export interface ProcessorUpdateOptions extends Omit<ProcessorOptions, 'renderOptions'> {
4
+ renderOptions?: Partial<RendererOptions>;
5
+ }
5
6
  export interface VideoProcessor {
6
- open(options?: ProcessorOptions): Promise<void>;
7
+ readonly status: ProcessStatus;
8
+ /**
9
+ * Initialize the video processor and related
10
+ */
11
+ open(options?: ProcessorUpdateOptions): Promise<void>;
12
+ /**
13
+ * Update the video processor with provided options
14
+ */
15
+ update(options: ProcessorUpdateOptions): Promise<void>;
16
+ mute(muted: boolean): void;
7
17
  /**
8
18
  * Process the video and return a MediaStream
9
19
  */
10
- process: (source: MediaStream) => Promise<MediaStream>;
20
+ process: (source: MediaStreamTrack) => Promise<MediaStreamTrack>;
11
21
  /**
12
22
  * Stop the process of video segmentation
13
23
  */
@@ -16,5 +26,17 @@ export interface VideoProcessor {
16
26
  * Destroy the segmentation wasm instance
17
27
  */
18
28
  destroy: () => Promise<void>;
29
+ /**
30
+ * Current renderer options
31
+ */
32
+ readonly renderOptions: RendererOptions;
33
+ /**
34
+ * Current processing width
35
+ */
36
+ readonly processingWidth: number;
37
+ /**
38
+ * Current processing height
39
+ */
40
+ readonly processingHeight: number;
19
41
  }
20
- export declare const createVideoProcessor: (transformers: Transform<InputFrame, InputFrame>[], processTrack: ProcessVideoTrack) => VideoProcessor;
42
+ export declare const createVideoProcessor: (segmenter: Segmenter, inputProcessAdaptor: InputProcessAdaptor) => VideoProcessor;