@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.
- package/CHANGELOG.md +80 -0
- package/README.md +17 -10
- package/api-docs/README.mdx +40 -19
- package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
- package/api-docs/functions/avg.mdx +1 -1
- package/api-docs/functions/createAnalyzerGraphNode.mdx +3 -1
- package/api-docs/functions/createAnalyzerSubscribableGraphNode.mdx +3 -1
- package/api-docs/functions/createAsyncCallbackLoop.mdx +6 -6
- package/api-docs/functions/createAudioDestinationGraphNode.mdx +1 -1
- package/api-docs/functions/createAudioSignalDetector.mdx +4 -4
- package/api-docs/functions/createAudioStats.mdx +1 -1
- package/api-docs/functions/createChannelMergerGraphNode.mdx +1 -1
- package/api-docs/functions/createChannelSplitterGraphNode.mdx +1 -1
- package/api-docs/functions/createDelayGraphNode.mdx +1 -1
- package/api-docs/functions/createDenoiseWorkletGraphNode.mdx +2 -2
- package/api-docs/functions/createFrameCallbackRequest.mdx +10 -4
- package/api-docs/functions/createGainGraphNode.mdx +3 -1
- package/api-docs/functions/createMediaElementSourceGraphNode.mdx +1 -1
- package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
- package/api-docs/functions/createSegmenter.mdx +1 -1
- package/api-docs/functions/createStreamDestinationGraphNode.mdx +1 -1
- package/api-docs/functions/createStreamSourceGraphNode.mdx +1 -1
- package/api-docs/functions/createVADetector.mdx +3 -3
- package/api-docs/functions/createVideoProcessor.mdx +3 -3
- package/api-docs/functions/createVoiceDetectorFromProbability.mdx +1 -1
- package/api-docs/functions/createVoiceDetectorFromTimeData.mdx +3 -1
- package/api-docs/functions/createWindowedStats.mdx +2 -2
- package/api-docs/functions/getBezierCurveControlPoints.mdx +3 -1
- package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
- package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
- package/api-docs/functions/labelComponents.mdx +3 -1
- package/api-docs/functions/loadScript.mdx +1 -1
- package/api-docs/functions/loadWasms.mdx +1 -1
- package/api-docs/functions/nearestPowerOfTwo.mdx +1 -1
- package/api-docs/functions/playVideo.mdx +7 -5
- package/api-docs/functions/processAverageVolume.mdx +1 -1
- package/api-docs/functions/rms.mdx +1 -1
- package/api-docs/functions/subscribeWorkletNode.mdx +1 -1
- package/api-docs/functions/sum.mdx +1 -1
- package/api-docs/interfaces/Analyzer.mdx +4 -0
- package/api-docs/interfaces/AnalyzerSubscribableOptions.mdx +5 -3
- package/api-docs/interfaces/AudioGraph.mdx +2 -2
- package/api-docs/interfaces/AudioNodeInit.mdx +10 -6
- package/api-docs/interfaces/AudioNodeProps.mdx +3 -3
- package/api-docs/interfaces/Benchmark.mdx +17 -3
- package/api-docs/interfaces/Clock.mdx +1 -1
- package/api-docs/interfaces/Denoise.mdx +2 -2
- package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
- package/api-docs/interfaces/Gain.mdx +4 -0
- package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
- package/api-docs/interfaces/InputProcessAdaptor.mdx +32 -0
- package/api-docs/interfaces/MPMask.mdx +1 -1
- package/api-docs/interfaces/Process.mdx +12 -7
- package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerDebugMessage.mdx +1 -1
- package/api-docs/interfaces/ProcessWorkerEventClosed.mdx +5 -0
- package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
- package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
- package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
- package/api-docs/interfaces/ProcessorEvent.mdx +3 -2
- package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
- package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
- package/api-docs/interfaces/ProcessorUpdateOptions.mdx +17 -0
- package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
- package/api-docs/interfaces/RenderEventHandlers.mdx +3 -3
- package/api-docs/interfaces/Renderer.mdx +5 -3
- package/api-docs/interfaces/RendererOptions.mdx +5 -9
- package/api-docs/interfaces/Runner.mdx +3 -3
- package/api-docs/interfaces/Segmentation.mdx +1 -1
- package/api-docs/interfaces/Segmenter.mdx +115 -16
- package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
- package/api-docs/interfaces/VideoFrameLike.mdx +1 -1
- package/api-docs/interfaces/VideoProcessor.mdx +52 -8
- package/api-docs/interfaces/WorkletMessagePortOptions.mdx +2 -2
- package/api-docs/type-aliases/AsyncCallback.mdx +1 -1
- package/api-docs/type-aliases/Callback.mdx +2 -2
- package/api-docs/type-aliases/ExtractMessageEventType.mdx +1 -1
- package/api-docs/type-aliases/IncludeMessageEventDataType.mdx +1 -1
- package/api-docs/type-aliases/Logger.mdx +12 -0
- package/api-docs/type-aliases/OptionalKeys.mdx +1 -1
- package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
- package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
- package/api-docs/type-aliases/ProcessWorkerEvents.mdx +7 -4
- package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
- package/api-docs/type-aliases/RunnerCreator.mdx +3 -3
- package/api-docs/type-aliases/Struct.mdx +9 -0
- package/api-docs/type-aliases/TupleOf.mdx +1 -1
- package/api-docs/variables/FRAME_RATE.mdx +1 -1
- package/api-docs/variables/ImageFetchError.mdx +3 -0
- package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
- package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
- package/api-docs/variables/cloneImageRecord.mdx +21 -0
- package/api-docs/variables/compareImageRecords.mdx +18 -0
- package/api-docs/variables/createLazyProps.mdx +1 -1
- package/api-docs/variables/createObjectUpdater.mdx +2 -2
- package/api-docs/variables/createRemoteImageBitmap.mdx +5 -5
- package/api-docs/variables/handleWebGLContextLoss.mdx +1 -1
- package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
- package/api-docs/variables/isValidGpuAPI.mdx +13 -0
- package/api-docs/variables/urls.mdx +2 -2
- package/dist/common/backends/webgl/renderer.js +2 -12
- package/dist/common/backends/webgl/texture.d.ts +1 -2
- package/dist/common/backends/webgl/texture.js +2 -2
- package/dist/common/backends/webgl/webglUtils.js +8 -4
- package/dist/common/backends/webgpu/renderer.js +2 -12
- package/dist/common/backends/webgpu/tentBlur.js +1 -1
- package/dist/common/backends/webgpu/texture.d.ts +1 -2
- package/dist/common/backends/webgpu/texture.js +6 -2
- package/dist/common/constants.d.ts +3 -0
- package/dist/common/constants.js +4 -0
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/tsconfig.tsbuildinfo +1 -1
- package/dist/common/typeGuards.d.ts +2 -0
- package/dist/common/typeGuards.js +7 -0
- package/dist/common/types/media.d.ts +5 -1
- package/dist/common/types/messageEvents.d.ts +34 -4
- package/dist/common/types/processor.d.ts +7 -4
- package/dist/common/types/render.d.ts +5 -2
- package/dist/common/types/render.js +23 -1
- package/dist/common/types/segmentation.d.ts +0 -12
- package/dist/common/utils.d.ts +13 -2
- package/dist/common/utils.js +54 -10
- package/dist/main/audio.js +3 -3
- package/dist/main/benchUtils.d.ts +2 -2
- package/dist/main/benchUtils.js +4 -4
- package/dist/main/index.d.ts +1 -1
- package/dist/main/index.js +1 -1
- package/dist/main/tsconfig.tsbuildinfo +1 -1
- package/dist/main/types.d.ts +0 -6
- package/dist/main/utils.d.ts +4 -0
- package/dist/main/utils.js +31 -0
- package/dist/main/video/constants.d.ts +1 -4
- package/dist/main/video/constants.js +1 -5
- package/dist/main/video/index.d.ts +2 -3
- package/dist/main/video/index.js +2 -3
- package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
- package/dist/main/video/inputProcessAdaptor.js +198 -0
- package/dist/main/video/segmenter.d.ts +42 -0
- package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +130 -75
- package/dist/main/video/typeGuards.d.ts +2 -0
- package/dist/main/video/typeGuards.js +8 -1
- package/dist/main/video/types.d.ts +37 -17
- package/dist/main/video/utils.d.ts +5 -4
- package/dist/main/video/utils.js +49 -44
- package/dist/main/video/video.d.ts +30 -8
- package/dist/main/video/video.js +338 -32
- package/dist/workers/mediaWorker.js +24 -24
- package/dist/workers/tsconfig.tsbuildinfo +1 -1
- package/dist/worklets/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/api-docs/functions/createCanvasTransform.mdx +0 -14
- package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
- package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
- package/api-docs/interfaces/Detector.mdx +0 -81
- package/api-docs/interfaces/SegmentationParams.mdx +0 -35
- package/api-docs/interfaces/Transform.mdx +0 -62
- package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
- package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
- package/dist/main/transformer.d.ts +0 -1
- package/dist/main/transformer.js +0 -4
- package/dist/main/video/canvasTransform.d.ts +0 -17
- package/dist/main/video/canvasTransform.js +0 -341
- package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
- package/dist/main/video/transformer.d.ts +0 -9
- package/dist/main/video/transformer.js +0 -68
- package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
- 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 '
|
|
3
|
-
|
|
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('
|
|
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}
|
|
24
|
+
log?.error({ event: error, error: error.error }, `${error.message}`);
|
|
28
25
|
error.preventDefault();
|
|
29
26
|
});
|
|
30
27
|
mediaWorker.addEventListener('message', (event) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
93
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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 ({
|
|
145
|
+
const open = async ({ imageSegmenterOptions, ...restOptions }) => {
|
|
117
146
|
props.status = PROCESS_STATUS.Opening;
|
|
118
|
-
props.mediaWorker
|
|
119
|
-
|
|
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?.(
|
|
136
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
146
|
-
});
|
|
175
|
+
log?.warn({ error: error }, 'Failed to open process');
|
|
176
|
+
return {};
|
|
147
177
|
}
|
|
148
178
|
};
|
|
149
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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?.(
|
|
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
|
-
|
|
212
|
-
});
|
|
262
|
+
log?.warn({ error: error }, 'Failed to update process');
|
|
263
|
+
return {};
|
|
213
264
|
}
|
|
214
265
|
};
|
|
215
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|