@pexip/media-processor 21.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 (170) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +17 -10
  3. package/api-docs/README.mdx +40 -19
  4. package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
  5. package/api-docs/functions/avg.mdx +1 -1
  6. package/api-docs/functions/createAnalyzerGraphNode.mdx +3 -1
  7. package/api-docs/functions/createAnalyzerSubscribableGraphNode.mdx +3 -1
  8. package/api-docs/functions/createAsyncCallbackLoop.mdx +6 -6
  9. package/api-docs/functions/createAudioDestinationGraphNode.mdx +1 -1
  10. package/api-docs/functions/createAudioSignalDetector.mdx +4 -4
  11. package/api-docs/functions/createAudioStats.mdx +1 -1
  12. package/api-docs/functions/createChannelMergerGraphNode.mdx +1 -1
  13. package/api-docs/functions/createChannelSplitterGraphNode.mdx +1 -1
  14. package/api-docs/functions/createDelayGraphNode.mdx +1 -1
  15. package/api-docs/functions/createDenoiseWorkletGraphNode.mdx +2 -2
  16. package/api-docs/functions/createFrameCallbackRequest.mdx +10 -4
  17. package/api-docs/functions/createGainGraphNode.mdx +3 -1
  18. package/api-docs/functions/createMediaElementSourceGraphNode.mdx +1 -1
  19. package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
  20. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
  21. package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
  22. package/api-docs/functions/createSegmenter.mdx +1 -1
  23. package/api-docs/functions/createStreamDestinationGraphNode.mdx +1 -1
  24. package/api-docs/functions/createStreamSourceGraphNode.mdx +1 -1
  25. package/api-docs/functions/createVADetector.mdx +3 -3
  26. package/api-docs/functions/createVideoProcessor.mdx +3 -3
  27. package/api-docs/functions/createVoiceDetectorFromProbability.mdx +1 -1
  28. package/api-docs/functions/createVoiceDetectorFromTimeData.mdx +3 -1
  29. package/api-docs/functions/createWindowedStats.mdx +2 -2
  30. package/api-docs/functions/getBezierCurveControlPoints.mdx +3 -1
  31. package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
  32. package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
  33. package/api-docs/functions/labelComponents.mdx +3 -1
  34. package/api-docs/functions/loadScript.mdx +1 -1
  35. package/api-docs/functions/loadWasms.mdx +1 -1
  36. package/api-docs/functions/nearestPowerOfTwo.mdx +1 -1
  37. package/api-docs/functions/playVideo.mdx +7 -5
  38. package/api-docs/functions/processAverageVolume.mdx +1 -1
  39. package/api-docs/functions/rms.mdx +1 -1
  40. package/api-docs/functions/subscribeWorkletNode.mdx +1 -1
  41. package/api-docs/functions/sum.mdx +1 -1
  42. package/api-docs/interfaces/Analyzer.mdx +4 -0
  43. package/api-docs/interfaces/AnalyzerSubscribableOptions.mdx +5 -3
  44. package/api-docs/interfaces/AudioGraph.mdx +2 -2
  45. package/api-docs/interfaces/AudioNodeInit.mdx +10 -6
  46. package/api-docs/interfaces/AudioNodeProps.mdx +3 -3
  47. package/api-docs/interfaces/Benchmark.mdx +17 -3
  48. package/api-docs/interfaces/Clock.mdx +1 -1
  49. package/api-docs/interfaces/Denoise.mdx +2 -2
  50. package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
  51. package/api-docs/interfaces/Gain.mdx +4 -0
  52. package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
  53. package/api-docs/interfaces/InputProcessAdaptor.mdx +32 -0
  54. package/api-docs/interfaces/MPMask.mdx +1 -1
  55. package/api-docs/interfaces/Process.mdx +12 -7
  56. package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
  57. package/api-docs/interfaces/ProcessWorkerDebugMessage.mdx +1 -1
  58. package/api-docs/interfaces/ProcessWorkerEventClosed.mdx +5 -0
  59. package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
  60. package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
  61. package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
  62. package/api-docs/interfaces/ProcessorEvent.mdx +3 -2
  63. package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
  64. package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
  65. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +17 -0
  66. package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
  67. package/api-docs/interfaces/RenderEventHandlers.mdx +3 -3
  68. package/api-docs/interfaces/Renderer.mdx +5 -3
  69. package/api-docs/interfaces/RendererOptions.mdx +5 -9
  70. package/api-docs/interfaces/Runner.mdx +3 -3
  71. package/api-docs/interfaces/Segmentation.mdx +1 -1
  72. package/api-docs/interfaces/Segmenter.mdx +115 -16
  73. package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
  74. package/api-docs/interfaces/VideoFrameLike.mdx +1 -1
  75. package/api-docs/interfaces/VideoProcessor.mdx +52 -8
  76. package/api-docs/interfaces/WorkletMessagePortOptions.mdx +2 -2
  77. package/api-docs/type-aliases/AsyncCallback.mdx +1 -1
  78. package/api-docs/type-aliases/Callback.mdx +2 -2
  79. package/api-docs/type-aliases/ExtractMessageEventType.mdx +1 -1
  80. package/api-docs/type-aliases/IncludeMessageEventDataType.mdx +1 -1
  81. package/api-docs/type-aliases/Logger.mdx +12 -0
  82. package/api-docs/type-aliases/OptionalKeys.mdx +1 -1
  83. package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
  84. package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
  85. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +7 -4
  86. package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
  87. package/api-docs/type-aliases/RunnerCreator.mdx +3 -3
  88. package/api-docs/type-aliases/Struct.mdx +9 -0
  89. package/api-docs/type-aliases/TupleOf.mdx +1 -1
  90. package/api-docs/variables/FRAME_RATE.mdx +1 -1
  91. package/api-docs/variables/ImageFetchError.mdx +3 -0
  92. package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
  93. package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
  94. package/api-docs/variables/cloneImageRecord.mdx +21 -0
  95. package/api-docs/variables/compareImageRecords.mdx +18 -0
  96. package/api-docs/variables/createLazyProps.mdx +1 -1
  97. package/api-docs/variables/createObjectUpdater.mdx +2 -2
  98. package/api-docs/variables/createRemoteImageBitmap.mdx +5 -5
  99. package/api-docs/variables/handleWebGLContextLoss.mdx +1 -1
  100. package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
  101. package/api-docs/variables/isValidGpuAPI.mdx +13 -0
  102. package/api-docs/variables/urls.mdx +2 -2
  103. package/dist/common/backends/webgl/renderer.js +2 -12
  104. package/dist/common/backends/webgl/texture.d.ts +1 -2
  105. package/dist/common/backends/webgl/texture.js +2 -2
  106. package/dist/common/backends/webgl/webglUtils.js +8 -4
  107. package/dist/common/backends/webgpu/renderer.js +2 -12
  108. package/dist/common/backends/webgpu/tentBlur.js +1 -1
  109. package/dist/common/backends/webgpu/texture.d.ts +1 -2
  110. package/dist/common/backends/webgpu/texture.js +6 -2
  111. package/dist/common/constants.d.ts +3 -0
  112. package/dist/common/constants.js +4 -0
  113. package/dist/common/index.d.ts +1 -0
  114. package/dist/common/index.js +1 -0
  115. package/dist/common/tsconfig.tsbuildinfo +1 -1
  116. package/dist/common/typeGuards.d.ts +2 -0
  117. package/dist/common/typeGuards.js +7 -0
  118. package/dist/common/types/media.d.ts +5 -1
  119. package/dist/common/types/messageEvents.d.ts +34 -4
  120. package/dist/common/types/processor.d.ts +7 -4
  121. package/dist/common/types/render.d.ts +5 -2
  122. package/dist/common/types/render.js +23 -1
  123. package/dist/common/types/segmentation.d.ts +0 -12
  124. package/dist/common/utils.d.ts +13 -2
  125. package/dist/common/utils.js +54 -10
  126. package/dist/main/audio.js +3 -3
  127. package/dist/main/benchUtils.d.ts +2 -2
  128. package/dist/main/benchUtils.js +4 -4
  129. package/dist/main/index.d.ts +1 -1
  130. package/dist/main/index.js +1 -1
  131. package/dist/main/tsconfig.tsbuildinfo +1 -1
  132. package/dist/main/types.d.ts +0 -6
  133. package/dist/main/utils.d.ts +4 -0
  134. package/dist/main/utils.js +31 -0
  135. package/dist/main/video/constants.d.ts +1 -4
  136. package/dist/main/video/constants.js +1 -5
  137. package/dist/main/video/index.d.ts +2 -3
  138. package/dist/main/video/index.js +2 -3
  139. package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
  140. package/dist/main/video/inputProcessAdaptor.js +198 -0
  141. package/dist/main/video/segmenter.d.ts +42 -0
  142. package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +130 -75
  143. package/dist/main/video/typeGuards.d.ts +2 -0
  144. package/dist/main/video/typeGuards.js +8 -1
  145. package/dist/main/video/types.d.ts +37 -17
  146. package/dist/main/video/utils.d.ts +5 -4
  147. package/dist/main/video/utils.js +49 -44
  148. package/dist/main/video/video.d.ts +30 -8
  149. package/dist/main/video/video.js +338 -32
  150. package/dist/workers/mediaWorker.js +24 -24
  151. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  152. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  153. package/package.json +8 -8
  154. package/api-docs/functions/createCanvasTransform.mdx +0 -14
  155. package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
  156. package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
  157. package/api-docs/interfaces/Detector.mdx +0 -81
  158. package/api-docs/interfaces/SegmentationParams.mdx +0 -35
  159. package/api-docs/interfaces/Transform.mdx +0 -62
  160. package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
  161. package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
  162. package/dist/main/transformer.d.ts +0 -1
  163. package/dist/main/transformer.js +0 -4
  164. package/dist/main/video/canvasTransform.d.ts +0 -17
  165. package/dist/main/video/canvasTransform.js +0 -341
  166. package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
  167. package/dist/main/video/transformer.d.ts +0 -9
  168. package/dist/main/video/transformer.js +0 -68
  169. package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
  170. package/dist/main/video/videoStreamTrackProcessor.js +0 -86
