@livekit/track-processors 0.4.0 → 0.5.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/README.md +9 -1
- package/dist/index.js +615 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +615 -119
- package/dist/index.mjs.map +1 -1
- package/dist/src/ProcessorWrapper.d.ts +26 -1
- package/dist/src/index.d.ts +20 -6
- package/dist/src/transformers/BackgroundTransformer.d.ts +8 -3
- package/dist/src/transformers/VideoTransformer.d.ts +2 -1
- package/dist/src/webgl/index.d.ts +9 -0
- package/package.json +9 -9
- package/src/ProcessorWrapper.ts +271 -18
- package/src/index.ts +82 -10
- package/src/transformers/BackgroundTransformer.ts +55 -104
- package/src/transformers/VideoTransformer.ts +14 -4
- package/src/utils.ts +1 -0
- package/src/webgl/index.ts +458 -0
package/dist/index.mjs
CHANGED
|
@@ -17,13 +17,29 @@ async function waitForTrackResolution(track) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// src/ProcessorWrapper.ts
|
|
20
|
-
var ProcessorWrapper = class {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
var ProcessorWrapper = class _ProcessorWrapper {
|
|
21
|
+
constructor(transformer, name, options = {}) {
|
|
22
|
+
// For tracking whether we're using the stream API fallback
|
|
23
|
+
this.useStreamFallback = false;
|
|
24
|
+
this.processingEnabled = false;
|
|
25
|
+
var _a;
|
|
25
26
|
this.name = name;
|
|
26
27
|
this.transformer = transformer;
|
|
28
|
+
this.maxFps = (_a = options.maxFps) != null ? _a : 30;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Determines if the Processor is supported on the current browser
|
|
32
|
+
*/
|
|
33
|
+
static get isSupported() {
|
|
34
|
+
const hasStreamProcessor = typeof MediaStreamTrackGenerator !== "undefined" && typeof MediaStreamTrackProcessor !== "undefined";
|
|
35
|
+
const hasFallbackSupport = typeof HTMLCanvasElement !== "undefined" && typeof VideoFrame !== "undefined" && "captureStream" in HTMLCanvasElement.prototype;
|
|
36
|
+
return hasStreamProcessor || hasFallbackSupport;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Determines if modern browser APIs are supported, which yield better performance
|
|
40
|
+
*/
|
|
41
|
+
static get hasModernApiSupport() {
|
|
42
|
+
return typeof MediaStreamTrackGenerator !== "undefined" && typeof MediaStreamTrackProcessor !== "undefined";
|
|
27
43
|
}
|
|
28
44
|
async setup(opts) {
|
|
29
45
|
this.source = opts.track;
|
|
@@ -36,41 +52,187 @@ var ProcessorWrapper = class {
|
|
|
36
52
|
this.sourceDummy.height = height != null ? height : 300;
|
|
37
53
|
this.sourceDummy.width = width != null ? width : 300;
|
|
38
54
|
}
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
this.useStreamFallback = !_ProcessorWrapper.hasModernApiSupport;
|
|
56
|
+
if (this.useStreamFallback) {
|
|
57
|
+
const existingCanvas = document.querySelector(
|
|
58
|
+
'canvas[data-livekit-processor="' + this.name + '"]'
|
|
59
|
+
);
|
|
60
|
+
if (existingCanvas) {
|
|
61
|
+
this.displayCanvas = existingCanvas;
|
|
62
|
+
this.displayCanvas.width = width != null ? width : 300;
|
|
63
|
+
this.displayCanvas.height = height != null ? height : 300;
|
|
64
|
+
} else {
|
|
65
|
+
this.displayCanvas = document.createElement("canvas");
|
|
66
|
+
this.displayCanvas.width = width != null ? width : 300;
|
|
67
|
+
this.displayCanvas.height = height != null ? height : 300;
|
|
68
|
+
this.displayCanvas.style.display = "none";
|
|
69
|
+
this.displayCanvas.dataset.livekitProcessor = this.name;
|
|
70
|
+
document.body.appendChild(this.displayCanvas);
|
|
71
|
+
}
|
|
72
|
+
this.renderContext = this.displayCanvas.getContext("2d");
|
|
73
|
+
this.capturedStream = this.displayCanvas.captureStream();
|
|
74
|
+
this.canvas = new OffscreenCanvas(width != null ? width : 300, height != null ? height : 300);
|
|
75
|
+
} else {
|
|
76
|
+
this.processor = new MediaStreamTrackProcessor({ track: this.source });
|
|
77
|
+
this.trackGenerator = new MediaStreamTrackGenerator({
|
|
78
|
+
kind: "video",
|
|
79
|
+
signalTarget: this.source
|
|
80
|
+
});
|
|
81
|
+
this.canvas = new OffscreenCanvas(width != null ? width : 300, height != null ? height : 300);
|
|
82
|
+
}
|
|
45
83
|
}
|
|
46
84
|
async init(opts) {
|
|
47
85
|
await this.setup(opts);
|
|
48
|
-
if (!this.canvas
|
|
49
|
-
throw new TypeError("Expected
|
|
86
|
+
if (!this.canvas) {
|
|
87
|
+
throw new TypeError("Expected canvas to be defined after setup");
|
|
50
88
|
}
|
|
51
|
-
const readableStream = this.processor.readable;
|
|
52
89
|
await this.transformer.init({
|
|
53
90
|
outputCanvas: this.canvas,
|
|
54
91
|
inputElement: this.sourceDummy
|
|
55
92
|
});
|
|
93
|
+
if (this.useStreamFallback) {
|
|
94
|
+
this.initFallbackPath();
|
|
95
|
+
} else {
|
|
96
|
+
this.initStreamProcessorPath();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
initStreamProcessorPath() {
|
|
100
|
+
if (!this.processor || !this.trackGenerator) {
|
|
101
|
+
throw new TypeError(
|
|
102
|
+
"Expected processor and trackGenerator to be defined for stream processor path"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const readableStream = this.processor.readable;
|
|
56
106
|
const pipedStream = readableStream.pipeThrough(this.transformer.transformer);
|
|
57
107
|
pipedStream.pipeTo(this.trackGenerator.writable).catch((e) => console.error("error when trying to pipe", e)).finally(() => this.destroy());
|
|
58
108
|
this.processedTrack = this.trackGenerator;
|
|
59
109
|
}
|
|
110
|
+
initFallbackPath() {
|
|
111
|
+
if (!this.capturedStream || !this.source || !this.canvas || !this.renderContext) {
|
|
112
|
+
throw new TypeError("Missing required components for fallback implementation");
|
|
113
|
+
}
|
|
114
|
+
this.processedTrack = this.capturedStream.getVideoTracks()[0];
|
|
115
|
+
this.processingEnabled = true;
|
|
116
|
+
this.frameCallback = (frame) => {
|
|
117
|
+
if (!this.processingEnabled || !frame) {
|
|
118
|
+
frame.close();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const controller = {
|
|
122
|
+
enqueue: (processedFrame) => {
|
|
123
|
+
if (this.renderContext && this.displayCanvas) {
|
|
124
|
+
this.renderContext.drawImage(
|
|
125
|
+
processedFrame,
|
|
126
|
+
0,
|
|
127
|
+
0,
|
|
128
|
+
this.displayCanvas.width,
|
|
129
|
+
this.displayCanvas.height
|
|
130
|
+
);
|
|
131
|
+
processedFrame.close();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
this.transformer.transform(frame, controller);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error("Error in transform:", e);
|
|
139
|
+
frame.close();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
this.startRenderLoop();
|
|
143
|
+
}
|
|
144
|
+
startRenderLoop() {
|
|
145
|
+
if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let lastVideoTimestamp = -1;
|
|
149
|
+
let lastFrameTime = 0;
|
|
150
|
+
const videoElement = this.sourceDummy;
|
|
151
|
+
const minFrameInterval = 1e3 / this.maxFps;
|
|
152
|
+
let estimatedVideoFps = this.maxFps;
|
|
153
|
+
let frameTimeHistory = [];
|
|
154
|
+
let lastVideoTimeChange = 0;
|
|
155
|
+
let frameCount = 0;
|
|
156
|
+
let lastFpsLog = 0;
|
|
157
|
+
const renderLoop = () => {
|
|
158
|
+
if (!this.processingEnabled || !this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const videoTime = videoElement.currentTime;
|
|
162
|
+
const now = performance.now();
|
|
163
|
+
const timeSinceLastFrame = now - lastFrameTime;
|
|
164
|
+
const hasNewFrame = videoTime !== lastVideoTimestamp;
|
|
165
|
+
if (hasNewFrame) {
|
|
166
|
+
if (lastVideoTimeChange > 0) {
|
|
167
|
+
const timeBetweenFrames = now - lastVideoTimeChange;
|
|
168
|
+
frameTimeHistory.push(timeBetweenFrames);
|
|
169
|
+
if (frameTimeHistory.length > 10) {
|
|
170
|
+
frameTimeHistory.shift();
|
|
171
|
+
}
|
|
172
|
+
if (frameTimeHistory.length > 2) {
|
|
173
|
+
const avgFrameTime = frameTimeHistory.reduce((sum, time) => sum + time, 0) / frameTimeHistory.length;
|
|
174
|
+
estimatedVideoFps = 1e3 / avgFrameTime;
|
|
175
|
+
const isDevelopment = typeof window !== "undefined" && window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
176
|
+
if (isDevelopment && now - lastFpsLog > 5e3) {
|
|
177
|
+
console.debug(
|
|
178
|
+
`[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(
|
|
179
|
+
1
|
|
180
|
+
)}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`
|
|
181
|
+
);
|
|
182
|
+
frameCount = 0;
|
|
183
|
+
lastFpsLog = now;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
lastVideoTimeChange = now;
|
|
188
|
+
}
|
|
189
|
+
const timeThresholdMet = timeSinceLastFrame >= minFrameInterval;
|
|
190
|
+
if (hasNewFrame && timeThresholdMet) {
|
|
191
|
+
lastVideoTimestamp = videoTime;
|
|
192
|
+
lastFrameTime = now;
|
|
193
|
+
frameCount++;
|
|
194
|
+
try {
|
|
195
|
+
const frame = new VideoFrame(videoElement);
|
|
196
|
+
if (this.frameCallback) {
|
|
197
|
+
this.frameCallback(frame);
|
|
198
|
+
} else {
|
|
199
|
+
frame.close();
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error("Error in render loop:", e);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.animationFrameId = requestAnimationFrame(renderLoop);
|
|
206
|
+
};
|
|
207
|
+
this.animationFrameId = requestAnimationFrame(renderLoop);
|
|
208
|
+
}
|
|
60
209
|
async restart(opts) {
|
|
61
210
|
await this.destroy();
|
|
62
|
-
|
|
211
|
+
await this.init(opts);
|
|
63
212
|
}
|
|
64
213
|
async restartTransformer(...options) {
|
|
65
|
-
this.transformer.restart(options[0]);
|
|
214
|
+
await this.transformer.restart(options[0]);
|
|
66
215
|
}
|
|
67
216
|
async updateTransformerOptions(...options) {
|
|
68
|
-
this.transformer.update(options[0]);
|
|
217
|
+
await this.transformer.update(options[0]);
|
|
69
218
|
}
|
|
70
219
|
async destroy() {
|
|
71
|
-
var _a;
|
|
220
|
+
var _a, _b, _c, _d;
|
|
221
|
+
if (this.useStreamFallback) {
|
|
222
|
+
this.processingEnabled = false;
|
|
223
|
+
if (this.animationFrameId) {
|
|
224
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
225
|
+
this.animationFrameId = void 0;
|
|
226
|
+
}
|
|
227
|
+
if (this.displayCanvas && this.displayCanvas.parentNode) {
|
|
228
|
+
this.displayCanvas.parentNode.removeChild(this.displayCanvas);
|
|
229
|
+
}
|
|
230
|
+
(_a = this.capturedStream) == null ? void 0 : _a.getTracks().forEach((track) => track.stop());
|
|
231
|
+
} else {
|
|
232
|
+
await ((_c = (_b = this.processor) == null ? void 0 : _b.writableControl) == null ? void 0 : _c.close());
|
|
233
|
+
(_d = this.trackGenerator) == null ? void 0 : _d.stop();
|
|
234
|
+
}
|
|
72
235
|
await this.transformer.destroy();
|
|
73
|
-
(_a = this.trackGenerator) == null ? void 0 : _a.stop();
|
|
74
236
|
}
|
|
75
237
|
};
|
|
76
238
|
|
|
@@ -79,7 +241,348 @@ import * as vision from "@mediapipe/tasks-vision";
|
|
|
79
241
|
|
|
80
242
|
// package.json
|
|
81
243
|
var dependencies = {
|
|
82
|
-
"@mediapipe/tasks-vision": "0.10.
|
|
244
|
+
"@mediapipe/tasks-vision": "^0.10.22-rc.20250304"
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/webgl/index.ts
|
|
248
|
+
var blurFragmentShader = `
|
|
249
|
+
precision highp float;
|
|
250
|
+
varying vec2 texCoords;
|
|
251
|
+
uniform sampler2D u_texture;
|
|
252
|
+
uniform vec2 u_texelSize;
|
|
253
|
+
uniform vec2 u_direction;
|
|
254
|
+
uniform float u_radius;
|
|
255
|
+
|
|
256
|
+
void main() {
|
|
257
|
+
float sigma = u_radius;
|
|
258
|
+
float twoSigmaSq = 2.0 * sigma * sigma;
|
|
259
|
+
float totalWeight = 0.0;
|
|
260
|
+
vec3 result = vec3(0.0);
|
|
261
|
+
const int MAX_SAMPLES = 16;
|
|
262
|
+
int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));
|
|
263
|
+
|
|
264
|
+
for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {
|
|
265
|
+
float offset = float(i);
|
|
266
|
+
if (abs(offset) > float(radius)) continue;
|
|
267
|
+
float weight = exp(-(offset * offset) / twoSigmaSq);
|
|
268
|
+
vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;
|
|
269
|
+
result += texture2D(u_texture, sampleCoord).rgb * weight;
|
|
270
|
+
totalWeight += weight;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
gl_FragColor = vec4(result / totalWeight, 1.0);
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
var createShaderProgram = (gl) => {
|
|
277
|
+
const vs = `
|
|
278
|
+
attribute vec2 position;
|
|
279
|
+
varying vec2 texCoords;
|
|
280
|
+
|
|
281
|
+
void main() {
|
|
282
|
+
texCoords = (position + 1.0) / 2.0;
|
|
283
|
+
texCoords.y = 1.0 - texCoords.y;
|
|
284
|
+
gl_Position = vec4(position, 0, 1.0);
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
const cS = `
|
|
288
|
+
precision highp float;
|
|
289
|
+
varying vec2 texCoords;
|
|
290
|
+
uniform sampler2D background;
|
|
291
|
+
uniform sampler2D frame;
|
|
292
|
+
uniform sampler2D mask;
|
|
293
|
+
void main() {
|
|
294
|
+
vec4 maskTex = texture2D(mask, texCoords);
|
|
295
|
+
vec4 frameTex = texture2D(frame, texCoords);
|
|
296
|
+
vec4 bgTex = texture2D(background, texCoords);
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
float a = maskTex.r;
|
|
300
|
+
|
|
301
|
+
gl_FragColor = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - a);
|
|
302
|
+
|
|
303
|
+
}
|
|
304
|
+
`;
|
|
305
|
+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
306
|
+
if (!vertexShader) {
|
|
307
|
+
throw Error("can not create vertex shader");
|
|
308
|
+
}
|
|
309
|
+
gl.shaderSource(vertexShader, vs);
|
|
310
|
+
gl.compileShader(vertexShader);
|
|
311
|
+
const compositeShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
312
|
+
if (!compositeShader) {
|
|
313
|
+
throw Error("can not create fragment shader");
|
|
314
|
+
}
|
|
315
|
+
gl.shaderSource(compositeShader, cS);
|
|
316
|
+
gl.compileShader(compositeShader);
|
|
317
|
+
const compositeProgram = gl.createProgram();
|
|
318
|
+
if (!compositeProgram) {
|
|
319
|
+
throw Error("can not create composite program");
|
|
320
|
+
}
|
|
321
|
+
gl.attachShader(compositeProgram, vertexShader);
|
|
322
|
+
gl.attachShader(compositeProgram, compositeShader);
|
|
323
|
+
gl.linkProgram(compositeProgram);
|
|
324
|
+
let blurProgram = null;
|
|
325
|
+
let blurVertexShader = null;
|
|
326
|
+
let blurFrag = null;
|
|
327
|
+
let blurUniforms = null;
|
|
328
|
+
blurFrag = gl.createShader(gl.FRAGMENT_SHADER);
|
|
329
|
+
if (!blurFrag) {
|
|
330
|
+
throw Error("can not create blur shader");
|
|
331
|
+
}
|
|
332
|
+
gl.shaderSource(blurFrag, blurFragmentShader);
|
|
333
|
+
gl.compileShader(blurFrag);
|
|
334
|
+
if (!gl.getShaderParameter(blurFrag, gl.COMPILE_STATUS)) {
|
|
335
|
+
const info = gl.getShaderInfoLog(blurFrag);
|
|
336
|
+
throw Error(`Failed to compile blur shader: ${info}`);
|
|
337
|
+
}
|
|
338
|
+
blurVertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
339
|
+
if (!blurVertexShader) {
|
|
340
|
+
throw Error("can not create blur vertex shader");
|
|
341
|
+
}
|
|
342
|
+
gl.shaderSource(blurVertexShader, vs);
|
|
343
|
+
gl.compileShader(blurVertexShader);
|
|
344
|
+
blurProgram = gl.createProgram();
|
|
345
|
+
if (!blurProgram) {
|
|
346
|
+
throw Error("can not create blur program");
|
|
347
|
+
}
|
|
348
|
+
gl.attachShader(blurProgram, blurVertexShader);
|
|
349
|
+
gl.attachShader(blurProgram, blurFrag);
|
|
350
|
+
gl.linkProgram(blurProgram);
|
|
351
|
+
if (!gl.getProgramParameter(blurProgram, gl.LINK_STATUS)) {
|
|
352
|
+
const info = gl.getProgramInfoLog(blurProgram);
|
|
353
|
+
throw Error(`Failed to link blur program: ${info}`);
|
|
354
|
+
}
|
|
355
|
+
blurUniforms = {
|
|
356
|
+
position: gl.getAttribLocation(blurProgram, "position"),
|
|
357
|
+
texture: gl.getUniformLocation(blurProgram, "u_texture"),
|
|
358
|
+
texelSize: gl.getUniformLocation(blurProgram, "u_texelSize"),
|
|
359
|
+
direction: gl.getUniformLocation(blurProgram, "u_direction"),
|
|
360
|
+
radius: gl.getUniformLocation(blurProgram, "u_radius")
|
|
361
|
+
};
|
|
362
|
+
return {
|
|
363
|
+
vertexShader,
|
|
364
|
+
compositeShader,
|
|
365
|
+
blurShader: blurFrag,
|
|
366
|
+
compositeProgram,
|
|
367
|
+
blurProgram,
|
|
368
|
+
attribLocations: {
|
|
369
|
+
position: gl.getAttribLocation(compositeProgram, "position")
|
|
370
|
+
},
|
|
371
|
+
uniformLocations: {
|
|
372
|
+
mask: gl.getUniformLocation(compositeProgram, "mask"),
|
|
373
|
+
frame: gl.getUniformLocation(compositeProgram, "frame"),
|
|
374
|
+
background: gl.getUniformLocation(compositeProgram, "background")
|
|
375
|
+
},
|
|
376
|
+
blurUniforms
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
function initTexture(gl, texIndex) {
|
|
380
|
+
const texRef = gl.TEXTURE0 + texIndex;
|
|
381
|
+
gl.activeTexture(texRef);
|
|
382
|
+
const texture = gl.createTexture();
|
|
383
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
384
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
385
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
386
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
387
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
388
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
389
|
+
return texture;
|
|
390
|
+
}
|
|
391
|
+
function createFramebuffer(gl, texture, width, height) {
|
|
392
|
+
const framebuffer = gl.createFramebuffer();
|
|
393
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
394
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
395
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
396
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
397
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
398
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
399
|
+
throw new Error("Framebuffer not complete");
|
|
400
|
+
}
|
|
401
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
402
|
+
return framebuffer;
|
|
403
|
+
}
|
|
404
|
+
var createVertexBuffer = (gl) => {
|
|
405
|
+
if (!gl) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const vertexBuffer = gl.createBuffer();
|
|
409
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
410
|
+
gl.bufferData(
|
|
411
|
+
gl.ARRAY_BUFFER,
|
|
412
|
+
new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),
|
|
413
|
+
gl.STATIC_DRAW
|
|
414
|
+
);
|
|
415
|
+
return vertexBuffer;
|
|
416
|
+
};
|
|
417
|
+
var setupWebGL = (canvas) => {
|
|
418
|
+
const gl = canvas.getContext("webgl2", { premultipliedAlpha: false });
|
|
419
|
+
let blurRadius = null;
|
|
420
|
+
if (!gl) {
|
|
421
|
+
return void 0;
|
|
422
|
+
}
|
|
423
|
+
gl.enable(gl.BLEND);
|
|
424
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
425
|
+
const {
|
|
426
|
+
compositeProgram,
|
|
427
|
+
blurProgram,
|
|
428
|
+
attribLocations: { position: positionLocation },
|
|
429
|
+
uniformLocations: {
|
|
430
|
+
mask: maskTextureLocation,
|
|
431
|
+
frame: frameTextureLocation,
|
|
432
|
+
background: bgTextureLocation
|
|
433
|
+
},
|
|
434
|
+
blurUniforms
|
|
435
|
+
} = createShaderProgram(gl);
|
|
436
|
+
const bgTexture = initTexture(gl, 0);
|
|
437
|
+
const frameTexture = initTexture(gl, 1);
|
|
438
|
+
const vertexBuffer = createVertexBuffer(gl);
|
|
439
|
+
let processTextures = [];
|
|
440
|
+
let processFramebuffers = [];
|
|
441
|
+
processTextures.push(initTexture(gl, 3));
|
|
442
|
+
processTextures.push(initTexture(gl, 4));
|
|
443
|
+
processFramebuffers.push(createFramebuffer(gl, processTextures[0], canvas.width, canvas.height));
|
|
444
|
+
processFramebuffers.push(createFramebuffer(gl, processTextures[1], canvas.width, canvas.height));
|
|
445
|
+
gl.useProgram(compositeProgram);
|
|
446
|
+
gl.uniform1i(bgTextureLocation, 0);
|
|
447
|
+
gl.uniform1i(frameTextureLocation, 1);
|
|
448
|
+
gl.uniform1i(maskTextureLocation, 2);
|
|
449
|
+
let customBackgroundImage = null;
|
|
450
|
+
function applyBlur(sourceTexture, width, height) {
|
|
451
|
+
if (!blurRadius || !blurProgram || !blurUniforms)
|
|
452
|
+
return bgTexture;
|
|
453
|
+
gl.useProgram(blurProgram);
|
|
454
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
455
|
+
gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);
|
|
456
|
+
gl.enableVertexAttribArray(blurUniforms.position);
|
|
457
|
+
const texelWidth = 1 / width;
|
|
458
|
+
const texelHeight = 1 / height;
|
|
459
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);
|
|
460
|
+
gl.viewport(0, 0, width, height);
|
|
461
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
462
|
+
gl.bindTexture(gl.TEXTURE_2D, sourceTexture);
|
|
463
|
+
gl.uniform1i(blurUniforms.texture, 0);
|
|
464
|
+
gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);
|
|
465
|
+
gl.uniform2f(blurUniforms.direction, 1, 0);
|
|
466
|
+
gl.uniform1f(blurUniforms.radius, blurRadius);
|
|
467
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
468
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);
|
|
469
|
+
gl.viewport(0, 0, width, height);
|
|
470
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
471
|
+
gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);
|
|
472
|
+
gl.uniform1i(blurUniforms.texture, 0);
|
|
473
|
+
gl.uniform2f(blurUniforms.direction, 0, 1);
|
|
474
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
475
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
476
|
+
return processTextures[1];
|
|
477
|
+
}
|
|
478
|
+
function render(frame, mask) {
|
|
479
|
+
if (frame.codedWidth === 0 || mask.width === 0) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const width = frame.displayWidth;
|
|
483
|
+
const height = frame.displayHeight;
|
|
484
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
485
|
+
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
486
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
|
|
487
|
+
let backgroundTexture = bgTexture;
|
|
488
|
+
if (customBackgroundImage) {
|
|
489
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
490
|
+
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
491
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
|
|
492
|
+
backgroundTexture = bgTexture;
|
|
493
|
+
} else if (blurRadius) {
|
|
494
|
+
backgroundTexture = applyBlur(frameTexture, width, height);
|
|
495
|
+
}
|
|
496
|
+
const maskTexture = mask.getAsWebGLTexture();
|
|
497
|
+
gl.viewport(0, 0, width, height);
|
|
498
|
+
gl.clearColor(1, 1, 1, 1);
|
|
499
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
500
|
+
gl.useProgram(compositeProgram);
|
|
501
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
502
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
503
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
504
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
505
|
+
gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);
|
|
506
|
+
gl.uniform1i(bgTextureLocation, 0);
|
|
507
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
508
|
+
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
509
|
+
gl.uniform1i(frameTextureLocation, 1);
|
|
510
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
511
|
+
gl.bindTexture(gl.TEXTURE_2D, maskTexture);
|
|
512
|
+
gl.uniform1i(maskTextureLocation, 2);
|
|
513
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
514
|
+
mask.close();
|
|
515
|
+
}
|
|
516
|
+
async function setBackgroundImage(image) {
|
|
517
|
+
customBackgroundImage = null;
|
|
518
|
+
if (image) {
|
|
519
|
+
try {
|
|
520
|
+
const canvasWidth = canvas.width;
|
|
521
|
+
const canvasHeight = canvas.height;
|
|
522
|
+
const imgAspect = image.width / image.height;
|
|
523
|
+
const canvasAspect = canvasWidth / canvasHeight;
|
|
524
|
+
let sx = 0;
|
|
525
|
+
let sy = 0;
|
|
526
|
+
let sWidth = image.width;
|
|
527
|
+
let sHeight = image.height;
|
|
528
|
+
if (imgAspect > canvasAspect) {
|
|
529
|
+
sWidth = Math.round(image.height * canvasAspect);
|
|
530
|
+
sx = Math.round((image.width - sWidth) / 2);
|
|
531
|
+
} else if (imgAspect < canvasAspect) {
|
|
532
|
+
sHeight = Math.round(image.width / canvasAspect);
|
|
533
|
+
sy = Math.round((image.height - sHeight) / 2);
|
|
534
|
+
}
|
|
535
|
+
const croppedImage = await createImageBitmap(image, sx, sy, sWidth, sHeight, {
|
|
536
|
+
resizeWidth: canvasWidth,
|
|
537
|
+
resizeHeight: canvasHeight,
|
|
538
|
+
resizeQuality: "medium"
|
|
539
|
+
});
|
|
540
|
+
customBackgroundImage = croppedImage;
|
|
541
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
542
|
+
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
543
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, croppedImage);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error("Error processing background image:", error);
|
|
546
|
+
customBackgroundImage = image;
|
|
547
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
548
|
+
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
549
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
const emptyImage = new ImageData(2, 2);
|
|
553
|
+
emptyImage.data[0] = 0;
|
|
554
|
+
emptyImage.data[1] = 0;
|
|
555
|
+
emptyImage.data[2] = 0;
|
|
556
|
+
emptyImage.data[3] = 0;
|
|
557
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
558
|
+
gl.bindTexture(gl.TEXTURE_2D, bgTexture);
|
|
559
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, emptyImage);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function setBlurRadius(radius) {
|
|
563
|
+
blurRadius = radius;
|
|
564
|
+
setBackgroundImage(null);
|
|
565
|
+
}
|
|
566
|
+
function cleanup() {
|
|
567
|
+
gl.deleteProgram(compositeProgram);
|
|
568
|
+
gl.deleteProgram(blurProgram);
|
|
569
|
+
gl.deleteTexture(bgTexture);
|
|
570
|
+
gl.deleteTexture(frameTexture);
|
|
571
|
+
for (const texture of processTextures) {
|
|
572
|
+
gl.deleteTexture(texture);
|
|
573
|
+
}
|
|
574
|
+
for (const framebuffer of processFramebuffers) {
|
|
575
|
+
gl.deleteFramebuffer(framebuffer);
|
|
576
|
+
}
|
|
577
|
+
gl.deleteBuffer(vertexBuffer);
|
|
578
|
+
if (customBackgroundImage) {
|
|
579
|
+
customBackgroundImage.close();
|
|
580
|
+
customBackgroundImage = null;
|
|
581
|
+
}
|
|
582
|
+
processTextures = [];
|
|
583
|
+
processFramebuffers = [];
|
|
584
|
+
}
|
|
585
|
+
return { render, setBackgroundImage, setBlurRadius, cleanup };
|
|
83
586
|
};
|
|
84
587
|
|
|
85
588
|
// src/transformers/VideoTransformer.ts
|
|
@@ -91,7 +594,6 @@ var VideoTransformer = class {
|
|
|
91
594
|
outputCanvas,
|
|
92
595
|
inputElement: inputVideo
|
|
93
596
|
}) {
|
|
94
|
-
var _a;
|
|
95
597
|
if (!(inputVideo instanceof HTMLVideoElement)) {
|
|
96
598
|
throw TypeError("Video transformer needs a HTMLVideoElement as input");
|
|
97
599
|
}
|
|
@@ -100,21 +602,29 @@ var VideoTransformer = class {
|
|
|
100
602
|
});
|
|
101
603
|
this.canvas = outputCanvas || null;
|
|
102
604
|
if (outputCanvas) {
|
|
103
|
-
this.
|
|
605
|
+
this.gl = setupWebGL(
|
|
606
|
+
this.canvas || new OffscreenCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
|
|
607
|
+
);
|
|
104
608
|
}
|
|
105
609
|
this.inputVideo = inputVideo;
|
|
106
610
|
this.isDisabled = false;
|
|
107
611
|
}
|
|
108
612
|
async restart({ outputCanvas, inputElement: inputVideo }) {
|
|
613
|
+
var _a;
|
|
109
614
|
this.canvas = outputCanvas || null;
|
|
110
|
-
|
|
615
|
+
(_a = this.gl) == null ? void 0 : _a.cleanup();
|
|
616
|
+
this.gl = setupWebGL(
|
|
617
|
+
this.canvas || new OffscreenCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
|
|
618
|
+
);
|
|
111
619
|
this.inputVideo = inputVideo;
|
|
112
620
|
this.isDisabled = false;
|
|
113
621
|
}
|
|
114
622
|
async destroy() {
|
|
623
|
+
var _a;
|
|
115
624
|
this.isDisabled = true;
|
|
116
625
|
this.canvas = void 0;
|
|
117
|
-
this.
|
|
626
|
+
(_a = this.gl) == null ? void 0 : _a.cleanup();
|
|
627
|
+
this.gl = void 0;
|
|
118
628
|
}
|
|
119
629
|
};
|
|
120
630
|
|
|
@@ -127,10 +637,10 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
127
637
|
this.update(opts);
|
|
128
638
|
}
|
|
129
639
|
static get isSupported() {
|
|
130
|
-
return typeof OffscreenCanvas !== "undefined";
|
|
640
|
+
return typeof OffscreenCanvas !== "undefined" && typeof VideoFrame !== "undefined" && typeof createImageBitmap !== "undefined" && !!document.createElement("canvas").getContext("webgl2");
|
|
131
641
|
}
|
|
132
642
|
async init({ outputCanvas, inputElement: inputVideo }) {
|
|
133
|
-
var _a, _b, _c, _d, _e;
|
|
643
|
+
var _a, _b, _c, _d, _e, _f;
|
|
134
644
|
await super.init({ outputCanvas, inputElement: inputVideo });
|
|
135
645
|
const fileSet = await vision.FilesetResolver.forVisionTasks(
|
|
136
646
|
(_b = (_a = this.options.assetPaths) == null ? void 0 : _a.tasksVisionFileSet) != null ? _b : `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${dependencies["@mediapipe/tasks-vision"]}/wasm`
|
|
@@ -141,6 +651,7 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
141
651
|
delegate: "GPU",
|
|
142
652
|
...this.options.segmenterOptions
|
|
143
653
|
},
|
|
654
|
+
canvas: this.canvas,
|
|
144
655
|
runningMode: "VIDEO",
|
|
145
656
|
outputCategoryMask: true,
|
|
146
657
|
outputConfidenceMasks: false
|
|
@@ -150,6 +661,9 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
150
661
|
(err) => console.error("Error while loading processor background image: ", err)
|
|
151
662
|
);
|
|
152
663
|
}
|
|
664
|
+
if (this.options.blurRadius) {
|
|
665
|
+
(_f = this.gl) == null ? void 0 : _f.setBlurRadius(this.options.blurRadius);
|
|
666
|
+
}
|
|
153
667
|
}
|
|
154
668
|
async destroy() {
|
|
155
669
|
var _a;
|
|
@@ -158,6 +672,7 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
158
672
|
this.backgroundImage = null;
|
|
159
673
|
}
|
|
160
674
|
async loadBackground(path) {
|
|
675
|
+
var _a;
|
|
161
676
|
const img = new Image();
|
|
162
677
|
await new Promise((resolve, reject) => {
|
|
163
678
|
img.crossOrigin = "Anonymous";
|
|
@@ -166,12 +681,12 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
166
681
|
img.src = path;
|
|
167
682
|
});
|
|
168
683
|
const imageData = await createImageBitmap(img);
|
|
169
|
-
this.
|
|
684
|
+
(_a = this.gl) == null ? void 0 : _a.setBackgroundImage(imageData);
|
|
170
685
|
}
|
|
171
686
|
async transform(frame, controller) {
|
|
172
687
|
var _a;
|
|
173
688
|
try {
|
|
174
|
-
if (!(frame instanceof VideoFrame)) {
|
|
689
|
+
if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
|
|
175
690
|
console.debug("empty frame detected, ignoring");
|
|
176
691
|
return;
|
|
177
692
|
}
|
|
@@ -182,118 +697,97 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
182
697
|
if (!this.canvas) {
|
|
183
698
|
throw TypeError("Canvas needs to be initialized first");
|
|
184
699
|
}
|
|
700
|
+
this.canvas.width = frame.displayWidth;
|
|
701
|
+
this.canvas.height = frame.displayHeight;
|
|
185
702
|
let startTimeMs = performance.now();
|
|
186
|
-
(_a = this.imageSegmenter) == null ? void 0 : _a.segmentForVideo(
|
|
187
|
-
|
|
188
|
-
startTimeMs
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
703
|
+
(_a = this.imageSegmenter) == null ? void 0 : _a.segmentForVideo(frame, startTimeMs, (result) => {
|
|
704
|
+
var _a2, _b;
|
|
705
|
+
const segmentationTimeMs = performance.now() - startTimeMs;
|
|
706
|
+
this.segmentationResults = result;
|
|
707
|
+
this.drawFrame(frame);
|
|
708
|
+
if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
|
|
709
|
+
const newFrame = new VideoFrame(this.canvas, {
|
|
710
|
+
timestamp: frame.timestamp || Date.now()
|
|
711
|
+
});
|
|
712
|
+
const filterTimeMs = performance.now() - startTimeMs - segmentationTimeMs;
|
|
713
|
+
const stats = {
|
|
714
|
+
processingTimeMs: performance.now() - startTimeMs,
|
|
715
|
+
segmentationTimeMs,
|
|
716
|
+
filterTimeMs
|
|
717
|
+
};
|
|
718
|
+
(_b = (_a2 = this.options).onFrameProcessed) == null ? void 0 : _b.call(_a2, stats);
|
|
719
|
+
controller.enqueue(newFrame);
|
|
720
|
+
} else {
|
|
721
|
+
controller.enqueue(frame);
|
|
722
|
+
}
|
|
723
|
+
frame.close();
|
|
198
724
|
});
|
|
199
|
-
|
|
200
|
-
|
|
725
|
+
} catch (e) {
|
|
726
|
+
console.error("Error while processing frame: ", e);
|
|
201
727
|
frame == null ? void 0 : frame.close();
|
|
202
728
|
}
|
|
203
729
|
}
|
|
204
730
|
async update(opts) {
|
|
205
|
-
|
|
731
|
+
var _a;
|
|
732
|
+
this.options = { ...this.options, ...opts };
|
|
206
733
|
if (opts.blurRadius) {
|
|
207
|
-
this.
|
|
734
|
+
(_a = this.gl) == null ? void 0 : _a.setBlurRadius(opts.blurRadius);
|
|
208
735
|
} else if (opts.imagePath) {
|
|
209
736
|
await this.loadBackground(opts.imagePath);
|
|
210
737
|
}
|
|
211
738
|
}
|
|
212
|
-
async
|
|
213
|
-
|
|
214
|
-
if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo)
|
|
215
|
-
return;
|
|
216
|
-
if (((_a = this.segmentationResults) == null ? void 0 : _a.categoryMask) && this.segmentationResults.categoryMask.width > 0) {
|
|
217
|
-
this.ctx.globalCompositeOperation = "copy";
|
|
218
|
-
const bitmap = await maskToBitmap(
|
|
219
|
-
this.segmentationResults.categoryMask,
|
|
220
|
-
this.segmentationResults.categoryMask.width,
|
|
221
|
-
this.segmentationResults.categoryMask.height
|
|
222
|
-
);
|
|
223
|
-
this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);
|
|
224
|
-
this.ctx.filter = "none";
|
|
225
|
-
this.ctx.globalCompositeOperation = "source-in";
|
|
226
|
-
if (this.backgroundImage) {
|
|
227
|
-
this.ctx.drawImage(
|
|
228
|
-
this.backgroundImage,
|
|
229
|
-
0,
|
|
230
|
-
0,
|
|
231
|
-
this.backgroundImage.width,
|
|
232
|
-
this.backgroundImage.height,
|
|
233
|
-
0,
|
|
234
|
-
0,
|
|
235
|
-
this.canvas.width,
|
|
236
|
-
this.canvas.height
|
|
237
|
-
);
|
|
238
|
-
} else {
|
|
239
|
-
this.ctx.fillStyle = "#00FF00";
|
|
240
|
-
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
241
|
-
}
|
|
242
|
-
this.ctx.globalCompositeOperation = "destination-over";
|
|
243
|
-
}
|
|
244
|
-
this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);
|
|
245
|
-
}
|
|
246
|
-
async blurBackground(frame) {
|
|
247
|
-
var _a, _b, _c;
|
|
248
|
-
if (!this.ctx || !this.canvas || !((_b = (_a = this.segmentationResults) == null ? void 0 : _a.categoryMask) == null ? void 0 : _b.canvas) || !this.inputVideo) {
|
|
739
|
+
async drawFrame(frame) {
|
|
740
|
+
if (!this.canvas || !this.gl || !this.segmentationResults || !this.inputVideo)
|
|
249
741
|
return;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (((_c = this.segmentationResults) == null ? void 0 : _c.categoryMask) && this.segmentationResults.categoryMask.width > 0) {
|
|
254
|
-
const bitmap = await maskToBitmap(
|
|
255
|
-
this.segmentationResults.categoryMask,
|
|
256
|
-
this.segmentationResults.categoryMask.width,
|
|
257
|
-
this.segmentationResults.categoryMask.height
|
|
258
|
-
);
|
|
259
|
-
this.ctx.globalCompositeOperation = "copy";
|
|
260
|
-
this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);
|
|
261
|
-
this.ctx.filter = "none";
|
|
262
|
-
this.ctx.globalCompositeOperation = "source-out";
|
|
263
|
-
this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);
|
|
264
|
-
this.ctx.globalCompositeOperation = "destination-over";
|
|
265
|
-
this.ctx.filter = `blur(${this.blurRadius}px)`;
|
|
266
|
-
this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);
|
|
267
|
-
this.ctx.restore();
|
|
742
|
+
const mask = this.segmentationResults.categoryMask;
|
|
743
|
+
if (mask) {
|
|
744
|
+
this.gl.render(frame, mask);
|
|
268
745
|
}
|
|
269
746
|
}
|
|
270
747
|
};
|
|
271
|
-
function maskToBitmap(mask, videoWidth, videoHeight) {
|
|
272
|
-
const dataArray = new Uint8ClampedArray(videoWidth * videoHeight * 4);
|
|
273
|
-
const result = mask.getAsUint8Array();
|
|
274
|
-
for (let i = 0; i < result.length; i += 1) {
|
|
275
|
-
dataArray[i * 4] = result[i];
|
|
276
|
-
dataArray[i * 4 + 1] = result[i];
|
|
277
|
-
dataArray[i * 4 + 2] = result[i];
|
|
278
|
-
dataArray[i * 4 + 3] = result[i];
|
|
279
|
-
}
|
|
280
|
-
const dataNew = new ImageData(dataArray, videoWidth, videoHeight);
|
|
281
|
-
return createImageBitmap(dataNew);
|
|
282
|
-
}
|
|
283
748
|
|
|
284
749
|
// src/index.ts
|
|
285
|
-
var
|
|
286
|
-
|
|
750
|
+
var supportsBackgroundProcessors = () => BackgroundProcessor.isSupported && ProcessorWrapper.isSupported;
|
|
751
|
+
var supportsModernBackgroundProcessors = () => BackgroundProcessor.isSupported && ProcessorWrapper.hasModernApiSupport;
|
|
752
|
+
var BackgroundBlur = (blurRadius = 10, segmenterOptions, onFrameProcessed, processorOptions) => {
|
|
753
|
+
return BackgroundProcessor2(
|
|
754
|
+
{
|
|
755
|
+
blurRadius,
|
|
756
|
+
segmenterOptions,
|
|
757
|
+
onFrameProcessed,
|
|
758
|
+
...processorOptions
|
|
759
|
+
},
|
|
760
|
+
"background-blur"
|
|
761
|
+
);
|
|
287
762
|
};
|
|
288
|
-
var VirtualBackground = (imagePath, segmenterOptions) => {
|
|
289
|
-
return BackgroundProcessor2(
|
|
763
|
+
var VirtualBackground = (imagePath, segmenterOptions, onFrameProcessed, processorOptions) => {
|
|
764
|
+
return BackgroundProcessor2(
|
|
765
|
+
{
|
|
766
|
+
imagePath,
|
|
767
|
+
segmenterOptions,
|
|
768
|
+
onFrameProcessed,
|
|
769
|
+
...processorOptions
|
|
770
|
+
},
|
|
771
|
+
"virtual-background"
|
|
772
|
+
);
|
|
290
773
|
};
|
|
291
774
|
var BackgroundProcessor2 = (options, name = "background-processor") => {
|
|
292
|
-
const
|
|
775
|
+
const isTransformerSupported = BackgroundProcessor.isSupported;
|
|
776
|
+
const isProcessorSupported = ProcessorWrapper.isSupported;
|
|
777
|
+
if (!isTransformerSupported) {
|
|
778
|
+
throw new Error("Background transformer is not supported in this browser");
|
|
779
|
+
}
|
|
293
780
|
if (!isProcessorSupported) {
|
|
294
|
-
throw new Error(
|
|
781
|
+
throw new Error(
|
|
782
|
+
"Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser"
|
|
783
|
+
);
|
|
295
784
|
}
|
|
296
|
-
const
|
|
785
|
+
const { blurRadius, imagePath, segmenterOptions, onFrameProcessed, ...processorOpts } = options;
|
|
786
|
+
const processor = new ProcessorWrapper(
|
|
787
|
+
new BackgroundProcessor({ blurRadius, imagePath, segmenterOptions, onFrameProcessed }),
|
|
788
|
+
name,
|
|
789
|
+
processorOpts
|
|
790
|
+
);
|
|
297
791
|
return processor;
|
|
298
792
|
};
|
|
299
793
|
export {
|
|
@@ -302,6 +796,8 @@ export {
|
|
|
302
796
|
BackgroundProcessor as BackgroundTransformer,
|
|
303
797
|
ProcessorWrapper,
|
|
304
798
|
VideoTransformer,
|
|
305
|
-
VirtualBackground
|
|
799
|
+
VirtualBackground,
|
|
800
|
+
supportsBackgroundProcessors,
|
|
801
|
+
supportsModernBackgroundProcessors
|
|
306
802
|
};
|
|
307
803
|
//# sourceMappingURL=index.mjs.map
|