@livekit/track-processors 0.5.4 → 0.5.6
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/dist/index.js +230 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +230 -166
- package/dist/index.mjs.map +1 -1
- package/dist/src/ProcessorWrapper.d.ts +1 -1
- package/dist/src/transformers/BackgroundTransformer.d.ts +3 -1
- package/dist/src/transformers/VideoTransformer.d.ts +1 -1
- package/dist/src/transformers/types.d.ts +1 -1
- package/dist/src/utils.d.ts +2 -2
- package/dist/src/webgl/index.d.ts +3 -3
- package/dist/src/webgl/shader-programs/downSampler.d.ts +12 -0
- package/dist/src/webgl/utils.d.ts +4 -2
- package/package.json +1 -1
- package/src/ProcessorWrapper.ts +4 -4
- package/src/transformers/BackgroundTransformer.ts +42 -28
- package/src/transformers/VideoTransformer.ts +4 -3
- package/src/transformers/types.ts +1 -1
- package/src/utils.ts +11 -2
- package/src/webgl/index.ts +103 -48
- package/src/webgl/shader-programs/blurShader.ts +5 -37
- package/src/webgl/shader-programs/boxBlurShader.ts +4 -42
- package/src/webgl/shader-programs/compositeShader.ts +5 -43
- package/src/webgl/shader-programs/downSampler.ts +94 -0
- package/src/webgl/utils.ts +46 -6
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }// src/utils.ts
|
|
2
|
+
var supportsOffscreenCanvas = () => typeof OffscreenCanvas !== "undefined";
|
|
2
3
|
async function sleep(time) {
|
|
3
4
|
return new Promise((resolve) => setTimeout(resolve, time));
|
|
4
5
|
}
|
|
@@ -15,6 +16,15 @@ async function waitForTrackResolution(track) {
|
|
|
15
16
|
}
|
|
16
17
|
return { width: void 0, height: void 0 };
|
|
17
18
|
}
|
|
19
|
+
function createCanvas(width, height) {
|
|
20
|
+
if (supportsOffscreenCanvas()) {
|
|
21
|
+
return new OffscreenCanvas(width, height);
|
|
22
|
+
}
|
|
23
|
+
const canvas = document.createElement("canvas");
|
|
24
|
+
canvas.width = width;
|
|
25
|
+
canvas.height = height;
|
|
26
|
+
return canvas;
|
|
27
|
+
}
|
|
18
28
|
|
|
19
29
|
// src/ProcessorWrapper.ts
|
|
20
30
|
var ProcessorWrapper = class _ProcessorWrapper {
|
|
@@ -71,14 +81,14 @@ var ProcessorWrapper = class _ProcessorWrapper {
|
|
|
71
81
|
}
|
|
72
82
|
this.renderContext = this.displayCanvas.getContext("2d");
|
|
73
83
|
this.capturedStream = this.displayCanvas.captureStream();
|
|
74
|
-
this.canvas =
|
|
84
|
+
this.canvas = createCanvas(width != null ? width : 300, height != null ? height : 300);
|
|
75
85
|
} else {
|
|
76
86
|
this.processor = new MediaStreamTrackProcessor({ track: this.source });
|
|
77
87
|
this.trackGenerator = new MediaStreamTrackGenerator({
|
|
78
88
|
kind: "video",
|
|
79
89
|
signalTarget: this.source
|
|
80
90
|
});
|
|
81
|
-
this.canvas =
|
|
91
|
+
this.canvas = createCanvas(width != null ? width : 300, height != null ? height : 300);
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
async init(opts) {
|
|
@@ -264,6 +274,28 @@ function initTexture(gl, texIndex) {
|
|
|
264
274
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
265
275
|
return texture;
|
|
266
276
|
}
|
|
277
|
+
function createShader(gl, type, source) {
|
|
278
|
+
const shader = gl.createShader(type);
|
|
279
|
+
gl.shaderSource(shader, source);
|
|
280
|
+
gl.compileShader(shader);
|
|
281
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
282
|
+
console.error("Shader compile failed:", gl.getShaderInfoLog(shader));
|
|
283
|
+
gl.deleteShader(shader);
|
|
284
|
+
throw new Error("Shader compile failed");
|
|
285
|
+
}
|
|
286
|
+
return shader;
|
|
287
|
+
}
|
|
288
|
+
function createProgram(gl, vs, fs) {
|
|
289
|
+
const program = gl.createProgram();
|
|
290
|
+
gl.attachShader(program, vs);
|
|
291
|
+
gl.attachShader(program, fs);
|
|
292
|
+
gl.linkProgram(program);
|
|
293
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
294
|
+
console.error("Program link failed:", gl.getProgramInfoLog(program));
|
|
295
|
+
throw new Error("Program link failed");
|
|
296
|
+
}
|
|
297
|
+
return program;
|
|
298
|
+
}
|
|
267
299
|
function createFramebuffer(gl, texture, width, height) {
|
|
268
300
|
const framebuffer = gl.createFramebuffer();
|
|
269
301
|
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
@@ -307,11 +339,17 @@ async function resizeImageToCover(image, targetWidth, targetHeight) {
|
|
|
307
339
|
resizeQuality: "medium"
|
|
308
340
|
});
|
|
309
341
|
}
|
|
310
|
-
var emptyImageData
|
|
311
|
-
|
|
312
|
-
emptyImageData
|
|
313
|
-
emptyImageData
|
|
314
|
-
emptyImageData.data[
|
|
342
|
+
var emptyImageData;
|
|
343
|
+
function getEmptyImageData() {
|
|
344
|
+
if (!emptyImageData) {
|
|
345
|
+
emptyImageData = new ImageData(2, 2);
|
|
346
|
+
emptyImageData.data[0] = 0;
|
|
347
|
+
emptyImageData.data[1] = 0;
|
|
348
|
+
emptyImageData.data[2] = 0;
|
|
349
|
+
emptyImageData.data[3] = 0;
|
|
350
|
+
}
|
|
351
|
+
return emptyImageData;
|
|
352
|
+
}
|
|
315
353
|
var glsl = (source) => source;
|
|
316
354
|
|
|
317
355
|
// src/webgl/shader-programs/vertexShader.ts
|
|
@@ -328,7 +366,7 @@ var vertexShaderSource = (flipY = true) => `#version 300 es
|
|
|
328
366
|
|
|
329
367
|
// src/webgl/shader-programs/blurShader.ts
|
|
330
368
|
var blurFragmentShader = glsl`#version 300 es
|
|
331
|
-
precision
|
|
369
|
+
precision mediump float;
|
|
332
370
|
in vec2 texCoords;
|
|
333
371
|
uniform sampler2D u_texture;
|
|
334
372
|
uniform vec2 u_texelSize;
|
|
@@ -357,33 +395,9 @@ var blurFragmentShader = glsl`#version 300 es
|
|
|
357
395
|
}
|
|
358
396
|
`;
|
|
359
397
|
function createBlurProgram(gl) {
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
gl.shaderSource(blurFrag, blurFragmentShader);
|
|
365
|
-
gl.compileShader(blurFrag);
|
|
366
|
-
if (!gl.getShaderParameter(blurFrag, gl.COMPILE_STATUS)) {
|
|
367
|
-
const info = gl.getShaderInfoLog(blurFrag);
|
|
368
|
-
throw Error(`Failed to compile blur shader: ${info}`);
|
|
369
|
-
}
|
|
370
|
-
const blurVertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
371
|
-
if (!blurVertexShader) {
|
|
372
|
-
throw Error("cannot create blur vertex shader");
|
|
373
|
-
}
|
|
374
|
-
gl.shaderSource(blurVertexShader, vertexShaderSource());
|
|
375
|
-
gl.compileShader(blurVertexShader);
|
|
376
|
-
const blurProgram = gl.createProgram();
|
|
377
|
-
if (!blurProgram) {
|
|
378
|
-
throw Error("cannot create blur program");
|
|
379
|
-
}
|
|
380
|
-
gl.attachShader(blurProgram, blurVertexShader);
|
|
381
|
-
gl.attachShader(blurProgram, blurFrag);
|
|
382
|
-
gl.linkProgram(blurProgram);
|
|
383
|
-
if (!gl.getProgramParameter(blurProgram, gl.LINK_STATUS)) {
|
|
384
|
-
const info = gl.getProgramInfoLog(blurProgram);
|
|
385
|
-
throw Error(`Failed to link blur program: ${info}`);
|
|
386
|
-
}
|
|
398
|
+
const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
|
|
399
|
+
const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);
|
|
400
|
+
const blurProgram = createProgram(gl, blurVertexShader, blurFrag);
|
|
387
401
|
const blurUniforms = {
|
|
388
402
|
position: gl.getAttribLocation(blurProgram, "position"),
|
|
389
403
|
texture: gl.getUniformLocation(blurProgram, "u_texture"),
|
|
@@ -458,37 +472,9 @@ void main() {
|
|
|
458
472
|
}
|
|
459
473
|
`;
|
|
460
474
|
function createBoxBlurProgram(gl) {
|
|
461
|
-
const vertexShader =
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
gl.shaderSource(vertexShader, vertexShaderSource());
|
|
466
|
-
gl.compileShader(vertexShader);
|
|
467
|
-
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
468
|
-
const info = gl.getShaderInfoLog(vertexShader);
|
|
469
|
-
throw Error(`Failed to compile vertex shader: ${info}`);
|
|
470
|
-
}
|
|
471
|
-
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
472
|
-
if (!fragmentShader) {
|
|
473
|
-
throw Error("cannot create fragment shader");
|
|
474
|
-
}
|
|
475
|
-
gl.shaderSource(fragmentShader, boxBlurFragmentShader);
|
|
476
|
-
gl.compileShader(fragmentShader);
|
|
477
|
-
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
478
|
-
const info = gl.getShaderInfoLog(fragmentShader);
|
|
479
|
-
throw Error(`Failed to compile box blur shader: ${info}`);
|
|
480
|
-
}
|
|
481
|
-
const program = gl.createProgram();
|
|
482
|
-
if (!program) {
|
|
483
|
-
throw Error("cannot create box blur program");
|
|
484
|
-
}
|
|
485
|
-
gl.attachShader(program, vertexShader);
|
|
486
|
-
gl.attachShader(program, fragmentShader);
|
|
487
|
-
gl.linkProgram(program);
|
|
488
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
489
|
-
const info = gl.getProgramInfoLog(program);
|
|
490
|
-
throw Error(`Failed to link box blur program: ${info}`);
|
|
491
|
-
}
|
|
475
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
|
|
476
|
+
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);
|
|
477
|
+
const program = createProgram(gl, vertexShader, fragmentShader);
|
|
492
478
|
const uniforms = {
|
|
493
479
|
position: gl.getAttribLocation(program, "position"),
|
|
494
480
|
texture: gl.getUniformLocation(program, "u_texture"),
|
|
@@ -506,7 +492,7 @@ function createBoxBlurProgram(gl) {
|
|
|
506
492
|
|
|
507
493
|
// src/webgl/shader-programs/compositeShader.ts
|
|
508
494
|
var compositeFragmentShader = glsl`#version 300 es
|
|
509
|
-
precision
|
|
495
|
+
precision mediump float;
|
|
510
496
|
in vec2 texCoords;
|
|
511
497
|
uniform sampler2D background;
|
|
512
498
|
uniform sampler2D frame;
|
|
@@ -536,37 +522,9 @@ var compositeFragmentShader = glsl`#version 300 es
|
|
|
536
522
|
}
|
|
537
523
|
`;
|
|
538
524
|
function createCompositeProgram(gl) {
|
|
539
|
-
const vertexShader =
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
gl.shaderSource(vertexShader, vertexShaderSource());
|
|
544
|
-
gl.compileShader(vertexShader);
|
|
545
|
-
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
546
|
-
const info = gl.getShaderInfoLog(vertexShader);
|
|
547
|
-
throw Error(`Failed to compile vertex shader: ${info}`);
|
|
548
|
-
}
|
|
549
|
-
const compositeShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
550
|
-
if (!compositeShader) {
|
|
551
|
-
throw Error("cannot create fragment shader");
|
|
552
|
-
}
|
|
553
|
-
gl.shaderSource(compositeShader, compositeFragmentShader);
|
|
554
|
-
gl.compileShader(compositeShader);
|
|
555
|
-
if (!gl.getShaderParameter(compositeShader, gl.COMPILE_STATUS)) {
|
|
556
|
-
const info = gl.getShaderInfoLog(compositeShader);
|
|
557
|
-
throw Error(`Failed to compile composite shader: ${info}`);
|
|
558
|
-
}
|
|
559
|
-
const compositeProgram = gl.createProgram();
|
|
560
|
-
if (!compositeProgram) {
|
|
561
|
-
throw Error("cannot create composite program");
|
|
562
|
-
}
|
|
563
|
-
gl.attachShader(compositeProgram, vertexShader);
|
|
564
|
-
gl.attachShader(compositeProgram, compositeShader);
|
|
565
|
-
gl.linkProgram(compositeProgram);
|
|
566
|
-
if (!gl.getProgramParameter(compositeProgram, gl.LINK_STATUS)) {
|
|
567
|
-
const info = gl.getProgramInfoLog(compositeProgram);
|
|
568
|
-
throw Error(`Failed to link composite program: ${info}`);
|
|
569
|
-
}
|
|
525
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());
|
|
526
|
+
const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);
|
|
527
|
+
const compositeProgram = createProgram(gl, vertexShader, compositeShader);
|
|
570
528
|
const attribLocations = {
|
|
571
529
|
position: gl.getAttribLocation(compositeProgram, "position")
|
|
572
530
|
};
|
|
@@ -585,6 +543,63 @@ function createCompositeProgram(gl) {
|
|
|
585
543
|
};
|
|
586
544
|
}
|
|
587
545
|
|
|
546
|
+
// src/webgl/shader-programs/downSampler.ts
|
|
547
|
+
function createDownSampler(gl, width, height) {
|
|
548
|
+
const texture = gl.createTexture();
|
|
549
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
550
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
551
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
552
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
553
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
554
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
555
|
+
const framebuffer = gl.createFramebuffer();
|
|
556
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
557
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
558
|
+
const vertexSource = `
|
|
559
|
+
attribute vec2 position;
|
|
560
|
+
varying vec2 v_uv;
|
|
561
|
+
void main() {
|
|
562
|
+
v_uv = (position + 1.0) * 0.5;
|
|
563
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
564
|
+
}
|
|
565
|
+
`;
|
|
566
|
+
const fragmentSource = `
|
|
567
|
+
precision mediump float;
|
|
568
|
+
varying vec2 v_uv;
|
|
569
|
+
uniform sampler2D u_texture;
|
|
570
|
+
void main() {
|
|
571
|
+
gl_FragColor = texture2D(u_texture, v_uv);
|
|
572
|
+
}
|
|
573
|
+
`;
|
|
574
|
+
const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
|
|
575
|
+
const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
|
|
576
|
+
const program = createProgram(gl, vertShader, fragShader);
|
|
577
|
+
const uniforms = {
|
|
578
|
+
texture: gl.getUniformLocation(program, "u_texture"),
|
|
579
|
+
position: gl.getAttribLocation(program, "position")
|
|
580
|
+
};
|
|
581
|
+
return {
|
|
582
|
+
framebuffer,
|
|
583
|
+
texture,
|
|
584
|
+
program,
|
|
585
|
+
uniforms
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function applyDownsampling(gl, inputTexture, downSampler, vertexBuffer, width, height) {
|
|
589
|
+
gl.useProgram(downSampler.program);
|
|
590
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);
|
|
591
|
+
gl.viewport(0, 0, width, height);
|
|
592
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
593
|
+
gl.enableVertexAttribArray(downSampler.uniforms.position);
|
|
594
|
+
gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);
|
|
595
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
596
|
+
gl.bindTexture(gl.TEXTURE_2D, inputTexture);
|
|
597
|
+
gl.uniform1i(downSampler.uniforms.texture, 0);
|
|
598
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
599
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
600
|
+
return downSampler.texture;
|
|
601
|
+
}
|
|
602
|
+
|
|
588
603
|
// src/webgl/index.ts
|
|
589
604
|
var setupWebGL = (canvas) => {
|
|
590
605
|
const gl = canvas.getContext("webgl2", {
|
|
@@ -592,6 +607,8 @@ var setupWebGL = (canvas) => {
|
|
|
592
607
|
premultipliedAlpha: true
|
|
593
608
|
});
|
|
594
609
|
let blurRadius = null;
|
|
610
|
+
let maskBlurRadius = 8;
|
|
611
|
+
const downsampleFactor = 4;
|
|
595
612
|
if (!gl) {
|
|
596
613
|
console.error("Failed to create WebGL context");
|
|
597
614
|
return void 0;
|
|
@@ -620,27 +637,36 @@ var setupWebGL = (canvas) => {
|
|
|
620
637
|
}
|
|
621
638
|
let bgBlurTextures = [];
|
|
622
639
|
let bgBlurFrameBuffers = [];
|
|
623
|
-
let
|
|
624
|
-
let
|
|
640
|
+
let blurredMaskTexture = null;
|
|
641
|
+
let finalMaskTextures = [];
|
|
642
|
+
let readMaskIndex = 0;
|
|
643
|
+
let writeMaskIndex = 1;
|
|
625
644
|
bgBlurTextures.push(initTexture(gl, 3));
|
|
626
645
|
bgBlurTextures.push(initTexture(gl, 4));
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
createFramebuffer(gl, maskBlurTextures[0], canvas.width, canvas.height)
|
|
646
|
+
const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);
|
|
647
|
+
const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);
|
|
648
|
+
const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);
|
|
649
|
+
bgBlurFrameBuffers.push(
|
|
650
|
+
createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight)
|
|
633
651
|
);
|
|
634
|
-
|
|
635
|
-
createFramebuffer(gl,
|
|
652
|
+
bgBlurFrameBuffers.push(
|
|
653
|
+
createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight)
|
|
636
654
|
);
|
|
655
|
+
const tempMaskTexture = initTexture(gl, 5);
|
|
656
|
+
const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);
|
|
657
|
+
finalMaskTextures.push(initTexture(gl, 6));
|
|
658
|
+
finalMaskTextures.push(initTexture(gl, 7));
|
|
659
|
+
const finalMaskFrameBuffers = [
|
|
660
|
+
createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),
|
|
661
|
+
createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height)
|
|
662
|
+
];
|
|
637
663
|
gl.useProgram(compositeProgram);
|
|
638
664
|
gl.uniform1i(bgTextureLocation, 0);
|
|
639
665
|
gl.uniform1i(frameTextureLocation, 1);
|
|
640
666
|
gl.uniform1i(maskTextureLocation, 2);
|
|
641
|
-
let customBackgroundImage =
|
|
642
|
-
function
|
|
643
|
-
if (frame.codedWidth === 0 ||
|
|
667
|
+
let customBackgroundImage = getEmptyImageData();
|
|
668
|
+
function renderFrame(frame) {
|
|
669
|
+
if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {
|
|
644
670
|
return;
|
|
645
671
|
}
|
|
646
672
|
const width = frame.displayWidth;
|
|
@@ -650,11 +676,19 @@ var setupWebGL = (canvas) => {
|
|
|
650
676
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);
|
|
651
677
|
let backgroundTexture = bgTexture;
|
|
652
678
|
if (blurRadius) {
|
|
653
|
-
|
|
679
|
+
const downSampledFrameTexture = applyDownsampling(
|
|
654
680
|
gl,
|
|
655
681
|
frameTexture,
|
|
656
|
-
|
|
657
|
-
|
|
682
|
+
downSampler,
|
|
683
|
+
vertexBuffer,
|
|
684
|
+
bgBlurTextureWidth,
|
|
685
|
+
bgBlurTextureHeight
|
|
686
|
+
);
|
|
687
|
+
backgroundTexture = applyBlur(
|
|
688
|
+
gl,
|
|
689
|
+
downSampledFrameTexture,
|
|
690
|
+
bgBlurTextureWidth,
|
|
691
|
+
bgBlurTextureHeight,
|
|
658
692
|
blurRadius,
|
|
659
693
|
blurProgram,
|
|
660
694
|
blurUniforms,
|
|
@@ -668,19 +702,6 @@ var setupWebGL = (canvas) => {
|
|
|
668
702
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
|
|
669
703
|
backgroundTexture = bgTexture;
|
|
670
704
|
}
|
|
671
|
-
const blurredMaskTexture = applyBlur(
|
|
672
|
-
gl,
|
|
673
|
-
mask.getAsWebGLTexture(),
|
|
674
|
-
width,
|
|
675
|
-
height,
|
|
676
|
-
blurRadius || 1,
|
|
677
|
-
// Use a default blur radius if not set
|
|
678
|
-
boxBlurProgram,
|
|
679
|
-
boxBlurUniforms,
|
|
680
|
-
vertexBuffer,
|
|
681
|
-
maskBlurFrameBuffers,
|
|
682
|
-
maskBlurTextures
|
|
683
|
-
);
|
|
684
705
|
gl.viewport(0, 0, width, height);
|
|
685
706
|
gl.clearColor(1, 1, 1, 1);
|
|
686
707
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
@@ -695,13 +716,12 @@ var setupWebGL = (canvas) => {
|
|
|
695
716
|
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
|
|
696
717
|
gl.uniform1i(frameTextureLocation, 1);
|
|
697
718
|
gl.activeTexture(gl.TEXTURE2);
|
|
698
|
-
gl.bindTexture(gl.TEXTURE_2D,
|
|
719
|
+
gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);
|
|
699
720
|
gl.uniform1i(maskTextureLocation, 2);
|
|
700
721
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
701
|
-
mask.close();
|
|
702
722
|
}
|
|
703
723
|
async function setBackgroundImage(image) {
|
|
704
|
-
customBackgroundImage =
|
|
724
|
+
customBackgroundImage = getEmptyImageData();
|
|
705
725
|
if (image) {
|
|
706
726
|
try {
|
|
707
727
|
const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);
|
|
@@ -718,40 +738,67 @@ var setupWebGL = (canvas) => {
|
|
|
718
738
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);
|
|
719
739
|
}
|
|
720
740
|
function setBlurRadius(radius) {
|
|
721
|
-
blurRadius = radius;
|
|
741
|
+
blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null;
|
|
722
742
|
setBackgroundImage(null);
|
|
723
743
|
}
|
|
744
|
+
function updateMask(mask) {
|
|
745
|
+
const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];
|
|
746
|
+
const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];
|
|
747
|
+
applyBlur(
|
|
748
|
+
gl,
|
|
749
|
+
mask,
|
|
750
|
+
canvas.width,
|
|
751
|
+
canvas.height,
|
|
752
|
+
maskBlurRadius || 1,
|
|
753
|
+
boxBlurProgram,
|
|
754
|
+
boxBlurUniforms,
|
|
755
|
+
vertexBuffer,
|
|
756
|
+
tempFramebuffers,
|
|
757
|
+
tempTextures
|
|
758
|
+
);
|
|
759
|
+
readMaskIndex = writeMaskIndex;
|
|
760
|
+
writeMaskIndex = 1 - writeMaskIndex;
|
|
761
|
+
}
|
|
724
762
|
function cleanup() {
|
|
725
763
|
gl.deleteProgram(compositeProgram);
|
|
726
764
|
gl.deleteProgram(blurProgram);
|
|
727
765
|
gl.deleteProgram(boxBlurProgram);
|
|
728
766
|
gl.deleteTexture(bgTexture);
|
|
729
767
|
gl.deleteTexture(frameTexture);
|
|
768
|
+
gl.deleteTexture(tempMaskTexture);
|
|
769
|
+
gl.deleteFramebuffer(tempMaskFrameBuffer);
|
|
730
770
|
for (const texture of bgBlurTextures) {
|
|
731
771
|
gl.deleteTexture(texture);
|
|
732
772
|
}
|
|
733
773
|
for (const framebuffer of bgBlurFrameBuffers) {
|
|
734
774
|
gl.deleteFramebuffer(framebuffer);
|
|
735
775
|
}
|
|
736
|
-
for (const texture of
|
|
776
|
+
for (const texture of finalMaskTextures) {
|
|
737
777
|
gl.deleteTexture(texture);
|
|
738
778
|
}
|
|
739
|
-
for (const framebuffer of
|
|
779
|
+
for (const framebuffer of finalMaskFrameBuffers) {
|
|
740
780
|
gl.deleteFramebuffer(framebuffer);
|
|
741
781
|
}
|
|
742
782
|
gl.deleteBuffer(vertexBuffer);
|
|
783
|
+
if (blurredMaskTexture) {
|
|
784
|
+
gl.deleteTexture(blurredMaskTexture);
|
|
785
|
+
}
|
|
786
|
+
if (downSampler) {
|
|
787
|
+
gl.deleteTexture(downSampler.texture);
|
|
788
|
+
gl.deleteFramebuffer(downSampler.framebuffer);
|
|
789
|
+
gl.deleteProgram(downSampler.program);
|
|
790
|
+
}
|
|
743
791
|
if (customBackgroundImage) {
|
|
744
792
|
if (customBackgroundImage instanceof ImageBitmap) {
|
|
745
793
|
customBackgroundImage.close();
|
|
746
794
|
}
|
|
747
|
-
customBackgroundImage =
|
|
795
|
+
customBackgroundImage = getEmptyImageData();
|
|
748
796
|
}
|
|
749
797
|
bgBlurTextures = [];
|
|
750
798
|
bgBlurFrameBuffers = [];
|
|
751
|
-
|
|
752
|
-
maskBlurFrameBuffers = [];
|
|
799
|
+
finalMaskTextures = [];
|
|
753
800
|
}
|
|
754
|
-
return {
|
|
801
|
+
return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };
|
|
755
802
|
};
|
|
756
803
|
|
|
757
804
|
// src/transformers/VideoTransformer.ts
|
|
@@ -772,7 +819,7 @@ var VideoTransformer = class {
|
|
|
772
819
|
this.canvas = outputCanvas || null;
|
|
773
820
|
if (outputCanvas) {
|
|
774
821
|
this.gl = setupWebGL(
|
|
775
|
-
this.canvas ||
|
|
822
|
+
this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
|
|
776
823
|
);
|
|
777
824
|
}
|
|
778
825
|
this.inputVideo = inputVideo;
|
|
@@ -783,7 +830,7 @@ var VideoTransformer = class {
|
|
|
783
830
|
this.canvas = outputCanvas || null;
|
|
784
831
|
(_a = this.gl) == null ? void 0 : _a.cleanup();
|
|
785
832
|
this.gl = setupWebGL(
|
|
786
|
-
this.canvas ||
|
|
833
|
+
this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
|
|
787
834
|
);
|
|
788
835
|
this.inputVideo = inputVideo;
|
|
789
836
|
this.isDisabled = false;
|
|
@@ -802,6 +849,7 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
802
849
|
constructor(opts) {
|
|
803
850
|
super();
|
|
804
851
|
this.backgroundImage = null;
|
|
852
|
+
this.segmentationTimeMs = 0;
|
|
805
853
|
this.options = opts;
|
|
806
854
|
this.update(opts);
|
|
807
855
|
}
|
|
@@ -853,7 +901,7 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
853
901
|
(_a = this.gl) == null ? void 0 : _a.setBackgroundImage(imageData);
|
|
854
902
|
}
|
|
855
903
|
async transform(frame, controller) {
|
|
856
|
-
var _a;
|
|
904
|
+
var _a, _b;
|
|
857
905
|
try {
|
|
858
906
|
if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
|
|
859
907
|
console.debug("empty frame detected, ignoring");
|
|
@@ -863,37 +911,49 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
863
911
|
controller.enqueue(frame);
|
|
864
912
|
return;
|
|
865
913
|
}
|
|
914
|
+
const frameTimeMs = Date.now();
|
|
866
915
|
if (!this.canvas) {
|
|
867
916
|
throw TypeError("Canvas needs to be initialized first");
|
|
868
917
|
}
|
|
869
918
|
this.canvas.width = frame.displayWidth;
|
|
870
919
|
this.canvas.height = frame.displayHeight;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
920
|
+
const segmentationPromise = new Promise((resolve, reject) => {
|
|
921
|
+
var _a2;
|
|
922
|
+
try {
|
|
923
|
+
let segmentationStartTimeMs = performance.now();
|
|
924
|
+
(_a2 = this.imageSegmenter) == null ? void 0 : _a2.segmentForVideo(frame, segmentationStartTimeMs, (result) => {
|
|
925
|
+
this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;
|
|
926
|
+
this.segmentationResults = result;
|
|
927
|
+
this.updateMask(result.categoryMask);
|
|
928
|
+
result.close();
|
|
929
|
+
resolve();
|
|
880
930
|
});
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
processingTimeMs: performance.now() - startTimeMs,
|
|
884
|
-
segmentationTimeMs,
|
|
885
|
-
filterTimeMs
|
|
886
|
-
};
|
|
887
|
-
(_b = (_a2 = this.options).onFrameProcessed) == null ? void 0 : _b.call(_a2, stats);
|
|
888
|
-
controller.enqueue(newFrame);
|
|
889
|
-
} else {
|
|
890
|
-
controller.enqueue(frame);
|
|
931
|
+
} catch (e) {
|
|
932
|
+
reject(e);
|
|
891
933
|
}
|
|
892
|
-
frame.close();
|
|
893
934
|
});
|
|
935
|
+
const filterStartTimeMs = performance.now();
|
|
936
|
+
this.drawFrame(frame);
|
|
937
|
+
if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
|
|
938
|
+
const newFrame = new VideoFrame(this.canvas, {
|
|
939
|
+
timestamp: frame.timestamp || frameTimeMs
|
|
940
|
+
});
|
|
941
|
+
controller.enqueue(newFrame);
|
|
942
|
+
const filterTimeMs = performance.now() - filterStartTimeMs;
|
|
943
|
+
const stats = {
|
|
944
|
+
processingTimeMs: this.segmentationTimeMs + filterTimeMs,
|
|
945
|
+
segmentationTimeMs: this.segmentationTimeMs,
|
|
946
|
+
filterTimeMs
|
|
947
|
+
};
|
|
948
|
+
(_b = (_a = this.options).onFrameProcessed) == null ? void 0 : _b.call(_a, stats);
|
|
949
|
+
} else {
|
|
950
|
+
controller.enqueue(frame);
|
|
951
|
+
}
|
|
952
|
+
await segmentationPromise;
|
|
894
953
|
} catch (e) {
|
|
895
954
|
console.error("Error while processing frame: ", e);
|
|
896
|
-
|
|
955
|
+
} finally {
|
|
956
|
+
frame.close();
|
|
897
957
|
}
|
|
898
958
|
}
|
|
899
959
|
async update(opts) {
|
|
@@ -906,12 +966,16 @@ var BackgroundProcessor = class extends VideoTransformer {
|
|
|
906
966
|
}
|
|
907
967
|
}
|
|
908
968
|
async drawFrame(frame) {
|
|
909
|
-
|
|
969
|
+
var _a;
|
|
970
|
+
if (!this.gl)
|
|
910
971
|
return;
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
972
|
+
(_a = this.gl) == null ? void 0 : _a.renderFrame(frame);
|
|
973
|
+
}
|
|
974
|
+
async updateMask(mask) {
|
|
975
|
+
var _a;
|
|
976
|
+
if (!mask)
|
|
977
|
+
return;
|
|
978
|
+
(_a = this.gl) == null ? void 0 : _a.updateMask(mask.getAsWebGLTexture());
|
|
915
979
|
}
|
|
916
980
|
};
|
|
917
981
|
|