@@ -0,0 +1,198 @@
1
+ import { assert } from '@pexip/utils';
2
+ import { createMediaStreamTrackProcessor } from '../processor';
3
+ import { createMediaStreamTrackGenerator } from '../generator';
4
+ import { playVideo, getCanvasRenderingContext2D } from './utils';
5
+ import { MediaStreamTrackTransferable } from '../utils';
6
+ import { FRAME_RATE } from './constants';
7
+ /**
8
+ * An implementation of `InputProcessAdaptor` which uses `MediaStreamTrackGenerator` and
9
+ * `MediaStreamTrackProcessor` APIs to create `ReadableStream` & `WritableStream`
10
+ */
11
+ export const createReadWritableStreamInputProcessAdaptor = () => {
12
+ let outputTrack;
13
+ return {
14
+ adapt(track) {
15
+ assert(track.kind === 'video');
16
+ const processor = createMediaStreamTrackProcessor({
17
+ track: track,
18
+ });
19
+ outputTrack = createMediaStreamTrackGenerator({
20
+ kind: 'video',
21
+ });
22
+ return {
23
+ readable: processor.readable,
24
+ writable: outputTrack.writable,
25
+ };
26
+ },
27
+ get outputTrack() {
28
+ return outputTrack;
29
+ },
30
+ };
31
+ };
32
+ /**
33
+ * Wait for the next frame using the best available API based on
34
+ * page visibility:
35
+ *
36
+ * - Visible + rVFC supported → `requestVideoFrameCallback`
37
+ * - Visible + no rVFC → `requestAnimationFrame`
38
+ * - Hidden → `setTimeout` at the given frame rate
39
+ */
40
+ const waitForNextFrame = (video, msPerFrame) => {
41
+ if (document.hidden) {
42
+ return new Promise(r => setTimeout(r, msPerFrame));
43
+ }
44
+ if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
45
+ return new Promise(r => video.requestVideoFrameCallback(() => r()));
46
+ }
47
+ return new Promise(r => requestAnimationFrame(() => r()));
48
+ };
49
+ /**
50
+ * An implementation of `InputProcessAdaptor` which uses `HTMLCanvasElement` or
51
+ * `OffscreenCanvas` API to create `ReadableStream` & `WritableStream`
52
+ */
53
+ export const createReadWritableStreamFallbackInputProcessAdaptor = ({ frameRate, }) => {
54
+ let outputTrack;
55
+ return {
56
+ adapt(track) {
57
+ assert(track.kind === 'video');
58
+ const video = document.createElement('video');
59
+ let readableCanvas;
60
+ let readableCtx;
61
+ let timestamp = 0;
62
+ const fps = track.getSettings().frameRate || frameRate;
63
+ const timeMsPerFrame = 1000 / fps;
64
+ const releaseElements = () => {
65
+ video.width = 0;
66
+ video.height = 0;
67
+ if (readableCanvas) {
68
+ readableCanvas.width = 0;
69
+ readableCanvas.height = 0;
70
+ }
71
+ };
72
+ const readable = new ReadableStream({
73
+ async start(controller) {
74
+ const cleanup = () => {
75
+ controller.close();
76
+ releaseElements();
77
+ };
78
+ track.addEventListener('ended', cleanup, {
79
+ once: true,
80
+ });
81
+ video.srcObject = new MediaStream([track]);
82
+ video.muted = true;
83
+ video.playsInline = true;
84
+ await playVideo(video);
85
+ readableCanvas = new OffscreenCanvas(video.videoWidth, video.videoHeight);
86
+ readableCtx = getCanvasRenderingContext2D(readableCanvas, {
87
+ alpha: false,
88
+ });
89
+ timestamp = performance.now();
90
+ },
91
+ cancel() {
92
+ releaseElements();
93
+ },
94
+ async pull(controller) {
95
+ if (track.readyState === 'ended') {
96
+ releaseElements();
97
+ return controller.close();
98
+ }
99
+ // Wait a bit if too fast, otherwise go ahead for the next frame
100
+ const diffMs = timeMsPerFrame - (performance.now() - timestamp);
101
+ if (diffMs) {
102
+ await waitForNextFrame(video, diffMs);
103
+ // @ts-expect-error --- sth is wrong with type narrowing
104
+ if (track.readyState === 'ended') {
105
+ releaseElements();
106
+ return controller.close();
107
+ }
108
+ }
109
+ if (readableCanvas.width !== video.videoWidth) {
110
+ readableCanvas.width = video.videoWidth;
111
+ }
112
+ if (readableCanvas.height !== video.videoHeight) {
113
+ readableCanvas.height = video.videoHeight;
114
+ }
115
+ timestamp = performance.now();
116
+ readableCtx.drawImage(video, 0, 0);
117
+ controller.enqueue(new VideoFrame(readableCanvas, {
118
+ // VideoFrame timestamp is in microseconds
119
+ // https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame/timestamp
120
+ timestamp: timestamp * 1000,
121
+ }));
122
+ },
123
+ });
124
+ // captureStream API is only available in HTMLCanvasElement
125
+ const writableCanvas = document.createElement('canvas');
126
+ const writableCtx = getCanvasRenderingContext2D(writableCanvas, {
127
+ alpha: false,
128
+ desynchronized: true,
129
+ });
130
+ const [capturedVideoTrack] = writableCanvas
131
+ .captureStream(frameRate)
132
+ .getVideoTracks();
133
+ outputTrack = capturedVideoTrack;
134
+ const release = () => {
135
+ capturedVideoTrack?.stop();
136
+ writableCanvas.width = 0;
137
+ writableCanvas.height = 0;
138
+ };
139
+ const writable = new WritableStream({
140
+ abort() {
141
+ release();
142
+ },
143
+ close() {
144
+ release();
145
+ },
146
+ write(frame) {
147
+ if (writableCanvas.width !== frame.displayWidth) {
148
+ writableCanvas.width = frame.displayWidth;
149
+ }
150
+ if (writableCanvas.height !== frame.displayHeight) {
151
+ writableCanvas.height = frame.displayHeight;
152
+ }
153
+ writableCtx.drawImage(frame, 0, 0, writableCanvas.width, writableCanvas.height);
154
+ frame.close();
155
+ },
156
+ });
157
+ return {
158
+ readable,
159
+ writable,
160
+ };
161
+ },
162
+ get outputTrack() {
163
+ return outputTrack;
164
+ },
165
+ };
166
+ };
167
+ /**
168
+ * An implementation of `InputProcessAdaptor` which adapts `MediaStreamTrack`
169
+ * directly as specified from the spec https://w3c.github.io/mediacapture-transform
170
+ */
171
+ export const createMediaStreamTrackInputProcessAdaptor = () => {
172
+ return {
173
+ adapt(track) {
174
+ return track;
175
+ },
176
+ };
177
+ };
178
+ /**
179
+ * Get the supported InputProcessAdaptor based on feature detection
180
+ * and preference provided
181
+ *
182
+ * @param tryNativeAPI - Whether or not to try to use the Native MediaStreamTrackProcessor APIs
183
+ * @param targetFrameRate - The target frame rate to use when fallback implementation of
184
+ * video frame extraction rate
185
+ */
186
+ export const getDefaultInputProcessAdaptor = (tryNativeAPI = true, targetFrameRate = FRAME_RATE) => {
187
+ if (tryNativeAPI) {
188
+ if (MediaStreamTrackTransferable) {
189
+ return createMediaStreamTrackInputProcessAdaptor();
190
+ }
191
+ else if ('MediaStreamTrackProcessor' in window) {
192
+ return createReadWritableStreamInputProcessAdaptor();
193
+ }
194
+ }
195
+ return createReadWritableStreamFallbackInputProcessAdaptor({
196
+ frameRate: targetFrameRate,
197
+ });
198
+ };
@@ -0,0 +1,42 @@
1
+ import type { Segmenter } from './types';
2
+ import type { Delegate, SegmenterOptions, ImageSegmenterOptions, SegmentationModelAsset } from '../../common/types/segmentation';
3
+ import type { FrameTransformStats } from '../../common/types/messageEvents';
4
+ type LogMethod = (meta: unknown, message?: string) => void;
5
+ export type Logger = {
6
+ debug: LogMethod;
7
+ info: LogMethod;
8
+ warn: LogMethod;
9
+ error: LogMethod;
10
+ };
11
+ interface Options extends Omit<ImageSegmenterOptions, 'delegate'> {
12
+ /**
13
+ * The URL for the worker script.
14
+ */
15
+ workerScriptUrl?: URL;
16
+ /**
17
+ * Request Credentials for the worker.
18
+ *
19
+ * @defaultValue 'same-origin'
20
+ */
21
+ credentials?: RequestCredentials;
22
+ /**
23
+ * @see `SegmentationModelAsset`
24
+ */
25
+ modelAsset: SegmentationModelAsset;
26
+ delegate?: () => Delegate;
27
+ log?: Logger;
28
+ /**
29
+ * Stats callback handler, e.g. benchmask
30
+ */
31
+ stats?: (stats: FrameTransformStats[]) => void;
32
+ }
33
+ export declare const createSegmenter: (
34
+ /**
35
+ * A base path to specify the directory the Wasm files should be loaded
36
+ * from. If not provided, it will be loaded from the host's root directory.
37
+ *
38
+ * @see {@link
39
+ * https://ai.google.dev/edge/api/mediapipe/js/tasks-vision.filesetresolver#filesetresolverforvisiontasks | FileResolver}
40
+ */
41
+ basePath?: SegmenterOptions["basePath"], { workerScriptUrl, credentials, delegate, log, stats, ...options }?: Options) => Segmenter;
42
+ export {};
@@ -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,
@@ -24,28 +21,53 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
24
21
  credentials,
