@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.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // 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 = new OffscreenCanvas(width != null ? width : 300, height != null ? height : 300);
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 = new OffscreenCanvas(width != null ? width : 300, height != null ? height : 300);
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 = new ImageData(2, 2);
311
- emptyImageData.data[0] = 0;
312
- emptyImageData.data[1] = 0;
313
- emptyImageData.data[2] = 0;
314
- emptyImageData.data[3] = 0;
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 highp float;
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 blurFrag = gl.createShader(gl.FRAGMENT_SHADER);
361
- if (!blurFrag) {
362
- throw Error("cannot create blur shader");
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 = gl.createShader(gl.VERTEX_SHADER);
462
- if (!vertexShader) {
463
- throw Error("cannot create vertex shader");
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 highp float;
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 = gl.createShader(gl.VERTEX_SHADER);
540
- if (!vertexShader) {
541
- throw Error("cannot create vertex shader");
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 maskBlurTextures = [];
624
- let maskBlurFrameBuffers = [];
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
- bgBlurFrameBuffers.push(createFramebuffer(gl, bgBlurTextures[0], canvas.width, canvas.height));
628
- bgBlurFrameBuffers.push(createFramebuffer(gl, bgBlurTextures[1], canvas.width, canvas.height));
629
- maskBlurTextures.push(initTexture(gl, 5));
630
- maskBlurTextures.push(initTexture(gl, 6));
631
- maskBlurFrameBuffers.push(
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
- maskBlurFrameBuffers.push(
635
- createFramebuffer(gl, maskBlurTextures[1], canvas.width, canvas.height)
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 = emptyImageData;
642
- function render(frame, mask) {
643
- if (frame.codedWidth === 0 || mask.width === 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
- backgroundTexture = applyBlur(
679
+ const downSampledFrameTexture = applyDownsampling(
654
680
  gl,
655
681
  frameTexture,
656
- width,
657
- height,
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, blurredMaskTexture);
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 = emptyImageData;
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 maskBlurTextures) {
776
+ for (const texture of finalMaskTextures) {
737
777
  gl.deleteTexture(texture);
738
778
  }
739
- for (const framebuffer of maskBlurFrameBuffers) {
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 = emptyImageData;
795
+ customBackgroundImage = getEmptyImageData();
748
796
  }
749
797
  bgBlurTextures = [];
750
798
  bgBlurFrameBuffers = [];
751
- maskBlurTextures = [];
752
- maskBlurFrameBuffers = [];
799
+ finalMaskTextures = [];
753
800
  }
754
- return { render, setBackgroundImage, setBlurRadius, cleanup };
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 || new OffscreenCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
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 || new OffscreenCanvas(inputVideo.videoWidth, inputVideo.videoHeight)
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
- let startTimeMs = performance.now();
872
- (_a = this.imageSegmenter) == null ? void 0 : _a.segmentForVideo(frame, startTimeMs, (result) => {
873
- var _a2, _b;
874
- const segmentationTimeMs = performance.now() - startTimeMs;
875
- this.segmentationResults = result;
876
- this.drawFrame(frame);
877
- if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
878
- const newFrame = new VideoFrame(this.canvas, {
879
- timestamp: frame.timestamp || Date.now()
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
- const filterTimeMs = performance.now() - startTimeMs - segmentationTimeMs;
882
- const stats = {
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
- frame == null ? void 0 : frame.close();
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
- if (!this.canvas || !this.gl || !this.segmentationResults || !this.inputVideo)
969
+ var _a;
970
+ if (!this.gl)
910
971
  return;
911
- const mask = this.segmentationResults.categoryMask;
912
- if (mask) {
913
- this.gl.render(frame, mask);
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