25
22
  });
26
23
  mediaWorker.addEventListener('error', error => {
27
- log?.(`${error.message}`, error);
24
+ log?.error({ event: error, error: error.error }, `${error.message}`);
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?.debug(event.data.data.context, event.data.data.message);
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
+ 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
+ }
49
+ for (const cb of props.eventHandlers.get(event.data.data.type) ?? []) {
50
+ cb();
51
+ }
52
+ switch (event.data.data.type) {
53
+ case RENDERING_EVENTS.ProcessorRestarted:
54
+ break;
55
+ case RENDERING_EVENTS.ContextRestored:
56
+ void handleContextRestored();
57
+ break;
58
+ default:
59
+ props.status = PROCESS_STATUS.Error;
60
+ break;
61
+ }
62
+ break;
63
+ }
64
+ case 'stats': {
65
+ stats?.(event.data.data);
66
+ break;
67
+ }
68
+ default: {
69
+ // Do nothing
70
+ break;
49
71
  }
50
72
  }
51
73
  });
@@ -64,15 +86,14 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
64
86
  }
65
87
  }
66
88
  catch (err) {
67
- log?.('Failed to restart process', {
68
- error: err,
69
- });
89
+ log?.error({ error: err }, 'Failed to restart process');
70
90
  props.status = PROCESS_STATUS.Error;
71
91
  }
72
92
  };
73
93
  async function postMsg(expectedResponseType, message, transfer) {
74
94
  if (!expectedResponseType) {
75
- return props.mediaWorker?.postMessage(message, transfer ?? []);
95
+ props.mediaWorker?.postMessage(message, transfer ?? []);
96
+ return;
76
97
  }
77
98
  return await new Promise((resolve, reject) => {
78
99
  const handleMessage = (ev) => {
@@ -83,26 +104,34 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
83
104
  resolve(ev.data.data);
84
105
  break;
85
106
  }
86
- case 'debug': {
87
- // Nothing to handle
107
+ case 'debug':
108
+ case 'stats': {
109
+ // Nothing to handle -- these are continues
110
+ // messages handled by the global event listener
88
111
  break;
89
112
  }
90
113
  case 'event': {
91
114
  removeListener();
92
- reject(new Error(ev.data.data.type, {
93
- cause: ev.data.data.message,
94
- }));
115
+ reject(ev.data.data.error ??
116
+ new Error(ev.data.data.type, {
117
+ cause: ev.data.data.message,
118
+ }));
95
119
  break;
96
120
  }
97
121
  default: {
98
- removeListener();
99
- 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;
100
129
  }
101
130
  }
102
131
  };
103
132
  const handleError = (error) => {
104
133
  removeListener();
105
- reject(error.message);
134
+ reject(error.error ?? new Error(error.message));
106
135
  };
107
136
  const removeListener = () => {
108
137
  props.mediaWorker?.removeEventListener('message', handleMessage);
@@ -113,17 +142,16 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
113
142
  props.mediaWorker?.postMessage(message, transfer ?? []);
114
143
  });
115
144
  }
116
- const open = async ({ processingWidth, processingHeight, output, imageSegmenterOptions, ...restOptions }) => {
145
+ const open = async ({ imageSegmenterOptions, ...restOptions }) => {
117
146
  props.status = PROCESS_STATUS.Opening;
118
- props.mediaWorker = initWorker();
119
- props.output = output;
147
+ if (!props.mediaWorker) {
148
+ props.mediaWorker = initWorker();
149
+ }
120
150
  const backgroundImage = restOptions?.backgroundImage &&
121
151
  (await cloneImageRecord(restOptions.backgroundImage));
122
152
  try {
123
153
  const processorSettings = {
124
154
  basePath,
125
- processingWidth,
126
- processingHeight,
127
155
  ...restOptions,
128
156
  imageSegmenterOptions: {
129
157
  ...options,
@@ -132,44 +160,65 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
132
160
  },
133
161
  backgroundImage,
134
162
  };
135
- log?.('Init processor with config', { config: processorSettings });
136
- await postMsg('opened', {
163
+ log?.debug({
164
+ config: processorSettings,
165
+ }, 'Init processor with config');
166
+ const result = await postMsg('opened', {
137
167
  type: 'open',
138
168
  data: processorSettings,
139
- }, [output, backgroundImage?.image].filter(Boolean));
169
+ }, backgroundImage?.image ? [backgroundImage.image] : []);
140
170
  props.status = PROCESS_STATUS.Opened;
171
+ return result;
141
172
  }
142
173
  catch (error) {
143
174
  props.status = PROCESS_STATUS.Error;
144
- log?.('Failed to open process', {
145
- error: error,
146
- });
175
+ log?.warn({ error: error }, 'Failed to open process');
176
+ return {};
147
177
  }
148
178
  };
149
- const process = async (input, options) => {
179
+ async function process(input, options) {
150
180
  if (props.status === PROCESS_STATUS.Error ||
151
181
  props.status === PROCESS_STATUS.Updating) {
152
182
  return input;
153
183
  }
154
184
  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) {
185
+ if (input instanceof MediaStreamTrack) {
186
+ try {
187
+ // Clone the track and keep the original in main
188
+ const clonedTrack = input.clone();
189
+ const output = await postMsg('processed', {
190
+ type: 'process',
191
+ data: { track: clonedTrack, renderOptions: options },
192
+ }, [clonedTrack]);
193
+ assert(output);
161
194
  props.status = PROCESS_STATUS.Idle;
195
+ return output;
196
+ }
197
+ catch (error) {
198
+ props.status = PROCESS_STATUS.Error;
199
+ log?.warn({ error }, 'Error during the processing, return original track instead.');
200
+ return input;
162
201
  }
163
- return resultFrame;
164
202
  }
165
- catch (error) {
166
- props.status = PROCESS_STATUS.Error;
167
- log?.('Error during the processing, return original instead.', {
168
- error,
169
- });
170
- return input;
203
+ else {
204
+ try {
205
+ await postMsg('processed', {
206
+ type: 'process',
207
+ data: {
208
+ readable: input.readable,
209
+ writable: input.writable,
210
+ renderOptions: options,
211
+ },
212
+ }, [input.readable, input.writable]);
213
+ props.status = PROCESS_STATUS.Idle;
214
+ }
215
+ catch (error) {
216
+ props.status = PROCESS_STATUS.Error;
217
+ log?.warn({ error }, 'Error during the processing, return original ReadWritableStream instead.');
218
+ return input;
219
+ }
171
220
  }
172
- };
221
+ }
173
222
  const update = async (options) => {
174
223
  try {
175
224
  if (props.status === PROCESS_STATUS.Processing ||
@@ -179,11 +228,13 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
179
228
  const handleMessage = (ev) => {
180
229
  if (ev.data.type === 'processed' ||
181
230
  ev.data.type === 'opened' ||
182
- ev.data.type === 'updated') {
231
+ ev.data.type === 'updated' ||
232
+ ev.data.type === 'closed') {
183
233
  props.mediaWorker?.removeEventListener('message', handleMessage);
184
234
  resolve(undefined);
185
235
  }
186
- else {
236
+ else if (ev.data.type !== 'debug' &&
237
+ ev.data.type !== 'stats') {
187
238
  reject(new Error(`UnhandledEvent: ${ev.data.type}`));
188
239
  }
189
240
  };
@@ -194,9 +245,9 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
194
245
  props.status = PROCESS_STATUS.Updating;
195
246
  const backgroundImage = options.backgroundImage &&
196
247
  (await cloneImageRecord(options.backgroundImage));
197
- log?.('Update processor config', { config: options });
248
+ log?.debug({ config: options }, 'Update processor config');
198
249
  const transfer = backgroundImage ? [backgroundImage.image] : [];
199
- await postMsg('updated', {
250
+ const changed = await postMsg('updated', {
200
251
  type: 'update',
201
252
  data: {
202
253
  ...options,
@@ -204,25 +255,28 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
204
255
  },
205
256
  }, transfer);
206
257
  props.status = PROCESS_STATUS.Updated;
258
+ return changed;
207
259
  }
208
260
  catch (error) {
209
261
  props.status = PROCESS_STATUS.Error;
210
- log?.('Failed to update process', {
211
- error: error,
212
- });
262
+ log?.warn({ error: error }, 'Failed to update process');
263
+ return {};
213
264
  }
214
265
  };
215
- const close = () => {
266
+ const mute = (muted) => {
267
+ postMsg(undefined, { type: 'mute', data: muted });
268
+ };
269
+ const close = async () => {
216
270
  props.status = PROCESS_STATUS.Closing;
217
- log?.('Close processor');
218
- void postMsg(undefined, { type: 'close' });
219
- props.eventHandlers.clear();
271
+ log?.info('Close processor');
272
+ await postMsg('closed', { type: 'close' });
220
273
  props.status = PROCESS_STATUS.Closed;
221
274
  };
222
275
  const destroy = async () => {
223
276
  props.status = PROCESS_STATUS.Destroying;
224
- log?.('Destroy processor');
225
- await postMsg(undefined, { type: 'destroy' });
277
+ log?.info('Destroy processor');
278
+ props.eventHandlers.clear();
279
+ postMsg(undefined, { type: 'destroy' });
226
280
  props.mediaWorker?.terminate();
227
281
  props.mediaWorker = undefined;
228
282
  props.status = PROCESS_STATUS.Destroyed;
@@ -251,6 +305,7 @@ basePath = '/', { workerScriptUrl = new URL('../../../workers/mediaWorker.js', i
251
305
  open,
252
306
  update,
253
307
  process,
308
+ mute,
254
309
  close,
255
310
  destroy,
256
311
  };
@@ -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
+ }