@livekit/track-processors 0.5.7 → 0.6.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 CHANGED
@@ -66,6 +66,7 @@ export const VirtualBackground = (imagePath: string) => {
66
66
  This repository includes a small example app built on [Vite](https://vitejs.dev/). Run it with:
67
67
 
68
68
  ```
69
- npm install
70
- npm run sample
69
+ # install pnpm: https://pnpm.io/installation
70
+ pnpm install
71
+ pnpm sample
71
72
  ```
package/dist/index.js CHANGED
@@ -114,7 +114,16 @@ var ProcessorWrapper = class _ProcessorWrapper {
114
114
  }
115
115
  const readableStream = this.processor.readable;
116
116
  const pipedStream = readableStream.pipeThrough(this.transformer.transformer);
117
- pipedStream.pipeTo(this.trackGenerator.writable).catch((e) => console.error("error when trying to pipe", e)).finally(() => this.destroy());
117
+ const symbol = Symbol("stream");
118
+ this.symbol = symbol;
119
+ pipedStream.pipeTo(this.trackGenerator.writable).then(() => this.destroy(symbol)).catch((e) => {
120
+ if (e instanceof DOMException && e.name === "AbortError") {
121
+ console.log("stream processor path aborted");
122
+ } else {
123
+ console.error("error when trying to pipe", e);
124
+ this.destroy(symbol);
125
+ }
126
+ });
118
127
  this.processedTrack = this.trackGenerator;
119
128
  }
120
129
  initFallbackPath() {
@@ -233,8 +242,11 @@ var ProcessorWrapper = class _ProcessorWrapper {
233
242
  async updateTransformerOptions(...options) {
234
243
  await this.transformer.update(options[0]);
235
244
  }
236
- async destroy() {
245
+ async destroy(symbol) {
237
246
  var _a, _b, _c, _d;
247
+ if (symbol && this.symbol !== symbol) {
248
+ return;
249
+ }
238
250
  if (this.useStreamFallback) {
239
251
  this.processingEnabled = false;
240
252
  if (this.animationFrameId) {
@@ -850,6 +862,7 @@ var BackgroundProcessor = class extends VideoTransformer {
850
862
  super();
851
863
  this.backgroundImage = null;
852
864
  this.segmentationTimeMs = 0;
865
+ this.isFirstFrame = true;
853
866
  this.options = opts;
854
867
  this.update(opts);
855
868
  }
@@ -887,6 +900,7 @@ var BackgroundProcessor = class extends VideoTransformer {
887
900
  await super.destroy();
888
901
  await ((_a = this.imageSegmenter) == null ? void 0 : _a.close());
889
902
  this.backgroundImage = null;
903
+ this.isFirstFrame = true;
890
904
  }
891
905
  async loadBackground(path) {
892
906
  var _a;
@@ -902,6 +916,7 @@ var BackgroundProcessor = class extends VideoTransformer {
902
916
  }
903
917
  async transform(frame, controller) {
904
918
  var _a, _b;
919
+ let enqueuedFrame = false;
905
920
  try {
906
921
  if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
907
922
  console.debug("empty frame detected, ignoring");
@@ -909,6 +924,7 @@ var BackgroundProcessor = class extends VideoTransformer {
909
924
  }
910
925
  if (this.isDisabled) {
911
926
  controller.enqueue(frame);
927
+ enqueuedFrame = true;
912
928
  return;
913
929
  }
914
930
  const frameTimeMs = Date.now();
@@ -917,6 +933,19 @@ var BackgroundProcessor = class extends VideoTransformer {
917
933
  }
918
934
  this.canvas.width = frame.displayWidth;
919
935
  this.canvas.height = frame.displayHeight;
936
+ if (this.isFirstFrame) {
937
+ controller.enqueue(frame.clone());
938
+ if (this.inputVideo) {
939
+ await new Promise((resolve) => {
940
+ this.inputVideo.requestVideoFrameCallback((_now, e) => {
941
+ const durationUntilFrameRenderedInMs = e.expectedDisplayTime - e.presentationTime;
942
+ setTimeout(resolve, durationUntilFrameRenderedInMs);
943
+ });
944
+ });
945
+ }
946
+ }
947
+ this.isFirstFrame = false;
948
+ const filterStartTimeMs = performance.now();
920
949
  const segmentationPromise = new Promise((resolve, reject) => {
921
950
  var _a2;
922
951
  try {
@@ -932,7 +961,6 @@ var BackgroundProcessor = class extends VideoTransformer {
932
961
  reject(e);
933
962
  }
934
963
  });
935
- const filterStartTimeMs = performance.now();
936
964
  this.drawFrame(frame);
937
965
  if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
938
966
  const newFrame = new VideoFrame(this.canvas, {
@@ -953,16 +981,19 @@ var BackgroundProcessor = class extends VideoTransformer {
953
981
  } catch (e) {
954
982
  console.error("Error while processing frame: ", e);
955
983
  } finally {
956
- frame.close();
984
+ if (!enqueuedFrame) {
985
+ frame.close();
986
+ }
957
987
  }
958
988
  }
959
989
  async update(opts) {
960
- var _a;
990
+ var _a, _b, _c;
961
991
  this.options = { ...this.options, ...opts };
962
- if (opts.blurRadius) {
963
- (_a = this.gl) == null ? void 0 : _a.setBlurRadius(opts.blurRadius);
964
- } else if (opts.imagePath) {
992
+ (_b = this.gl) == null ? void 0 : _b.setBlurRadius((_a = opts.blurRadius) != null ? _a : null);
993
+ if (opts.imagePath) {
965
994
  await this.loadBackground(opts.imagePath);
995
+ } else {
996
+ (_c = this.gl) == null ? void 0 : _c.setBackgroundImage(null);
966
997
  }
967
998
  }
968
999
  async drawFrame(frame) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/webgl/utils.ts","../src/webgl/shader-programs/vertexShader.ts","../src/webgl/shader-programs/blurShader.ts","../src/webgl/shader-programs/boxBlurShader.ts","../src/webgl/shader-programs/compositeShader.ts","../src/webgl/shader-programs/downSampler.ts","../src/webgl/index.ts","../src/transformers/VideoTransformer.ts","../src/index.ts"],"names":["_a","BackgroundProcessor"],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\nexport const supportsOffscreenCanvas = () => typeof OffscreenCanvas !== 'undefined';\n\nasync function sleep(time: number) {\n return new Promise((resolve) => setTimeout(resolve, time));\n}\n\nexport async function waitForTrackResolution(track: MediaStreamTrack) {\n const timeout = 500;\n\n // browsers report wrong initial resolution on iOS.\n // when slightly delaying the call to .getSettings(), the correct resolution is being reported\n await sleep(10);\n\n const started = Date.now();\n while (Date.now() - started < timeout) {\n const { width, height } = track.getSettings();\n if (width && height) {\n return { width, height };\n }\n await sleep(50);\n }\n return { width: undefined, height: undefined };\n}\n\nexport function createCanvas(width: number, height: number) {\n if (supportsOffscreenCanvas()) {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n","import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\nimport { createCanvas, waitForTrackResolution } from './utils';\n\nexport interface ProcessorWrapperOptions {\n /**\n * Maximum frame rate for fallback canvas.captureStream implementation\n * Default: 30\n */\n maxFps?: number;\n}\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n /**\n * Determines if the Processor is supported on the current browser\n */\n static get isSupported() {\n // Check for primary implementation support\n const hasStreamProcessor =\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined';\n\n // Check for fallback implementation support\n const hasFallbackSupport =\n typeof HTMLCanvasElement !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n 'captureStream' in HTMLCanvasElement.prototype;\n\n // We can work if either implementation is available\n return hasStreamProcessor || hasFallbackSupport;\n }\n\n /**\n * Determines if modern browser APIs are supported, which yield better performance\n */\n static get hasModernApiSupport() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n displayCanvas?: HTMLCanvasElement;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n // For tracking whether we're using the stream API fallback\n private useStreamFallback = false;\n\n // For fallback rendering with canvas.captureStream()\n private capturedStream?: MediaStream;\n\n private animationFrameId?: number;\n\n private renderContext?: CanvasRenderingContext2D;\n\n private frameCallback?: (frame: VideoFrame) => void;\n\n private processingEnabled = false;\n\n // FPS control for fallback implementation\n private maxFps: number;\n\n constructor(\n transformer: TrackTransformer<TransformerOptions>,\n name: string,\n options: ProcessorWrapperOptions = {},\n ) {\n this.name = name;\n this.transformer = transformer;\n this.maxFps = options.maxFps ?? 30;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n\n const { width, height } = await waitForTrackResolution(this.source);\n this.sourceDummy = opts.element;\n\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = height ?? 300;\n this.sourceDummy.width = width ?? 300;\n }\n\n this.useStreamFallback = !ProcessorWrapper.hasModernApiSupport;\n\n if (this.useStreamFallback) {\n // Create a visible canvas for the fallback implementation or use an existing one if provided\n const existingCanvas = document.querySelector(\n 'canvas[data-livekit-processor=\"' + this.name + '\"]',\n ) as HTMLCanvasElement;\n\n if (existingCanvas) {\n this.displayCanvas = existingCanvas;\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n } else {\n this.displayCanvas = document.createElement('canvas');\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n this.displayCanvas.style.display = 'none';\n this.displayCanvas.dataset.livekitProcessor = this.name;\n document.body.appendChild(this.displayCanvas);\n }\n\n this.renderContext = this.displayCanvas.getContext('2d')!;\n this.capturedStream = this.displayCanvas.captureStream();\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n } else {\n // Use MediaStreamTrackProcessor API\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n }\n }\n\n async init(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.setup(opts);\n\n if (!this.canvas) {\n throw new TypeError('Expected canvas to be defined after setup');\n }\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n\n if (this.useStreamFallback) {\n this.initFallbackPath();\n } else {\n this.initStreamProcessorPath();\n }\n }\n\n private initStreamProcessorPath() {\n if (!this.processor || !this.trackGenerator) {\n throw new TypeError(\n 'Expected processor and trackGenerator to be defined for stream processor path',\n );\n }\n\n const readableStream = this.processor.readable;\n const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n pipedStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n private initFallbackPath() {\n if (!this.capturedStream || !this.source || !this.canvas || !this.renderContext) {\n throw new TypeError('Missing required components for fallback implementation');\n }\n\n this.processedTrack = this.capturedStream.getVideoTracks()[0];\n this.processingEnabled = true;\n\n // Set up the frame callback for the transformer\n this.frameCallback = (frame: VideoFrame) => {\n if (!this.processingEnabled || !frame) {\n frame.close();\n return;\n }\n\n const controller = {\n enqueue: (processedFrame: VideoFrame) => {\n if (this.renderContext && this.displayCanvas) {\n // Draw the processed frame to the visible canvas\n this.renderContext.drawImage(\n processedFrame,\n 0,\n 0,\n this.displayCanvas.width,\n this.displayCanvas.height,\n );\n processedFrame.close();\n }\n },\n } as TransformStreamDefaultController<VideoFrame>;\n\n try {\n // Pass the frame through our transformer\n // @ts-ignore - The controller expects both VideoFrame & AudioData but we're only using VideoFrame\n this.transformer.transform(frame, controller);\n } catch (e) {\n console.error('Error in transform:', e);\n frame.close();\n }\n };\n\n // Start the rendering loop\n this.startRenderLoop();\n }\n\n private startRenderLoop() {\n if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {\n return;\n }\n\n // Store the last processed timestamp to avoid duplicate processing\n let lastVideoTimestamp = -1;\n let lastFrameTime = 0;\n const videoElement = this.sourceDummy as HTMLVideoElement;\n const minFrameInterval = 1000 / this.maxFps; // Minimum time between frames\n\n // Estimate the video's native frame rate\n let estimatedVideoFps = this.maxFps;\n let frameTimeHistory: number[] = [];\n let lastVideoTimeChange = 0;\n let frameCount = 0;\n let lastFpsLog = 0;\n\n const renderLoop = () => {\n if (\n !this.processingEnabled ||\n !this.sourceDummy ||\n !(this.sourceDummy instanceof HTMLVideoElement)\n ) {\n return;\n }\n\n if (this.sourceDummy.paused) {\n console.warn('Video is paused, trying to play');\n this.sourceDummy.play();\n return;\n }\n\n // Only process a new frame if the video has actually updated\n const videoTime = videoElement.currentTime;\n const now = performance.now();\n const timeSinceLastFrame = now - lastFrameTime;\n\n // Detect if video has a new frame\n const hasNewFrame = videoTime !== lastVideoTimestamp;\n\n // Update frame rate estimation if we have a new frame\n if (hasNewFrame) {\n if (lastVideoTimeChange > 0) {\n const timeBetweenFrames = now - lastVideoTimeChange;\n frameTimeHistory.push(timeBetweenFrames);\n\n // Keep a rolling window of the last 10 frame times\n if (frameTimeHistory.length > 10) {\n frameTimeHistory.shift();\n }\n\n // Calculate average frame interval\n if (frameTimeHistory.length > 2) {\n const avgFrameTime =\n frameTimeHistory.reduce((sum, time) => sum + time, 0) / frameTimeHistory.length;\n estimatedVideoFps = 1000 / avgFrameTime;\n\n // Log estimated FPS every 5 seconds in development environments\n // Use a simpler check that works in browsers without process.env\n const isDevelopment =\n (typeof window !== 'undefined' && window.location.hostname === 'localhost') ||\n window.location.hostname === '127.0.0.1';\n\n if (isDevelopment && now - lastFpsLog > 5000) {\n console.debug(\n `[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(\n 1,\n )}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`,\n );\n frameCount = 0;\n lastFpsLog = now;\n }\n }\n }\n lastVideoTimeChange = now;\n }\n\n // Determine if we should process this frame\n // We'll process if:\n // 1. The video has a new frame\n // 2. Enough time has passed since last frame (respecting maxFps)\n const timeThresholdMet = timeSinceLastFrame >= minFrameInterval;\n\n if (hasNewFrame && timeThresholdMet) {\n lastVideoTimestamp = videoTime;\n lastFrameTime = now;\n frameCount++;\n\n try {\n // Create a VideoFrame from the video element\n if (videoElement.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {\n const frame = new VideoFrame(videoElement);\n if (this.frameCallback) {\n this.frameCallback(frame);\n } else {\n frame.close();\n }\n }\n } catch (e) {\n console.error('Error in render loop:', e);\n }\n }\n this.animationFrameId = requestAnimationFrame(renderLoop);\n };\n\n this.animationFrameId = requestAnimationFrame(renderLoop);\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.destroy();\n await this.init(opts);\n }\n\n async restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>) {\n // @ts-ignore unclear why the restart method only accepts VideoTransformerInitOptions instead of either those or AudioTransformerInitOptions\n await this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n await this.transformer.update(options[0]);\n }\n\n async destroy() {\n if (this.useStreamFallback) {\n this.processingEnabled = false;\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n if (this.displayCanvas && this.displayCanvas.parentNode) {\n this.displayCanvas.parentNode.removeChild(this.displayCanvas);\n }\n this.capturedStream?.getTracks().forEach((track) => track.stop());\n } else {\n await this.processor?.writableControl?.close();\n this.trackGenerator?.stop();\n }\n await this.transformer.destroy();\n }\n}\n","import * as vision from '@mediapipe/tasks-vision';\nimport { dependencies } from '../../package.json';\nimport VideoTransformer from './VideoTransformer';\nimport { VideoTransformerInitOptions } from './types';\n\nexport type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport interface FrameProcessingStats {\n processingTimeMs: number;\n segmentationTimeMs: number;\n filterTimeMs: number;\n}\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n /** cannot be updated through the `update` method, needs a restart */\n segmenterOptions?: SegmenterOptions;\n /** cannot be updated through the `update` method, needs a restart */\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n /** called when a new frame is processed */\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return (\n typeof OffscreenCanvas !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n typeof createImageBitmap !== 'undefined' &&\n !!document.createElement('canvas').getContext('webgl2')\n );\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n options: BackgroundOptions;\n\n segmentationTimeMs: number = 0;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n this.update(opts);\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n // Initialize WebGL with appropriate options based on our current state\n\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\n this.options.assetPaths?.tasksVisionFileSet ??\n `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${dependencies['@mediapipe/tasks-vision']}/wasm`,\n );\n\n this.imageSegmenter = await vision.ImageSegmenter.createFromOptions(fileSet, {\n baseOptions: {\n modelAssetPath:\n this.options.assetPaths?.modelAssetPath ??\n 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n canvas: this.canvas,\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // Skip loading the image here if update already loaded the image below\n if (this.options?.imagePath && !this.backgroundImage) {\n await this.loadBackground(this.options.imagePath).catch((err) =>\n console.error('Error while loading processor background image: ', err),\n );\n }\n if (this.options.blurRadius) {\n this.gl?.setBlurRadius(this.options.blurRadius);\n }\n }\n\n async destroy() {\n await super.destroy();\n await this.imageSegmenter?.close();\n this.backgroundImage = null;\n }\n\n async loadBackground(path: string) {\n const img = new Image();\n\n await new Promise((resolve, reject) => {\n img.crossOrigin = 'Anonymous';\n img.onload = () => resolve(img);\n img.onerror = (err) => reject(err);\n img.src = path;\n });\n const imageData = await createImageBitmap(img);\n this.gl?.setBackgroundImage(imageData);\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {\n console.debug('empty frame detected, ignoring');\n return;\n }\n\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n const frameTimeMs = Date.now();\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n this.canvas.width = frame.displayWidth;\n this.canvas.height = frame.displayHeight;\n const segmentationPromise = new Promise<void>((resolve, reject) => {\n try {\n let segmentationStartTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(frame, segmentationStartTimeMs, (result) => {\n this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;\n this.segmentationResults = result;\n this.updateMask(result.categoryMask);\n result.close();\n resolve();\n });\n } catch (e) {\n reject(e);\n }\n });\n\n const filterStartTimeMs = performance.now();\n this.drawFrame(frame);\n if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || frameTimeMs,\n });\n controller.enqueue(newFrame);\n const filterTimeMs = performance.now() - filterStartTimeMs;\n const stats: FrameProcessingStats = {\n processingTimeMs: this.segmentationTimeMs + filterTimeMs,\n segmentationTimeMs: this.segmentationTimeMs,\n filterTimeMs,\n };\n this.options.onFrameProcessed?.(stats);\n } else {\n controller.enqueue(frame);\n }\n await segmentationPromise;\n } catch (e) {\n console.error('Error while processing frame: ', e);\n } finally {\n frame.close();\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = { ...this.options, ...opts };\n if (opts.blurRadius) {\n this.gl?.setBlurRadius(opts.blurRadius);\n } else if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n }\n }\n\n private async drawFrame(frame: VideoFrame) {\n if (!this.gl) return;\n this.gl?.renderFrame(frame);\n }\n\n private async updateMask(mask: vision.MPMask | undefined) {\n if (!mask) return;\n this.gl?.updateMask(mask.getAsWebGLTexture());\n }\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.5.7\",\n \"description\": \"LiveKit track processors\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"source\": \"src/index.ts\",\n \"types\": \"dist/src/index.d.ts\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/livekit/track-processors-js.git\"\n },\n \"author\": \"Lukas Seiler\",\n \"license\": \"Apache-2.0\",\n \"scripts\": {\n \"build\": \"tsup --onSuccess \\\"tsc --declaration --emitDeclarationOnly\\\"\",\n \"build-sample\": \"cd example && vite build\",\n \"lint\": \"eslint src\",\n \"release\": \"pnpm build && changeset publish\",\n \"test\": \"jest\",\n \"sample\": \"vite serve example --port 8080 --open\"\n },\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"dependencies\": {\n \"@mediapipe/tasks-vision\": \"0.10.14\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.1.0\",\n \"@types/dom-mediacapture-transform\": \"^0.1.9\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.2.1\",\n \"@types/offscreencanvas\": \"^2019.7.3\",\n \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n \"eslint\": \"8.39.0\",\n \"eslint-config-airbnb-typescript\": \"17.0.0\",\n \"eslint-config-prettier\": \"8.8.0\",\n \"eslint-plugin-ecmascript-compat\": \"^3.1.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^4.5.0\"\n },\n \"packageManager\": \"pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531\"\n}\n","/**\n * Initialize a WebGL texture\n */\nexport function initTexture(gl: WebGL2RenderingContext, texIndex: number) {\n const texRef = gl.TEXTURE0 + texIndex;\n gl.activeTexture(texRef);\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n return texture;\n}\n\nexport function createShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error('Shader compile failed:', gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n throw new Error('Shader compile failed');\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vs: WebGLShader,\n fs: WebGLShader,\n): WebGLProgram {\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('Program link failed:', gl.getProgramInfoLog(program));\n throw new Error('Program link failed');\n }\n return program;\n}\n\n/**\n * Create a WebGL framebuffer with the given texture as color attachment\n */\nexport function createFramebuffer(\n gl: WebGL2RenderingContext,\n texture: WebGLTexture,\n width: number,\n height: number,\n) {\n const framebuffer = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n\n // Set the texture as the color attachment\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Ensure texture dimensions match the provided width and height\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n\n // Check if framebuffer is complete\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error('Framebuffer not complete');\n }\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return framebuffer;\n}\n\n/**\n * Create a vertex buffer for a full-screen quad\n */\nexport function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {\n const vertexBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),\n gl.STATIC_DRAW,\n );\n return vertexBuffer;\n}\n\n/**\n * Resizes and crops an image to cover a target canvas while maintaining aspect ratio\n * @param image The source image\n * @param targetWidth The target width\n * @param targetHeight The target height\n * @returns A cropped and resized ImageBitmap\n */\nexport async function resizeImageToCover(\n image: ImageBitmap,\n targetWidth: number,\n targetHeight: number,\n): Promise<ImageBitmap> {\n // Calculate dimensions and crop for \"cover\" mode\n const imgAspect = image.width / image.height;\n const targetAspect = targetWidth / targetHeight;\n\n let sx = 0;\n let sy = 0;\n let sWidth = image.width;\n let sHeight = image.height;\n\n // For cover mode, we need to crop some parts of the image\n // to ensure it covers the canvas while maintaining aspect ratio\n if (imgAspect > targetAspect) {\n // Image is wider than target - crop the sides\n sWidth = Math.round(image.height * targetAspect);\n sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally\n } else if (imgAspect < targetAspect) {\n // Image is taller than target - crop the top/bottom\n sHeight = Math.round(image.width / targetAspect);\n sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically\n }\n\n // Create a new ImageBitmap with the cropped portion\n return createImageBitmap(image, sx, sy, sWidth, sHeight, {\n resizeWidth: targetWidth,\n resizeHeight: targetHeight,\n resizeQuality: 'medium',\n });\n}\n\nlet emptyImageData: ImageData | undefined;\n\nfunction getEmptyImageData() {\n if (!emptyImageData) {\n emptyImageData = new ImageData(2, 2);\n emptyImageData.data[0] = 0;\n emptyImageData.data[1] = 0;\n emptyImageData.data[2] = 0;\n emptyImageData.data[3] = 0;\n }\n\n return emptyImageData;\n}\n\nconst glsl = (source: any) => source;\n\nexport { getEmptyImageData, glsl };\n","// Vertex shader source\nexport const vertexShaderSource = (flipY: boolean = true) => `#version 300 es\n in vec2 position;\n out vec2 texCoords;\n\n void main() {\n texCoords = (position + 1.0) / 2.0;\n texCoords.y = ${flipY ? '1.0 - texCoords.y' : 'texCoords.y'};\n gl_Position = vec4(position, 0, 1.0);\n }\n`;\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Define the blur fragment shader\nexport const blurFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D u_texture;\n uniform vec2 u_texelSize;\n uniform vec2 u_direction;\n uniform float u_radius;\n out vec4 fragColor;\n\n void main() {\n float sigma = u_radius;\n float twoSigmaSq = 2.0 * sigma * sigma;\n float totalWeight = 0.0;\n vec3 result = vec3(0.0);\n const int MAX_SAMPLES = 16;\n int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));\n\n for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {\n float offset = float(i);\n if (abs(offset) > float(radius)) continue;\n float weight = exp(-(offset * offset) / twoSigmaSq);\n vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;\n result += texture(u_texture, sampleCoord).rgb * weight;\n totalWeight += weight;\n }\n\n fragColor = vec4(result / totalWeight, 1.0);\n }\n`;\n\nexport function createBlurProgram(gl: WebGL2RenderingContext) {\n const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);\n\n const blurProgram = createProgram(gl, blurVertexShader, blurFrag);\n\n // Get uniform locations\n const blurUniforms = {\n position: gl.getAttribLocation(blurProgram, 'position'),\n texture: gl.getUniformLocation(blurProgram, 'u_texture'),\n texelSize: gl.getUniformLocation(blurProgram, 'u_texelSize'),\n direction: gl.getUniformLocation(blurProgram, 'u_direction'),\n radius: gl.getUniformLocation(blurProgram, 'u_radius'),\n };\n\n return {\n program: blurProgram,\n shader: blurFrag,\n vertexShader: blurVertexShader,\n uniforms: blurUniforms,\n };\n}\n\nexport function applyBlur(\n gl: WebGL2RenderingContext,\n sourceTexture: WebGLTexture,\n width: number,\n height: number,\n blurRadius: number,\n blurProgram: WebGLProgram,\n blurUniforms: any,\n vertexBuffer: WebGLBuffer,\n processFramebuffers: WebGLFramebuffer[],\n processTextures: WebGLTexture[],\n) {\n gl.useProgram(blurProgram);\n\n // Set common attributes\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(blurUniforms.position);\n\n const texelWidth = 1.0 / width;\n const texelHeight = 1.0 / height;\n\n // First pass - horizontal blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, sourceTexture);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);\n gl.uniform2f(blurUniforms.direction, 1.0, 0.0); // Horizontal\n gl.uniform1f(blurUniforms.radius, blurRadius);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Second pass - vertical blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.direction, 0.0, 1.0); // Vertical\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Reset framebuffer\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return processTextures[1];\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\nexport const boxBlurFragmentShader = glsl`#version 300 es\nprecision mediump float;\n\nin vec2 texCoords;\n\nuniform sampler2D u_texture;\nuniform vec2 u_texelSize; // 1.0 / texture size\nuniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical\nuniform float u_radius; // blur radius in texels\n\nout vec4 fragColor;\n\nvoid main() {\n vec3 sum = vec3(0.0);\n float count = 0.0;\n\n // Limit radius to avoid excessive loop cost\n const int MAX_RADIUS = 16;\n int radius = int(min(float(MAX_RADIUS), u_radius));\n\n for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {\n if (abs(i) > radius) continue;\n\n vec2 offset = u_direction * u_texelSize * float(i);\n sum += texture(u_texture, texCoords + offset).rgb;\n count += 1.0;\n }\n\n fragColor = vec4(sum / count, 1.0);\n}\n`;\n\n/**\n * Create the box blur shader program\n */\nexport function createBoxBlurProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);\n\n const program = createProgram(gl, vertexShader, fragmentShader);\n\n // Get attribute and uniform locations\n const uniforms = {\n position: gl.getAttribLocation(program, 'position'),\n texture: gl.getUniformLocation(program, 'u_texture'),\n texelSize: gl.getUniformLocation(program, 'u_texelSize'),\n direction: gl.getUniformLocation(program, 'u_direction'),\n radius: gl.getUniformLocation(program, 'u_radius'),\n };\n\n return {\n program,\n vertexShader,\n fragmentShader,\n uniforms,\n };\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Fragment shader source for compositing\nexport const compositeFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D background;\n uniform sampler2D frame;\n uniform sampler2D mask;\n out vec4 fragColor;\n \n void main() {\n \n vec4 frameTex = texture(frame, texCoords);\n vec4 bgTex = texture(background, texCoords);\n\n float maskVal = texture(mask, texCoords).r;\n\n // Compute screen-space gradient to detect edge sharpness\n float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));\n\n float edgeSoftness = 2.0; // higher = softer\n \n // Create a smooth edge around binary transition\n float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);\n\n // Optional: preserve frame alpha, or override as fully opaque\n vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);\n \n fragColor = blended;\n \n }\n`;\n\n/**\n * Create the composite shader program\n */\nexport function createCompositeProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);\n\n const compositeProgram = createProgram(gl, vertexShader, compositeShader);\n\n // Get attribute and uniform locations\n const attribLocations = {\n position: gl.getAttribLocation(compositeProgram, 'position'),\n };\n\n const uniformLocations = {\n mask: gl.getUniformLocation(compositeProgram, 'mask')!,\n frame: gl.getUniformLocation(compositeProgram, 'frame')!,\n background: gl.getUniformLocation(compositeProgram, 'background')!,\n stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,\n };\n\n return {\n program: compositeProgram,\n vertexShader,\n fragmentShader: compositeShader,\n attribLocations,\n uniformLocations,\n };\n}\n","import { createProgram, createShader } from '../utils';\n\nexport function createDownSampler(\n gl: WebGL2RenderingContext,\n width: number,\n height: number,\n): {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n} {\n // Create texture\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n // Create framebuffer\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Create shader program for copying\n const vertexSource = `\n attribute vec2 position;\n varying vec2 v_uv;\n void main() {\n v_uv = (position + 1.0) * 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\n const fragmentSource = `\n precision mediump float;\n varying vec2 v_uv;\n uniform sampler2D u_texture;\n void main() {\n gl_FragColor = texture2D(u_texture, v_uv);\n }\n `;\n\n const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = createProgram(gl, vertShader, fragShader);\n\n const uniforms = {\n texture: gl.getUniformLocation(program, 'u_texture'),\n position: gl.getAttribLocation(program, 'position'),\n };\n\n return {\n framebuffer,\n texture,\n program,\n uniforms,\n };\n}\n\nexport function applyDownsampling(\n gl: WebGL2RenderingContext,\n inputTexture: WebGLTexture,\n downSampler: {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n },\n vertexBuffer: WebGLBuffer,\n width: number,\n height: number,\n): WebGLTexture {\n gl.useProgram(downSampler.program);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);\n gl.viewport(0, 0, width, height);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.enableVertexAttribArray(downSampler.uniforms.position);\n gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, inputTexture);\n gl.uniform1i(downSampler.uniforms.texture, 0);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return downSampler.texture;\n}\n","/**\n * WebGL setup for the mask processor\n * potential improvements:\n * - downsample the video texture in background blur scenario before applying the (gaussian) blur for better performance\n *\n */\nimport { applyBlur, createBlurProgram } from './shader-programs/blurShader';\nimport { createBoxBlurProgram } from './shader-programs/boxBlurShader';\nimport { createCompositeProgram } from './shader-programs/compositeShader';\nimport { applyDownsampling, createDownSampler } from './shader-programs/downSampler';\nimport {\n createFramebuffer,\n createVertexBuffer,\n getEmptyImageData,\n initTexture,\n resizeImageToCover,\n} from './utils';\n\nexport const setupWebGL = (canvas: OffscreenCanvas | HTMLCanvasElement) => {\n const gl = canvas.getContext('webgl2', {\n antialias: true,\n premultipliedAlpha: true,\n }) as WebGL2RenderingContext;\n\n let blurRadius: number | null = null;\n let maskBlurRadius: number | null = 8;\n const downsampleFactor = 4;\n\n if (!gl) {\n console.error('Failed to create WebGL context');\n return undefined;\n }\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n // Create the composite program\n const composite = createCompositeProgram(gl);\n const compositeProgram = composite.program;\n const positionLocation = composite.attribLocations.position;\n const {\n mask: maskTextureLocation,\n frame: frameTextureLocation,\n background: bgTextureLocation,\n } = composite.uniformLocations;\n\n // Create the blur program using the same vertex shader source\n const blur = createBlurProgram(gl);\n const blurProgram = blur.program;\n const blurUniforms = blur.uniforms;\n\n // Create the box blur program\n const boxBlur = createBoxBlurProgram(gl);\n const boxBlurProgram = boxBlur.program;\n const boxBlurUniforms = boxBlur.uniforms;\n\n const bgTexture = initTexture(gl, 0);\n const frameTexture = initTexture(gl, 1);\n const vertexBuffer = createVertexBuffer(gl);\n\n if (!vertexBuffer) {\n throw new Error('Failed to create vertex buffer');\n }\n\n // Create additional textures and framebuffers for processing\n let bgBlurTextures: WebGLTexture[] = [];\n let bgBlurFrameBuffers: WebGLFramebuffer[] = [];\n let blurredMaskTexture: WebGLTexture | null = null;\n\n // For double buffering the final mask\n let finalMaskTextures: WebGLTexture[] = [];\n let readMaskIndex = 0; // Index for renderFrame to read from\n let writeMaskIndex = 1; // Index for updateMask to write to\n\n // Create textures for background processing (blur)\n bgBlurTextures.push(initTexture(gl, 3)); // For blur pass 1\n bgBlurTextures.push(initTexture(gl, 4)); // For blur pass 2\n\n const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);\n const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);\n\n const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);\n\n // Create framebuffers for background processing\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n\n // Initialize texture for the first mask blur pass\n const tempMaskTexture = initTexture(gl, 5);\n const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);\n\n // Initialize two textures for double-buffering the final mask\n finalMaskTextures.push(initTexture(gl, 6)); // For reading in renderFrame\n finalMaskTextures.push(initTexture(gl, 7)); // For writing in updateMask\n\n // Create framebuffers for the final mask textures\n const finalMaskFrameBuffers = [\n createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),\n createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height),\n ];\n\n // Set up uniforms for the composite shader\n gl.useProgram(compositeProgram);\n gl.uniform1i(bgTextureLocation, 0);\n gl.uniform1i(frameTextureLocation, 1);\n gl.uniform1i(maskTextureLocation, 2);\n\n // Store custom background image\n let customBackgroundImage: ImageBitmap | ImageData = getEmptyImageData();\n\n function renderFrame(frame: VideoFrame) {\n if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {\n return;\n }\n\n const width = frame.displayWidth;\n const height = frame.displayHeight;\n\n // Prepare frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);\n\n // Apply blur if enabled (and no custom background is set)\n let backgroundTexture = bgTexture;\n\n if (blurRadius) {\n const downSampledFrameTexture = applyDownsampling(\n gl,\n frameTexture,\n downSampler,\n vertexBuffer!,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n );\n backgroundTexture = applyBlur(\n gl,\n downSampledFrameTexture,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n blurRadius,\n blurProgram,\n blurUniforms,\n vertexBuffer!,\n bgBlurFrameBuffers,\n bgBlurTextures,\n );\n } else {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n backgroundTexture = bgTexture;\n }\n\n // Render the final composite\n gl.viewport(0, 0, width, height);\n gl.clearColor(1.0, 1.0, 1.0, 1.0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n gl.useProgram(compositeProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(positionLocation);\n\n // Set background texture (either original, blurred or custom)\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);\n gl.uniform1i(bgTextureLocation, 0);\n\n // Set frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.uniform1i(frameTextureLocation, 1);\n\n // Set mask texture - always read from the current read index\n gl.activeTexture(gl.TEXTURE2);\n gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);\n gl.uniform1i(maskTextureLocation, 2);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n\n /**\n * Set or update the background image\n * @param image The background image to use, or null to clear\n */\n async function setBackgroundImage(image: ImageBitmap | null) {\n // Clear existing background\n customBackgroundImage = getEmptyImageData();\n\n if (image) {\n try {\n // Resize and crop the image to cover the canvas\n const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);\n\n // Store the cropped and resized image\n customBackgroundImage = croppedImage;\n } catch (error) {\n console.error(\n 'Error processing background image, falling back to black background:',\n error,\n );\n }\n }\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n }\n\n function setBlurRadius(radius: number | null) {\n blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null; // we are downsampling the blur texture, so decrease the radius here for better performance with a similar visual result\n setBackgroundImage(null);\n }\n\n function updateMask(mask: WebGLTexture) {\n // Use the existing applyBlur function to apply the first blur pass\n // The second blur pass will be written to finalMaskTextures[writeMaskIndex]\n\n // Create temporary arrays for the single blur operation\n const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];\n\n const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];\n\n // Apply the blur using the existing function\n applyBlur(\n gl,\n mask,\n canvas.width,\n canvas.height,\n maskBlurRadius || 1.0,\n boxBlurProgram,\n boxBlurUniforms,\n vertexBuffer!,\n tempFramebuffers,\n tempTextures,\n );\n\n // Swap indices for the next frame\n readMaskIndex = writeMaskIndex;\n writeMaskIndex = 1 - writeMaskIndex;\n }\n\n function cleanup() {\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(blurProgram);\n gl.deleteProgram(boxBlurProgram);\n gl.deleteTexture(bgTexture);\n gl.deleteTexture(frameTexture);\n gl.deleteTexture(tempMaskTexture);\n gl.deleteFramebuffer(tempMaskFrameBuffer);\n\n for (const texture of bgBlurTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of bgBlurFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n for (const texture of finalMaskTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of finalMaskFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n gl.deleteBuffer(vertexBuffer);\n\n if (blurredMaskTexture) {\n gl.deleteTexture(blurredMaskTexture);\n }\n\n if (downSampler) {\n gl.deleteTexture(downSampler.texture);\n gl.deleteFramebuffer(downSampler.framebuffer);\n gl.deleteProgram(downSampler.program);\n }\n\n // Release any ImageBitmap resources\n if (customBackgroundImage) {\n if (customBackgroundImage instanceof ImageBitmap) {\n customBackgroundImage.close();\n }\n customBackgroundImage = getEmptyImageData();\n }\n bgBlurTextures = [];\n bgBlurFrameBuffers = [];\n finalMaskTextures = [];\n }\n\n return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };\n};\n","import { createCanvas } from '../utils';\nimport { setupWebGL } from '../webgl/index';\nimport { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer<Options extends Record<string, unknown>>\n implements VideoTrackTransformer<Options>\n{\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n // ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\n\n gl?: ReturnType<typeof setupWebGL>;\n\n protected isDisabled?: Boolean = false;\n\n async init({\n outputCanvas,\n inputElement: inputVideo,\n }: VideoTransformerInitOptions): Promise<void> {\n if (!(inputVideo instanceof HTMLVideoElement)) {\n throw TypeError('Video transformer needs a HTMLVideoElement as input');\n }\n\n this.transformer = new TransformStream({\n transform: (frame, controller) => this.transform(frame, controller),\n });\n this.canvas = outputCanvas || null;\n if (outputCanvas) {\n // this.ctx = this.canvas?.getContext('2d') || undefined;\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.gl?.cleanup();\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.gl?.cleanup();\n this.gl = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n\n abstract update(options: Options): void;\n}\n","import ProcessorWrapper, { ProcessorWrapperOptions } from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n FrameProcessingStats,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport {\n ProcessorWrapper,\n type BackgroundOptions,\n type SegmenterOptions,\n BackgroundTransformer,\n type ProcessorWrapperOptions,\n};\n\n/**\n * Determines if the current browser supports background processors\n */\nexport const supportsBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.isSupported;\n\n/**\n * Determines if the current browser supports modern background processors, which yield better performance\n */\nexport const supportsModernBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.hasModernApiSupport;\n\nexport interface BackgroundProcessorOptions extends ProcessorWrapperOptions {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n}\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n blurRadius,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'background-blur',\n );\n};\n\nexport const VirtualBackground = (\n imagePath: string,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n imagePath,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'virtual-background',\n );\n};\n\nexport const BackgroundProcessor = (\n options: BackgroundProcessorOptions,\n name = 'background-processor',\n) => {\n const isTransformerSupported = BackgroundTransformer.isSupported;\n const isProcessorSupported = ProcessorWrapper.isSupported;\n\n if (!isTransformerSupported) {\n throw new Error('Background transformer is not supported in this browser');\n }\n\n if (!isProcessorSupported) {\n throw new Error(\n 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',\n );\n }\n\n // Extract transformer-specific options and processor options\n const {\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n ...processorOpts\n } = options;\n\n const transformer = new BackgroundTransformer({\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n });\n\n const processor = new ProcessorWrapper(transformer, name, processorOpts);\n\n return processor;\n};\n"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/webgl/utils.ts","../src/webgl/shader-programs/vertexShader.ts","../src/webgl/shader-programs/blurShader.ts","../src/webgl/shader-programs/boxBlurShader.ts","../src/webgl/shader-programs/compositeShader.ts","../src/webgl/shader-programs/downSampler.ts","../src/webgl/index.ts","../src/transformers/VideoTransformer.ts","../src/index.ts"],"names":["_a","BackgroundProcessor"],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\nexport const supportsOffscreenCanvas = () => typeof OffscreenCanvas !== 'undefined';\n\nasync function sleep(time: number) {\n return new Promise((resolve) => setTimeout(resolve, time));\n}\n\nexport async function waitForTrackResolution(track: MediaStreamTrack) {\n const timeout = 500;\n\n // browsers report wrong initial resolution on iOS.\n // when slightly delaying the call to .getSettings(), the correct resolution is being reported\n await sleep(10);\n\n const started = Date.now();\n while (Date.now() - started < timeout) {\n const { width, height } = track.getSettings();\n if (width && height) {\n return { width, height };\n }\n await sleep(50);\n }\n return { width: undefined, height: undefined };\n}\n\nexport function createCanvas(width: number, height: number) {\n if (supportsOffscreenCanvas()) {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n","import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\nimport { createCanvas, waitForTrackResolution } from './utils';\n\nexport interface ProcessorWrapperOptions {\n /**\n * Maximum frame rate for fallback canvas.captureStream implementation\n * Default: 30\n */\n maxFps?: number;\n}\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n /**\n * Determines if the Processor is supported on the current browser\n */\n static get isSupported() {\n // Check for primary implementation support\n const hasStreamProcessor =\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined';\n\n // Check for fallback implementation support\n const hasFallbackSupport =\n typeof HTMLCanvasElement !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n 'captureStream' in HTMLCanvasElement.prototype;\n\n // We can work if either implementation is available\n return hasStreamProcessor || hasFallbackSupport;\n }\n\n /**\n * Determines if modern browser APIs are supported, which yield better performance\n */\n static get hasModernApiSupport() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n displayCanvas?: HTMLCanvasElement;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n // For tracking whether we're using the stream API fallback\n private useStreamFallback = false;\n\n // For fallback rendering with canvas.captureStream()\n private capturedStream?: MediaStream;\n\n private animationFrameId?: number;\n\n private renderContext?: CanvasRenderingContext2D;\n\n private frameCallback?: (frame: VideoFrame) => void;\n\n private processingEnabled = false;\n\n // FPS control for fallback implementation\n private maxFps: number;\n\n private symbol?: Symbol;\n\n constructor(\n transformer: TrackTransformer<TransformerOptions>,\n name: string,\n options: ProcessorWrapperOptions = {},\n ) {\n this.name = name;\n this.transformer = transformer;\n this.maxFps = options.maxFps ?? 30;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n\n const { width, height } = await waitForTrackResolution(this.source);\n this.sourceDummy = opts.element;\n\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = height ?? 300;\n this.sourceDummy.width = width ?? 300;\n }\n\n this.useStreamFallback = !ProcessorWrapper.hasModernApiSupport;\n\n if (this.useStreamFallback) {\n // Create a visible canvas for the fallback implementation or use an existing one if provided\n const existingCanvas = document.querySelector(\n 'canvas[data-livekit-processor=\"' + this.name + '\"]',\n ) as HTMLCanvasElement;\n\n if (existingCanvas) {\n this.displayCanvas = existingCanvas;\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n } else {\n this.displayCanvas = document.createElement('canvas');\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n this.displayCanvas.style.display = 'none';\n this.displayCanvas.dataset.livekitProcessor = this.name;\n document.body.appendChild(this.displayCanvas);\n }\n\n this.renderContext = this.displayCanvas.getContext('2d')!;\n this.capturedStream = this.displayCanvas.captureStream();\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n } else {\n // Use MediaStreamTrackProcessor API\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n }\n }\n\n async init(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.setup(opts);\n\n if (!this.canvas) {\n throw new TypeError('Expected canvas to be defined after setup');\n }\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n\n if (this.useStreamFallback) {\n this.initFallbackPath();\n } else {\n this.initStreamProcessorPath();\n }\n }\n\n private initStreamProcessorPath() {\n if (!this.processor || !this.trackGenerator) {\n throw new TypeError(\n 'Expected processor and trackGenerator to be defined for stream processor path',\n );\n }\n\n const readableStream = this.processor.readable;\n const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n const symbol = Symbol('stream');\n this.symbol = symbol;\n\n pipedStream\n .pipeTo(this.trackGenerator.writable)\n // destroy processor if stream finishes\n .then(() => this.destroy(symbol))\n // destroy processor if stream errors - unless it's an abort error\n .catch((e) => {\n if (e instanceof DOMException && e.name === 'AbortError') {\n console.log('stream processor path aborted');\n } else {\n console.error('error when trying to pipe', e);\n this.destroy(symbol);\n }\n });\n\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n private initFallbackPath() {\n if (!this.capturedStream || !this.source || !this.canvas || !this.renderContext) {\n throw new TypeError('Missing required components for fallback implementation');\n }\n\n this.processedTrack = this.capturedStream.getVideoTracks()[0];\n this.processingEnabled = true;\n\n // Set up the frame callback for the transformer\n this.frameCallback = (frame: VideoFrame) => {\n if (!this.processingEnabled || !frame) {\n frame.close();\n return;\n }\n\n const controller = {\n enqueue: (processedFrame: VideoFrame) => {\n if (this.renderContext && this.displayCanvas) {\n // Draw the processed frame to the visible canvas\n this.renderContext.drawImage(\n processedFrame,\n 0,\n 0,\n this.displayCanvas.width,\n this.displayCanvas.height,\n );\n processedFrame.close();\n }\n },\n } as TransformStreamDefaultController<VideoFrame>;\n\n try {\n // Pass the frame through our transformer\n // @ts-ignore - The controller expects both VideoFrame & AudioData but we're only using VideoFrame\n this.transformer.transform(frame, controller);\n } catch (e) {\n console.error('Error in transform:', e);\n frame.close();\n }\n };\n\n // Start the rendering loop\n this.startRenderLoop();\n }\n\n private startRenderLoop() {\n if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {\n return;\n }\n\n // Store the last processed timestamp to avoid duplicate processing\n let lastVideoTimestamp = -1;\n let lastFrameTime = 0;\n const videoElement = this.sourceDummy as HTMLVideoElement;\n const minFrameInterval = 1000 / this.maxFps; // Minimum time between frames\n\n // Estimate the video's native frame rate\n let estimatedVideoFps = this.maxFps;\n let frameTimeHistory: number[] = [];\n let lastVideoTimeChange = 0;\n let frameCount = 0;\n let lastFpsLog = 0;\n\n const renderLoop = () => {\n if (\n !this.processingEnabled ||\n !this.sourceDummy ||\n !(this.sourceDummy instanceof HTMLVideoElement)\n ) {\n return;\n }\n\n if (this.sourceDummy.paused) {\n console.warn('Video is paused, trying to play');\n this.sourceDummy.play();\n return;\n }\n\n // Only process a new frame if the video has actually updated\n const videoTime = videoElement.currentTime;\n const now = performance.now();\n const timeSinceLastFrame = now - lastFrameTime;\n\n // Detect if video has a new frame\n const hasNewFrame = videoTime !== lastVideoTimestamp;\n\n // Update frame rate estimation if we have a new frame\n if (hasNewFrame) {\n if (lastVideoTimeChange > 0) {\n const timeBetweenFrames = now - lastVideoTimeChange;\n frameTimeHistory.push(timeBetweenFrames);\n\n // Keep a rolling window of the last 10 frame times\n if (frameTimeHistory.length > 10) {\n frameTimeHistory.shift();\n }\n\n // Calculate average frame interval\n if (frameTimeHistory.length > 2) {\n const avgFrameTime =\n frameTimeHistory.reduce((sum, time) => sum + time, 0) / frameTimeHistory.length;\n estimatedVideoFps = 1000 / avgFrameTime;\n\n // Log estimated FPS every 5 seconds in development environments\n // Use a simpler check that works in browsers without process.env\n const isDevelopment =\n (typeof window !== 'undefined' && window.location.hostname === 'localhost') ||\n window.location.hostname === '127.0.0.1';\n\n if (isDevelopment && now - lastFpsLog > 5000) {\n console.debug(\n `[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(\n 1,\n )}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`,\n );\n frameCount = 0;\n lastFpsLog = now;\n }\n }\n }\n lastVideoTimeChange = now;\n }\n\n // Determine if we should process this frame\n // We'll process if:\n // 1. The video has a new frame\n // 2. Enough time has passed since last frame (respecting maxFps)\n const timeThresholdMet = timeSinceLastFrame >= minFrameInterval;\n\n if (hasNewFrame && timeThresholdMet) {\n lastVideoTimestamp = videoTime;\n lastFrameTime = now;\n frameCount++;\n\n try {\n // Create a VideoFrame from the video element\n if (videoElement.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {\n const frame = new VideoFrame(videoElement);\n if (this.frameCallback) {\n this.frameCallback(frame);\n } else {\n frame.close();\n }\n }\n } catch (e) {\n console.error('Error in render loop:', e);\n }\n }\n this.animationFrameId = requestAnimationFrame(renderLoop);\n };\n\n this.animationFrameId = requestAnimationFrame(renderLoop);\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.destroy();\n await this.init(opts);\n }\n\n async restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>) {\n // @ts-ignore unclear why the restart method only accepts VideoTransformerInitOptions instead of either those or AudioTransformerInitOptions\n await this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n await this.transformer.update(options[0]);\n }\n\n async destroy(symbol?: Symbol) {\n if (symbol && this.symbol !== symbol) {\n // If the symbol is provided, we only destroy if it matches the current symbol\n return;\n }\n if (this.useStreamFallback) {\n this.processingEnabled = false;\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n if (this.displayCanvas && this.displayCanvas.parentNode) {\n this.displayCanvas.parentNode.removeChild(this.displayCanvas);\n }\n this.capturedStream?.getTracks().forEach((track) => track.stop());\n } else {\n await this.processor?.writableControl?.close();\n this.trackGenerator?.stop();\n }\n await this.transformer.destroy();\n }\n}\n","import * as vision from '@mediapipe/tasks-vision';\nimport { dependencies } from '../../package.json';\nimport VideoTransformer from './VideoTransformer';\nimport { VideoTransformerInitOptions } from './types';\n\nexport type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport interface FrameProcessingStats {\n processingTimeMs: number;\n segmentationTimeMs: number;\n filterTimeMs: number;\n}\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n /** cannot be updated through the `update` method, needs a restart */\n segmenterOptions?: SegmenterOptions;\n /** cannot be updated through the `update` method, needs a restart */\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n /** called when a new frame is processed */\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return (\n typeof OffscreenCanvas !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n typeof createImageBitmap !== 'undefined' &&\n !!document.createElement('canvas').getContext('webgl2')\n );\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n options: BackgroundOptions;\n\n segmentationTimeMs: number = 0;\n\n isFirstFrame = true;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n this.update(opts);\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n // Initialize WebGL with appropriate options based on our current state\n\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\n this.options.assetPaths?.tasksVisionFileSet ??\n `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${dependencies['@mediapipe/tasks-vision']}/wasm`,\n );\n\n this.imageSegmenter = await vision.ImageSegmenter.createFromOptions(fileSet, {\n baseOptions: {\n modelAssetPath:\n this.options.assetPaths?.modelAssetPath ??\n 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n canvas: this.canvas,\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // Skip loading the image here if update already loaded the image below\n if (this.options?.imagePath && !this.backgroundImage) {\n await this.loadBackground(this.options.imagePath).catch((err) =>\n console.error('Error while loading processor background image: ', err),\n );\n }\n if (this.options.blurRadius) {\n this.gl?.setBlurRadius(this.options.blurRadius);\n }\n }\n\n async destroy() {\n await super.destroy();\n await this.imageSegmenter?.close();\n this.backgroundImage = null;\n this.isFirstFrame = true;\n }\n\n async loadBackground(path: string) {\n const img = new Image();\n\n await new Promise((resolve, reject) => {\n img.crossOrigin = 'Anonymous';\n img.onload = () => resolve(img);\n img.onerror = (err) => reject(err);\n img.src = path;\n });\n const imageData = await createImageBitmap(img);\n this.gl?.setBackgroundImage(imageData);\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n let enqueuedFrame = false;\n try {\n if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {\n console.debug('empty frame detected, ignoring');\n return;\n }\n\n if (this.isDisabled) {\n controller.enqueue(frame);\n enqueuedFrame = true;\n return;\n }\n\n const frameTimeMs = Date.now();\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n this.canvas.width = frame.displayWidth;\n this.canvas.height = frame.displayHeight;\n\n // Render a copy of the first frame is rendered to the screen as soon as possible to act\n // as a less jarring initial state than a solid color while the synchronous work below\n // (segmentation + frame rendering) occurs.\n //\n // Ideally, these sync tasks could be offloaded to a webworker, but this is challenging\n // given WebGLTextures cannot be easily passed in a `postMessage`.\n if (this.isFirstFrame) {\n controller.enqueue(frame.clone());\n\n // Wait for the frame that was enqueued above to render before doing the sync work\n // below - otherwise, the sync work will take over the event loop and prevent the render\n // from occurring\n if (this.inputVideo) {\n await new Promise((resolve) => {\n this.inputVideo!.requestVideoFrameCallback((_now, e) => {\n const durationUntilFrameRenderedInMs = e.expectedDisplayTime - e.presentationTime;\n setTimeout(resolve, durationUntilFrameRenderedInMs);\n });\n });\n }\n }\n this.isFirstFrame = false;\n\n const filterStartTimeMs = performance.now();\n\n const segmentationPromise = new Promise<void>((resolve, reject) => {\n try {\n let segmentationStartTimeMs = performance.now();\n // NOTE: this.imageSegmenter?.segmentForVideo is synchronous, and blocks the event loop\n // for tens to ~100 ms! The promise wrapper is just used to flatten out the call hierarchy.\n this.imageSegmenter?.segmentForVideo(frame, segmentationStartTimeMs, (result) => {\n this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;\n this.segmentationResults = result;\n this.updateMask(result.categoryMask);\n result.close();\n resolve();\n });\n } catch (e) {\n reject(e);\n }\n });\n\n // NOTE: `this.drawFrame` is synchronous, and could take tens of ms to run!\n this.drawFrame(frame);\n if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || frameTimeMs,\n });\n controller.enqueue(newFrame);\n const filterTimeMs = performance.now() - filterStartTimeMs;\n const stats: FrameProcessingStats = {\n processingTimeMs: this.segmentationTimeMs + filterTimeMs,\n segmentationTimeMs: this.segmentationTimeMs,\n filterTimeMs,\n };\n this.options.onFrameProcessed?.(stats);\n } else {\n controller.enqueue(frame);\n }\n await segmentationPromise;\n } catch (e) {\n console.error('Error while processing frame: ', e);\n } finally {\n if (!enqueuedFrame) {\n frame.close();\n }\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = { ...this.options, ...opts };\n\n this.gl?.setBlurRadius(opts.blurRadius ?? null);\n if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n } else {\n this.gl?.setBackgroundImage(null);\n }\n }\n\n private async drawFrame(frame: VideoFrame) {\n if (!this.gl) return;\n this.gl?.renderFrame(frame);\n }\n\n private async updateMask(mask: vision.MPMask | undefined) {\n if (!mask) return;\n this.gl?.updateMask(mask.getAsWebGLTexture());\n }\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.6.0\",\n \"description\": \"LiveKit track processors\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"source\": \"src/index.ts\",\n \"types\": \"dist/src/index.d.ts\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/livekit/track-processors-js.git\"\n },\n \"author\": \"Lukas Seiler\",\n \"license\": \"Apache-2.0\",\n \"scripts\": {\n \"build\": \"tsup --onSuccess \\\"tsc --declaration --emitDeclarationOnly\\\"\",\n \"build-sample\": \"cd example && vite build\",\n \"lint\": \"eslint src\",\n \"release\": \"pnpm build && changeset publish\",\n \"test\": \"jest\",\n \"dev\": \"vite example -c vite.config.mjs --open\",\n \"sample\": \"pnpm run dev\"\n },\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"dependencies\": {\n \"@mediapipe/tasks-vision\": \"0.10.14\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.1.0\",\n \"@types/dom-mediacapture-transform\": \"^0.1.9\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.2.1\",\n \"@types/offscreencanvas\": \"^2019.7.3\",\n \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n \"eslint\": \"8.39.0\",\n \"eslint-config-airbnb-typescript\": \"17.0.0\",\n \"eslint-config-prettier\": \"8.8.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^7.0.2\"\n },\n \"packageManager\": \"pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531\"\n}\n","/**\n * Initialize a WebGL texture\n */\nexport function initTexture(gl: WebGL2RenderingContext, texIndex: number) {\n const texRef = gl.TEXTURE0 + texIndex;\n gl.activeTexture(texRef);\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n return texture;\n}\n\nexport function createShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error('Shader compile failed:', gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n throw new Error('Shader compile failed');\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vs: WebGLShader,\n fs: WebGLShader,\n): WebGLProgram {\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('Program link failed:', gl.getProgramInfoLog(program));\n throw new Error('Program link failed');\n }\n return program;\n}\n\n/**\n * Create a WebGL framebuffer with the given texture as color attachment\n */\nexport function createFramebuffer(\n gl: WebGL2RenderingContext,\n texture: WebGLTexture,\n width: number,\n height: number,\n) {\n const framebuffer = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n\n // Set the texture as the color attachment\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Ensure texture dimensions match the provided width and height\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n\n // Check if framebuffer is complete\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error('Framebuffer not complete');\n }\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return framebuffer;\n}\n\n/**\n * Create a vertex buffer for a full-screen quad\n */\nexport function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {\n const vertexBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),\n gl.STATIC_DRAW,\n );\n return vertexBuffer;\n}\n\n/**\n * Resizes and crops an image to cover a target canvas while maintaining aspect ratio\n * @param image The source image\n * @param targetWidth The target width\n * @param targetHeight The target height\n * @returns A cropped and resized ImageBitmap\n */\nexport async function resizeImageToCover(\n image: ImageBitmap,\n targetWidth: number,\n targetHeight: number,\n): Promise<ImageBitmap> {\n // Calculate dimensions and crop for \"cover\" mode\n const imgAspect = image.width / image.height;\n const targetAspect = targetWidth / targetHeight;\n\n let sx = 0;\n let sy = 0;\n let sWidth = image.width;\n let sHeight = image.height;\n\n // For cover mode, we need to crop some parts of the image\n // to ensure it covers the canvas while maintaining aspect ratio\n if (imgAspect > targetAspect) {\n // Image is wider than target - crop the sides\n sWidth = Math.round(image.height * targetAspect);\n sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally\n } else if (imgAspect < targetAspect) {\n // Image is taller than target - crop the top/bottom\n sHeight = Math.round(image.width / targetAspect);\n sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically\n }\n\n // Create a new ImageBitmap with the cropped portion\n return createImageBitmap(image, sx, sy, sWidth, sHeight, {\n resizeWidth: targetWidth,\n resizeHeight: targetHeight,\n resizeQuality: 'medium',\n });\n}\n\nlet emptyImageData: ImageData | undefined;\n\nfunction getEmptyImageData() {\n if (!emptyImageData) {\n emptyImageData = new ImageData(2, 2);\n emptyImageData.data[0] = 0;\n emptyImageData.data[1] = 0;\n emptyImageData.data[2] = 0;\n emptyImageData.data[3] = 0;\n }\n\n return emptyImageData;\n}\n\nconst glsl = (source: any) => source;\n\nexport { getEmptyImageData, glsl };\n","// Vertex shader source\nexport const vertexShaderSource = (flipY: boolean = true) => `#version 300 es\n in vec2 position;\n out vec2 texCoords;\n\n void main() {\n texCoords = (position + 1.0) / 2.0;\n texCoords.y = ${flipY ? '1.0 - texCoords.y' : 'texCoords.y'};\n gl_Position = vec4(position, 0, 1.0);\n }\n`;\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Define the blur fragment shader\nexport const blurFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D u_texture;\n uniform vec2 u_texelSize;\n uniform vec2 u_direction;\n uniform float u_radius;\n out vec4 fragColor;\n\n void main() {\n float sigma = u_radius;\n float twoSigmaSq = 2.0 * sigma * sigma;\n float totalWeight = 0.0;\n vec3 result = vec3(0.0);\n const int MAX_SAMPLES = 16;\n int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));\n\n for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {\n float offset = float(i);\n if (abs(offset) > float(radius)) continue;\n float weight = exp(-(offset * offset) / twoSigmaSq);\n vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;\n result += texture(u_texture, sampleCoord).rgb * weight;\n totalWeight += weight;\n }\n\n fragColor = vec4(result / totalWeight, 1.0);\n }\n`;\n\nexport function createBlurProgram(gl: WebGL2RenderingContext) {\n const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);\n\n const blurProgram = createProgram(gl, blurVertexShader, blurFrag);\n\n // Get uniform locations\n const blurUniforms = {\n position: gl.getAttribLocation(blurProgram, 'position'),\n texture: gl.getUniformLocation(blurProgram, 'u_texture'),\n texelSize: gl.getUniformLocation(blurProgram, 'u_texelSize'),\n direction: gl.getUniformLocation(blurProgram, 'u_direction'),\n radius: gl.getUniformLocation(blurProgram, 'u_radius'),\n };\n\n return {\n program: blurProgram,\n shader: blurFrag,\n vertexShader: blurVertexShader,\n uniforms: blurUniforms,\n };\n}\n\nexport function applyBlur(\n gl: WebGL2RenderingContext,\n sourceTexture: WebGLTexture,\n width: number,\n height: number,\n blurRadius: number,\n blurProgram: WebGLProgram,\n blurUniforms: any,\n vertexBuffer: WebGLBuffer,\n processFramebuffers: WebGLFramebuffer[],\n processTextures: WebGLTexture[],\n) {\n gl.useProgram(blurProgram);\n\n // Set common attributes\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(blurUniforms.position);\n\n const texelWidth = 1.0 / width;\n const texelHeight = 1.0 / height;\n\n // First pass - horizontal blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, sourceTexture);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);\n gl.uniform2f(blurUniforms.direction, 1.0, 0.0); // Horizontal\n gl.uniform1f(blurUniforms.radius, blurRadius);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Second pass - vertical blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.direction, 0.0, 1.0); // Vertical\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Reset framebuffer\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return processTextures[1];\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\nexport const boxBlurFragmentShader = glsl`#version 300 es\nprecision mediump float;\n\nin vec2 texCoords;\n\nuniform sampler2D u_texture;\nuniform vec2 u_texelSize; // 1.0 / texture size\nuniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical\nuniform float u_radius; // blur radius in texels\n\nout vec4 fragColor;\n\nvoid main() {\n vec3 sum = vec3(0.0);\n float count = 0.0;\n\n // Limit radius to avoid excessive loop cost\n const int MAX_RADIUS = 16;\n int radius = int(min(float(MAX_RADIUS), u_radius));\n\n for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {\n if (abs(i) > radius) continue;\n\n vec2 offset = u_direction * u_texelSize * float(i);\n sum += texture(u_texture, texCoords + offset).rgb;\n count += 1.0;\n }\n\n fragColor = vec4(sum / count, 1.0);\n}\n`;\n\n/**\n * Create the box blur shader program\n */\nexport function createBoxBlurProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);\n\n const program = createProgram(gl, vertexShader, fragmentShader);\n\n // Get attribute and uniform locations\n const uniforms = {\n position: gl.getAttribLocation(program, 'position'),\n texture: gl.getUniformLocation(program, 'u_texture'),\n texelSize: gl.getUniformLocation(program, 'u_texelSize'),\n direction: gl.getUniformLocation(program, 'u_direction'),\n radius: gl.getUniformLocation(program, 'u_radius'),\n };\n\n return {\n program,\n vertexShader,\n fragmentShader,\n uniforms,\n };\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Fragment shader source for compositing\nexport const compositeFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D background;\n uniform sampler2D frame;\n uniform sampler2D mask;\n out vec4 fragColor;\n \n void main() {\n \n vec4 frameTex = texture(frame, texCoords);\n vec4 bgTex = texture(background, texCoords);\n\n float maskVal = texture(mask, texCoords).r;\n\n // Compute screen-space gradient to detect edge sharpness\n float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));\n\n float edgeSoftness = 2.0; // higher = softer\n \n // Create a smooth edge around binary transition\n float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);\n\n // Optional: preserve frame alpha, or override as fully opaque\n vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);\n \n fragColor = blended;\n \n }\n`;\n\n/**\n * Create the composite shader program\n */\nexport function createCompositeProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);\n\n const compositeProgram = createProgram(gl, vertexShader, compositeShader);\n\n // Get attribute and uniform locations\n const attribLocations = {\n position: gl.getAttribLocation(compositeProgram, 'position'),\n };\n\n const uniformLocations = {\n mask: gl.getUniformLocation(compositeProgram, 'mask')!,\n frame: gl.getUniformLocation(compositeProgram, 'frame')!,\n background: gl.getUniformLocation(compositeProgram, 'background')!,\n stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,\n };\n\n return {\n program: compositeProgram,\n vertexShader,\n fragmentShader: compositeShader,\n attribLocations,\n uniformLocations,\n };\n}\n","import { createProgram, createShader } from '../utils';\n\nexport function createDownSampler(\n gl: WebGL2RenderingContext,\n width: number,\n height: number,\n): {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n} {\n // Create texture\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n // Create framebuffer\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Create shader program for copying\n const vertexSource = `\n attribute vec2 position;\n varying vec2 v_uv;\n void main() {\n v_uv = (position + 1.0) * 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\n const fragmentSource = `\n precision mediump float;\n varying vec2 v_uv;\n uniform sampler2D u_texture;\n void main() {\n gl_FragColor = texture2D(u_texture, v_uv);\n }\n `;\n\n const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = createProgram(gl, vertShader, fragShader);\n\n const uniforms = {\n texture: gl.getUniformLocation(program, 'u_texture'),\n position: gl.getAttribLocation(program, 'position'),\n };\n\n return {\n framebuffer,\n texture,\n program,\n uniforms,\n };\n}\n\nexport function applyDownsampling(\n gl: WebGL2RenderingContext,\n inputTexture: WebGLTexture,\n downSampler: {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n },\n vertexBuffer: WebGLBuffer,\n width: number,\n height: number,\n): WebGLTexture {\n gl.useProgram(downSampler.program);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);\n gl.viewport(0, 0, width, height);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.enableVertexAttribArray(downSampler.uniforms.position);\n gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, inputTexture);\n gl.uniform1i(downSampler.uniforms.texture, 0);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return downSampler.texture;\n}\n","/**\n * WebGL setup for the mask processor\n * potential improvements:\n * - downsample the video texture in background blur scenario before applying the (gaussian) blur for better performance\n *\n */\nimport { applyBlur, createBlurProgram } from './shader-programs/blurShader';\nimport { createBoxBlurProgram } from './shader-programs/boxBlurShader';\nimport { createCompositeProgram } from './shader-programs/compositeShader';\nimport { applyDownsampling, createDownSampler } from './shader-programs/downSampler';\nimport {\n createFramebuffer,\n createVertexBuffer,\n getEmptyImageData,\n initTexture,\n resizeImageToCover,\n} from './utils';\n\nexport const setupWebGL = (canvas: OffscreenCanvas | HTMLCanvasElement) => {\n const gl = canvas.getContext('webgl2', {\n antialias: true,\n premultipliedAlpha: true,\n }) as WebGL2RenderingContext;\n\n let blurRadius: number | null = null;\n let maskBlurRadius: number | null = 8;\n const downsampleFactor = 4;\n\n if (!gl) {\n console.error('Failed to create WebGL context');\n return undefined;\n }\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n // Create the composite program\n const composite = createCompositeProgram(gl);\n const compositeProgram = composite.program;\n const positionLocation = composite.attribLocations.position;\n const {\n mask: maskTextureLocation,\n frame: frameTextureLocation,\n background: bgTextureLocation,\n } = composite.uniformLocations;\n\n // Create the blur program using the same vertex shader source\n const blur = createBlurProgram(gl);\n const blurProgram = blur.program;\n const blurUniforms = blur.uniforms;\n\n // Create the box blur program\n const boxBlur = createBoxBlurProgram(gl);\n const boxBlurProgram = boxBlur.program;\n const boxBlurUniforms = boxBlur.uniforms;\n\n const bgTexture = initTexture(gl, 0);\n const frameTexture = initTexture(gl, 1);\n const vertexBuffer = createVertexBuffer(gl);\n\n if (!vertexBuffer) {\n throw new Error('Failed to create vertex buffer');\n }\n\n // Create additional textures and framebuffers for processing\n let bgBlurTextures: WebGLTexture[] = [];\n let bgBlurFrameBuffers: WebGLFramebuffer[] = [];\n let blurredMaskTexture: WebGLTexture | null = null;\n\n // For double buffering the final mask\n let finalMaskTextures: WebGLTexture[] = [];\n let readMaskIndex = 0; // Index for renderFrame to read from\n let writeMaskIndex = 1; // Index for updateMask to write to\n\n // Create textures for background processing (blur)\n bgBlurTextures.push(initTexture(gl, 3)); // For blur pass 1\n bgBlurTextures.push(initTexture(gl, 4)); // For blur pass 2\n\n const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);\n const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);\n\n const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);\n\n // Create framebuffers for background processing\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n\n // Initialize texture for the first mask blur pass\n const tempMaskTexture = initTexture(gl, 5);\n const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);\n\n // Initialize two textures for double-buffering the final mask\n finalMaskTextures.push(initTexture(gl, 6)); // For reading in renderFrame\n finalMaskTextures.push(initTexture(gl, 7)); // For writing in updateMask\n\n // Create framebuffers for the final mask textures\n const finalMaskFrameBuffers = [\n createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),\n createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height),\n ];\n\n // Set up uniforms for the composite shader\n gl.useProgram(compositeProgram);\n gl.uniform1i(bgTextureLocation, 0);\n gl.uniform1i(frameTextureLocation, 1);\n gl.uniform1i(maskTextureLocation, 2);\n\n // Store custom background image\n let customBackgroundImage: ImageBitmap | ImageData = getEmptyImageData();\n\n function renderFrame(frame: VideoFrame) {\n if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {\n return;\n }\n\n const width = frame.displayWidth;\n const height = frame.displayHeight;\n\n // Prepare frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);\n\n // Apply blur if enabled (and no custom background is set)\n let backgroundTexture = bgTexture;\n\n if (blurRadius) {\n const downSampledFrameTexture = applyDownsampling(\n gl,\n frameTexture,\n downSampler,\n vertexBuffer!,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n );\n backgroundTexture = applyBlur(\n gl,\n downSampledFrameTexture,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n blurRadius,\n blurProgram,\n blurUniforms,\n vertexBuffer!,\n bgBlurFrameBuffers,\n bgBlurTextures,\n );\n } else {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n backgroundTexture = bgTexture;\n }\n\n // Render the final composite\n gl.viewport(0, 0, width, height);\n gl.clearColor(1.0, 1.0, 1.0, 1.0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n gl.useProgram(compositeProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(positionLocation);\n\n // Set background texture (either original, blurred or custom)\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);\n gl.uniform1i(bgTextureLocation, 0);\n\n // Set frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.uniform1i(frameTextureLocation, 1);\n\n // Set mask texture - always read from the current read index\n gl.activeTexture(gl.TEXTURE2);\n gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);\n gl.uniform1i(maskTextureLocation, 2);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n\n /**\n * Set or update the background image\n * @param image The background image to use, or null to clear\n */\n async function setBackgroundImage(image: ImageBitmap | null) {\n // Clear existing background\n customBackgroundImage = getEmptyImageData();\n\n if (image) {\n try {\n // Resize and crop the image to cover the canvas\n const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);\n\n // Store the cropped and resized image\n customBackgroundImage = croppedImage;\n } catch (error) {\n console.error(\n 'Error processing background image, falling back to black background:',\n error,\n );\n }\n }\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n }\n\n function setBlurRadius(radius: number | null) {\n blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null; // we are downsampling the blur texture, so decrease the radius here for better performance with a similar visual result\n setBackgroundImage(null);\n }\n\n function updateMask(mask: WebGLTexture) {\n // Use the existing applyBlur function to apply the first blur pass\n // The second blur pass will be written to finalMaskTextures[writeMaskIndex]\n\n // Create temporary arrays for the single blur operation\n const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];\n\n const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];\n\n // Apply the blur using the existing function\n applyBlur(\n gl,\n mask,\n canvas.width,\n canvas.height,\n maskBlurRadius || 1.0,\n boxBlurProgram,\n boxBlurUniforms,\n vertexBuffer!,\n tempFramebuffers,\n tempTextures,\n );\n\n // Swap indices for the next frame\n readMaskIndex = writeMaskIndex;\n writeMaskIndex = 1 - writeMaskIndex;\n }\n\n function cleanup() {\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(blurProgram);\n gl.deleteProgram(boxBlurProgram);\n gl.deleteTexture(bgTexture);\n gl.deleteTexture(frameTexture);\n gl.deleteTexture(tempMaskTexture);\n gl.deleteFramebuffer(tempMaskFrameBuffer);\n\n for (const texture of bgBlurTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of bgBlurFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n for (const texture of finalMaskTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of finalMaskFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n gl.deleteBuffer(vertexBuffer);\n\n if (blurredMaskTexture) {\n gl.deleteTexture(blurredMaskTexture);\n }\n\n if (downSampler) {\n gl.deleteTexture(downSampler.texture);\n gl.deleteFramebuffer(downSampler.framebuffer);\n gl.deleteProgram(downSampler.program);\n }\n\n // Release any ImageBitmap resources\n if (customBackgroundImage) {\n if (customBackgroundImage instanceof ImageBitmap) {\n customBackgroundImage.close();\n }\n customBackgroundImage = getEmptyImageData();\n }\n bgBlurTextures = [];\n bgBlurFrameBuffers = [];\n finalMaskTextures = [];\n }\n\n return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };\n};\n","import { createCanvas } from '../utils';\nimport { setupWebGL } from '../webgl/index';\nimport { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer<Options extends Record<string, unknown>>\n implements VideoTrackTransformer<Options>\n{\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n // ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\n\n gl?: ReturnType<typeof setupWebGL>;\n\n protected isDisabled?: Boolean = false;\n\n async init({\n outputCanvas,\n inputElement: inputVideo,\n }: VideoTransformerInitOptions): Promise<void> {\n if (!(inputVideo instanceof HTMLVideoElement)) {\n throw TypeError('Video transformer needs a HTMLVideoElement as input');\n }\n\n this.transformer = new TransformStream({\n transform: (frame, controller) => this.transform(frame, controller),\n });\n this.canvas = outputCanvas || null;\n if (outputCanvas) {\n // this.ctx = this.canvas?.getContext('2d') || undefined;\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.gl?.cleanup();\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.gl?.cleanup();\n this.gl = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n\n abstract update(options: Options): void;\n}\n","import ProcessorWrapper, { ProcessorWrapperOptions } from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n FrameProcessingStats,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport {\n ProcessorWrapper,\n type BackgroundOptions,\n type SegmenterOptions,\n BackgroundTransformer,\n type ProcessorWrapperOptions,\n};\n\n/**\n * Determines if the current browser supports background processors\n */\nexport const supportsBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.isSupported;\n\n/**\n * Determines if the current browser supports modern background processors, which yield better performance\n */\nexport const supportsModernBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.hasModernApiSupport;\n\nexport interface BackgroundProcessorOptions extends ProcessorWrapperOptions {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n}\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n blurRadius,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'background-blur',\n );\n};\n\nexport const VirtualBackground = (\n imagePath: string,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n imagePath,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'virtual-background',\n );\n};\n\nexport const BackgroundProcessor = (\n options: BackgroundProcessorOptions,\n name = 'background-processor',\n) => {\n const isTransformerSupported = BackgroundTransformer.isSupported;\n const isProcessorSupported = ProcessorWrapper.isSupported;\n\n if (!isTransformerSupported) {\n throw new Error('Background transformer is not supported in this browser');\n }\n\n if (!isProcessorSupported) {\n throw new Error(\n 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',\n );\n }\n\n // Extract transformer-specific options and processor options\n const {\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n ...processorOpts\n } = options;\n\n const transformer = new BackgroundTransformer({\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n });\n\n const processor = new ProcessorWrapper(transformer, name, processorOpts);\n\n return processor;\n};\n"]}
package/dist/index.mjs CHANGED
@@ -114,7 +114,16 @@ var ProcessorWrapper = class _ProcessorWrapper {
114
114
  }
115
115
  const readableStream = this.processor.readable;
116
116
  const pipedStream = readableStream.pipeThrough(this.transformer.transformer);
117
- pipedStream.pipeTo(this.trackGenerator.writable).catch((e) => console.error("error when trying to pipe", e)).finally(() => this.destroy());
117
+ const symbol = Symbol("stream");
118
+ this.symbol = symbol;
119
+ pipedStream.pipeTo(this.trackGenerator.writable).then(() => this.destroy(symbol)).catch((e) => {
120
+ if (e instanceof DOMException && e.name === "AbortError") {
121
+ console.log("stream processor path aborted");
122
+ } else {
123
+ console.error("error when trying to pipe", e);
124
+ this.destroy(symbol);
125
+ }
126
+ });
118
127
  this.processedTrack = this.trackGenerator;
119
128
  }
120
129
  initFallbackPath() {
@@ -233,8 +242,11 @@ var ProcessorWrapper = class _ProcessorWrapper {
233
242
  async updateTransformerOptions(...options) {
234
243
  await this.transformer.update(options[0]);
235
244
  }
236
- async destroy() {
245
+ async destroy(symbol) {
237
246
  var _a, _b, _c, _d;
247
+ if (symbol && this.symbol !== symbol) {
248
+ return;
249
+ }
238
250
  if (this.useStreamFallback) {
239
251
  this.processingEnabled = false;
240
252
  if (this.animationFrameId) {
@@ -850,6 +862,7 @@ var BackgroundProcessor = class extends VideoTransformer {
850
862
  super();
851
863
  this.backgroundImage = null;
852
864
  this.segmentationTimeMs = 0;
865
+ this.isFirstFrame = true;
853
866
  this.options = opts;
854
867
  this.update(opts);
855
868
  }
@@ -887,6 +900,7 @@ var BackgroundProcessor = class extends VideoTransformer {
887
900
  await super.destroy();
888
901
  await ((_a = this.imageSegmenter) == null ? void 0 : _a.close());
889
902
  this.backgroundImage = null;
903
+ this.isFirstFrame = true;
890
904
  }
891
905
  async loadBackground(path) {
892
906
  var _a;
@@ -902,6 +916,7 @@ var BackgroundProcessor = class extends VideoTransformer {
902
916
  }
903
917
  async transform(frame, controller) {
904
918
  var _a, _b;
919
+ let enqueuedFrame = false;
905
920
  try {
906
921
  if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
907
922
  console.debug("empty frame detected, ignoring");
@@ -909,6 +924,7 @@ var BackgroundProcessor = class extends VideoTransformer {
909
924
  }
910
925
  if (this.isDisabled) {
911
926
  controller.enqueue(frame);
927
+ enqueuedFrame = true;
912
928
  return;
913
929
  }
914
930
  const frameTimeMs = Date.now();
@@ -917,6 +933,19 @@ var BackgroundProcessor = class extends VideoTransformer {
917
933
  }
918
934
  this.canvas.width = frame.displayWidth;
919
935
  this.canvas.height = frame.displayHeight;
936
+ if (this.isFirstFrame) {
937
+ controller.enqueue(frame.clone());
938
+ if (this.inputVideo) {
939
+ await new Promise((resolve) => {
940
+ this.inputVideo.requestVideoFrameCallback((_now, e) => {
941
+ const durationUntilFrameRenderedInMs = e.expectedDisplayTime - e.presentationTime;
942
+ setTimeout(resolve, durationUntilFrameRenderedInMs);
943
+ });
944
+ });
945
+ }
946
+ }
947
+ this.isFirstFrame = false;
948
+ const filterStartTimeMs = performance.now();
920
949
  const segmentationPromise = new Promise((resolve, reject) => {
921
950
  var _a2;
922
951
  try {
@@ -932,7 +961,6 @@ var BackgroundProcessor = class extends VideoTransformer {
932
961
  reject(e);
933
962
  }
934
963
  });
935
- const filterStartTimeMs = performance.now();
936
964
  this.drawFrame(frame);
937
965
  if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
938
966
  const newFrame = new VideoFrame(this.canvas, {
@@ -953,16 +981,19 @@ var BackgroundProcessor = class extends VideoTransformer {
953
981
  } catch (e) {
954
982
  console.error("Error while processing frame: ", e);
955
983
  } finally {
956
- frame.close();
984
+ if (!enqueuedFrame) {
985
+ frame.close();
986
+ }
957
987
  }
958
988
  }
959
989
  async update(opts) {
960
- var _a;
990
+ var _a, _b, _c;
961
991
  this.options = { ...this.options, ...opts };
962
- if (opts.blurRadius) {
963
- (_a = this.gl) == null ? void 0 : _a.setBlurRadius(opts.blurRadius);
964
- } else if (opts.imagePath) {
992
+ (_b = this.gl) == null ? void 0 : _b.setBlurRadius((_a = opts.blurRadius) != null ? _a : null);
993
+ if (opts.imagePath) {
965
994
  await this.loadBackground(opts.imagePath);
995
+ } else {
996
+ (_c = this.gl) == null ? void 0 : _c.setBackgroundImage(null);
966
997
  }
967
998
  }
968
999
  async drawFrame(frame) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/webgl/utils.ts","../src/webgl/shader-programs/vertexShader.ts","../src/webgl/shader-programs/blurShader.ts","../src/webgl/shader-programs/boxBlurShader.ts","../src/webgl/shader-programs/compositeShader.ts","../src/webgl/shader-programs/downSampler.ts","../src/webgl/index.ts","../src/transformers/VideoTransformer.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\nexport const supportsOffscreenCanvas = () => typeof OffscreenCanvas !== 'undefined';\n\nasync function sleep(time: number) {\n return new Promise((resolve) => setTimeout(resolve, time));\n}\n\nexport async function waitForTrackResolution(track: MediaStreamTrack) {\n const timeout = 500;\n\n // browsers report wrong initial resolution on iOS.\n // when slightly delaying the call to .getSettings(), the correct resolution is being reported\n await sleep(10);\n\n const started = Date.now();\n while (Date.now() - started < timeout) {\n const { width, height } = track.getSettings();\n if (width && height) {\n return { width, height };\n }\n await sleep(50);\n }\n return { width: undefined, height: undefined };\n}\n\nexport function createCanvas(width: number, height: number) {\n if (supportsOffscreenCanvas()) {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n","import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\nimport { createCanvas, waitForTrackResolution } from './utils';\n\nexport interface ProcessorWrapperOptions {\n /**\n * Maximum frame rate for fallback canvas.captureStream implementation\n * Default: 30\n */\n maxFps?: number;\n}\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n /**\n * Determines if the Processor is supported on the current browser\n */\n static get isSupported() {\n // Check for primary implementation support\n const hasStreamProcessor =\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined';\n\n // Check for fallback implementation support\n const hasFallbackSupport =\n typeof HTMLCanvasElement !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n 'captureStream' in HTMLCanvasElement.prototype;\n\n // We can work if either implementation is available\n return hasStreamProcessor || hasFallbackSupport;\n }\n\n /**\n * Determines if modern browser APIs are supported, which yield better performance\n */\n static get hasModernApiSupport() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n displayCanvas?: HTMLCanvasElement;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n // For tracking whether we're using the stream API fallback\n private useStreamFallback = false;\n\n // For fallback rendering with canvas.captureStream()\n private capturedStream?: MediaStream;\n\n private animationFrameId?: number;\n\n private renderContext?: CanvasRenderingContext2D;\n\n private frameCallback?: (frame: VideoFrame) => void;\n\n private processingEnabled = false;\n\n // FPS control for fallback implementation\n private maxFps: number;\n\n constructor(\n transformer: TrackTransformer<TransformerOptions>,\n name: string,\n options: ProcessorWrapperOptions = {},\n ) {\n this.name = name;\n this.transformer = transformer;\n this.maxFps = options.maxFps ?? 30;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n\n const { width, height } = await waitForTrackResolution(this.source);\n this.sourceDummy = opts.element;\n\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = height ?? 300;\n this.sourceDummy.width = width ?? 300;\n }\n\n this.useStreamFallback = !ProcessorWrapper.hasModernApiSupport;\n\n if (this.useStreamFallback) {\n // Create a visible canvas for the fallback implementation or use an existing one if provided\n const existingCanvas = document.querySelector(\n 'canvas[data-livekit-processor=\"' + this.name + '\"]',\n ) as HTMLCanvasElement;\n\n if (existingCanvas) {\n this.displayCanvas = existingCanvas;\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n } else {\n this.displayCanvas = document.createElement('canvas');\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n this.displayCanvas.style.display = 'none';\n this.displayCanvas.dataset.livekitProcessor = this.name;\n document.body.appendChild(this.displayCanvas);\n }\n\n this.renderContext = this.displayCanvas.getContext('2d')!;\n this.capturedStream = this.displayCanvas.captureStream();\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n } else {\n // Use MediaStreamTrackProcessor API\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n }\n }\n\n async init(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.setup(opts);\n\n if (!this.canvas) {\n throw new TypeError('Expected canvas to be defined after setup');\n }\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n\n if (this.useStreamFallback) {\n this.initFallbackPath();\n } else {\n this.initStreamProcessorPath();\n }\n }\n\n private initStreamProcessorPath() {\n if (!this.processor || !this.trackGenerator) {\n throw new TypeError(\n 'Expected processor and trackGenerator to be defined for stream processor path',\n );\n }\n\n const readableStream = this.processor.readable;\n const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n pipedStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n private initFallbackPath() {\n if (!this.capturedStream || !this.source || !this.canvas || !this.renderContext) {\n throw new TypeError('Missing required components for fallback implementation');\n }\n\n this.processedTrack = this.capturedStream.getVideoTracks()[0];\n this.processingEnabled = true;\n\n // Set up the frame callback for the transformer\n this.frameCallback = (frame: VideoFrame) => {\n if (!this.processingEnabled || !frame) {\n frame.close();\n return;\n }\n\n const controller = {\n enqueue: (processedFrame: VideoFrame) => {\n if (this.renderContext && this.displayCanvas) {\n // Draw the processed frame to the visible canvas\n this.renderContext.drawImage(\n processedFrame,\n 0,\n 0,\n this.displayCanvas.width,\n this.displayCanvas.height,\n );\n processedFrame.close();\n }\n },\n } as TransformStreamDefaultController<VideoFrame>;\n\n try {\n // Pass the frame through our transformer\n // @ts-ignore - The controller expects both VideoFrame & AudioData but we're only using VideoFrame\n this.transformer.transform(frame, controller);\n } catch (e) {\n console.error('Error in transform:', e);\n frame.close();\n }\n };\n\n // Start the rendering loop\n this.startRenderLoop();\n }\n\n private startRenderLoop() {\n if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {\n return;\n }\n\n // Store the last processed timestamp to avoid duplicate processing\n let lastVideoTimestamp = -1;\n let lastFrameTime = 0;\n const videoElement = this.sourceDummy as HTMLVideoElement;\n const minFrameInterval = 1000 / this.maxFps; // Minimum time between frames\n\n // Estimate the video's native frame rate\n let estimatedVideoFps = this.maxFps;\n let frameTimeHistory: number[] = [];\n let lastVideoTimeChange = 0;\n let frameCount = 0;\n let lastFpsLog = 0;\n\n const renderLoop = () => {\n if (\n !this.processingEnabled ||\n !this.sourceDummy ||\n !(this.sourceDummy instanceof HTMLVideoElement)\n ) {\n return;\n }\n\n if (this.sourceDummy.paused) {\n console.warn('Video is paused, trying to play');\n this.sourceDummy.play();\n return;\n }\n\n // Only process a new frame if the video has actually updated\n const videoTime = videoElement.currentTime;\n const now = performance.now();\n const timeSinceLastFrame = now - lastFrameTime;\n\n // Detect if video has a new frame\n const hasNewFrame = videoTime !== lastVideoTimestamp;\n\n // Update frame rate estimation if we have a new frame\n if (hasNewFrame) {\n if (lastVideoTimeChange > 0) {\n const timeBetweenFrames = now - lastVideoTimeChange;\n frameTimeHistory.push(timeBetweenFrames);\n\n // Keep a rolling window of the last 10 frame times\n if (frameTimeHistory.length > 10) {\n frameTimeHistory.shift();\n }\n\n // Calculate average frame interval\n if (frameTimeHistory.length > 2) {\n const avgFrameTime =\n frameTimeHistory.reduce((sum, time) => sum + time, 0) / frameTimeHistory.length;\n estimatedVideoFps = 1000 / avgFrameTime;\n\n // Log estimated FPS every 5 seconds in development environments\n // Use a simpler check that works in browsers without process.env\n const isDevelopment =\n (typeof window !== 'undefined' && window.location.hostname === 'localhost') ||\n window.location.hostname === '127.0.0.1';\n\n if (isDevelopment && now - lastFpsLog > 5000) {\n console.debug(\n `[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(\n 1,\n )}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`,\n );\n frameCount = 0;\n lastFpsLog = now;\n }\n }\n }\n lastVideoTimeChange = now;\n }\n\n // Determine if we should process this frame\n // We'll process if:\n // 1. The video has a new frame\n // 2. Enough time has passed since last frame (respecting maxFps)\n const timeThresholdMet = timeSinceLastFrame >= minFrameInterval;\n\n if (hasNewFrame && timeThresholdMet) {\n lastVideoTimestamp = videoTime;\n lastFrameTime = now;\n frameCount++;\n\n try {\n // Create a VideoFrame from the video element\n if (videoElement.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {\n const frame = new VideoFrame(videoElement);\n if (this.frameCallback) {\n this.frameCallback(frame);\n } else {\n frame.close();\n }\n }\n } catch (e) {\n console.error('Error in render loop:', e);\n }\n }\n this.animationFrameId = requestAnimationFrame(renderLoop);\n };\n\n this.animationFrameId = requestAnimationFrame(renderLoop);\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.destroy();\n await this.init(opts);\n }\n\n async restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>) {\n // @ts-ignore unclear why the restart method only accepts VideoTransformerInitOptions instead of either those or AudioTransformerInitOptions\n await this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n await this.transformer.update(options[0]);\n }\n\n async destroy() {\n if (this.useStreamFallback) {\n this.processingEnabled = false;\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n if (this.displayCanvas && this.displayCanvas.parentNode) {\n this.displayCanvas.parentNode.removeChild(this.displayCanvas);\n }\n this.capturedStream?.getTracks().forEach((track) => track.stop());\n } else {\n await this.processor?.writableControl?.close();\n this.trackGenerator?.stop();\n }\n await this.transformer.destroy();\n }\n}\n","import * as vision from '@mediapipe/tasks-vision';\nimport { dependencies } from '../../package.json';\nimport VideoTransformer from './VideoTransformer';\nimport { VideoTransformerInitOptions } from './types';\n\nexport type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport interface FrameProcessingStats {\n processingTimeMs: number;\n segmentationTimeMs: number;\n filterTimeMs: number;\n}\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n /** cannot be updated through the `update` method, needs a restart */\n segmenterOptions?: SegmenterOptions;\n /** cannot be updated through the `update` method, needs a restart */\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n /** called when a new frame is processed */\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return (\n typeof OffscreenCanvas !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n typeof createImageBitmap !== 'undefined' &&\n !!document.createElement('canvas').getContext('webgl2')\n );\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n options: BackgroundOptions;\n\n segmentationTimeMs: number = 0;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n this.update(opts);\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n // Initialize WebGL with appropriate options based on our current state\n\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\n this.options.assetPaths?.tasksVisionFileSet ??\n `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${dependencies['@mediapipe/tasks-vision']}/wasm`,\n );\n\n this.imageSegmenter = await vision.ImageSegmenter.createFromOptions(fileSet, {\n baseOptions: {\n modelAssetPath:\n this.options.assetPaths?.modelAssetPath ??\n 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n canvas: this.canvas,\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // Skip loading the image here if update already loaded the image below\n if (this.options?.imagePath && !this.backgroundImage) {\n await this.loadBackground(this.options.imagePath).catch((err) =>\n console.error('Error while loading processor background image: ', err),\n );\n }\n if (this.options.blurRadius) {\n this.gl?.setBlurRadius(this.options.blurRadius);\n }\n }\n\n async destroy() {\n await super.destroy();\n await this.imageSegmenter?.close();\n this.backgroundImage = null;\n }\n\n async loadBackground(path: string) {\n const img = new Image();\n\n await new Promise((resolve, reject) => {\n img.crossOrigin = 'Anonymous';\n img.onload = () => resolve(img);\n img.onerror = (err) => reject(err);\n img.src = path;\n });\n const imageData = await createImageBitmap(img);\n this.gl?.setBackgroundImage(imageData);\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {\n console.debug('empty frame detected, ignoring');\n return;\n }\n\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n const frameTimeMs = Date.now();\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n this.canvas.width = frame.displayWidth;\n this.canvas.height = frame.displayHeight;\n const segmentationPromise = new Promise<void>((resolve, reject) => {\n try {\n let segmentationStartTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(frame, segmentationStartTimeMs, (result) => {\n this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;\n this.segmentationResults = result;\n this.updateMask(result.categoryMask);\n result.close();\n resolve();\n });\n } catch (e) {\n reject(e);\n }\n });\n\n const filterStartTimeMs = performance.now();\n this.drawFrame(frame);\n if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || frameTimeMs,\n });\n controller.enqueue(newFrame);\n const filterTimeMs = performance.now() - filterStartTimeMs;\n const stats: FrameProcessingStats = {\n processingTimeMs: this.segmentationTimeMs + filterTimeMs,\n segmentationTimeMs: this.segmentationTimeMs,\n filterTimeMs,\n };\n this.options.onFrameProcessed?.(stats);\n } else {\n controller.enqueue(frame);\n }\n await segmentationPromise;\n } catch (e) {\n console.error('Error while processing frame: ', e);\n } finally {\n frame.close();\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = { ...this.options, ...opts };\n if (opts.blurRadius) {\n this.gl?.setBlurRadius(opts.blurRadius);\n } else if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n }\n }\n\n private async drawFrame(frame: VideoFrame) {\n if (!this.gl) return;\n this.gl?.renderFrame(frame);\n }\n\n private async updateMask(mask: vision.MPMask | undefined) {\n if (!mask) return;\n this.gl?.updateMask(mask.getAsWebGLTexture());\n }\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.5.7\",\n \"description\": \"LiveKit track processors\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"source\": \"src/index.ts\",\n \"types\": \"dist/src/index.d.ts\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/livekit/track-processors-js.git\"\n },\n \"author\": \"Lukas Seiler\",\n \"license\": \"Apache-2.0\",\n \"scripts\": {\n \"build\": \"tsup --onSuccess \\\"tsc --declaration --emitDeclarationOnly\\\"\",\n \"build-sample\": \"cd example && vite build\",\n \"lint\": \"eslint src\",\n \"release\": \"pnpm build && changeset publish\",\n \"test\": \"jest\",\n \"sample\": \"vite serve example --port 8080 --open\"\n },\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"dependencies\": {\n \"@mediapipe/tasks-vision\": \"0.10.14\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.1.0\",\n \"@types/dom-mediacapture-transform\": \"^0.1.9\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.2.1\",\n \"@types/offscreencanvas\": \"^2019.7.3\",\n \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n \"eslint\": \"8.39.0\",\n \"eslint-config-airbnb-typescript\": \"17.0.0\",\n \"eslint-config-prettier\": \"8.8.0\",\n \"eslint-plugin-ecmascript-compat\": \"^3.1.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^4.5.0\"\n },\n \"packageManager\": \"pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531\"\n}\n","/**\n * Initialize a WebGL texture\n */\nexport function initTexture(gl: WebGL2RenderingContext, texIndex: number) {\n const texRef = gl.TEXTURE0 + texIndex;\n gl.activeTexture(texRef);\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n return texture;\n}\n\nexport function createShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error('Shader compile failed:', gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n throw new Error('Shader compile failed');\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vs: WebGLShader,\n fs: WebGLShader,\n): WebGLProgram {\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('Program link failed:', gl.getProgramInfoLog(program));\n throw new Error('Program link failed');\n }\n return program;\n}\n\n/**\n * Create a WebGL framebuffer with the given texture as color attachment\n */\nexport function createFramebuffer(\n gl: WebGL2RenderingContext,\n texture: WebGLTexture,\n width: number,\n height: number,\n) {\n const framebuffer = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n\n // Set the texture as the color attachment\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Ensure texture dimensions match the provided width and height\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n\n // Check if framebuffer is complete\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error('Framebuffer not complete');\n }\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return framebuffer;\n}\n\n/**\n * Create a vertex buffer for a full-screen quad\n */\nexport function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {\n const vertexBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),\n gl.STATIC_DRAW,\n );\n return vertexBuffer;\n}\n\n/**\n * Resizes and crops an image to cover a target canvas while maintaining aspect ratio\n * @param image The source image\n * @param targetWidth The target width\n * @param targetHeight The target height\n * @returns A cropped and resized ImageBitmap\n */\nexport async function resizeImageToCover(\n image: ImageBitmap,\n targetWidth: number,\n targetHeight: number,\n): Promise<ImageBitmap> {\n // Calculate dimensions and crop for \"cover\" mode\n const imgAspect = image.width / image.height;\n const targetAspect = targetWidth / targetHeight;\n\n let sx = 0;\n let sy = 0;\n let sWidth = image.width;\n let sHeight = image.height;\n\n // For cover mode, we need to crop some parts of the image\n // to ensure it covers the canvas while maintaining aspect ratio\n if (imgAspect > targetAspect) {\n // Image is wider than target - crop the sides\n sWidth = Math.round(image.height * targetAspect);\n sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally\n } else if (imgAspect < targetAspect) {\n // Image is taller than target - crop the top/bottom\n sHeight = Math.round(image.width / targetAspect);\n sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically\n }\n\n // Create a new ImageBitmap with the cropped portion\n return createImageBitmap(image, sx, sy, sWidth, sHeight, {\n resizeWidth: targetWidth,\n resizeHeight: targetHeight,\n resizeQuality: 'medium',\n });\n}\n\nlet emptyImageData: ImageData | undefined;\n\nfunction getEmptyImageData() {\n if (!emptyImageData) {\n emptyImageData = new ImageData(2, 2);\n emptyImageData.data[0] = 0;\n emptyImageData.data[1] = 0;\n emptyImageData.data[2] = 0;\n emptyImageData.data[3] = 0;\n }\n\n return emptyImageData;\n}\n\nconst glsl = (source: any) => source;\n\nexport { getEmptyImageData, glsl };\n","// Vertex shader source\nexport const vertexShaderSource = (flipY: boolean = true) => `#version 300 es\n in vec2 position;\n out vec2 texCoords;\n\n void main() {\n texCoords = (position + 1.0) / 2.0;\n texCoords.y = ${flipY ? '1.0 - texCoords.y' : 'texCoords.y'};\n gl_Position = vec4(position, 0, 1.0);\n }\n`;\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Define the blur fragment shader\nexport const blurFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D u_texture;\n uniform vec2 u_texelSize;\n uniform vec2 u_direction;\n uniform float u_radius;\n out vec4 fragColor;\n\n void main() {\n float sigma = u_radius;\n float twoSigmaSq = 2.0 * sigma * sigma;\n float totalWeight = 0.0;\n vec3 result = vec3(0.0);\n const int MAX_SAMPLES = 16;\n int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));\n\n for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {\n float offset = float(i);\n if (abs(offset) > float(radius)) continue;\n float weight = exp(-(offset * offset) / twoSigmaSq);\n vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;\n result += texture(u_texture, sampleCoord).rgb * weight;\n totalWeight += weight;\n }\n\n fragColor = vec4(result / totalWeight, 1.0);\n }\n`;\n\nexport function createBlurProgram(gl: WebGL2RenderingContext) {\n const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);\n\n const blurProgram = createProgram(gl, blurVertexShader, blurFrag);\n\n // Get uniform locations\n const blurUniforms = {\n position: gl.getAttribLocation(blurProgram, 'position'),\n texture: gl.getUniformLocation(blurProgram, 'u_texture'),\n texelSize: gl.getUniformLocation(blurProgram, 'u_texelSize'),\n direction: gl.getUniformLocation(blurProgram, 'u_direction'),\n radius: gl.getUniformLocation(blurProgram, 'u_radius'),\n };\n\n return {\n program: blurProgram,\n shader: blurFrag,\n vertexShader: blurVertexShader,\n uniforms: blurUniforms,\n };\n}\n\nexport function applyBlur(\n gl: WebGL2RenderingContext,\n sourceTexture: WebGLTexture,\n width: number,\n height: number,\n blurRadius: number,\n blurProgram: WebGLProgram,\n blurUniforms: any,\n vertexBuffer: WebGLBuffer,\n processFramebuffers: WebGLFramebuffer[],\n processTextures: WebGLTexture[],\n) {\n gl.useProgram(blurProgram);\n\n // Set common attributes\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(blurUniforms.position);\n\n const texelWidth = 1.0 / width;\n const texelHeight = 1.0 / height;\n\n // First pass - horizontal blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, sourceTexture);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);\n gl.uniform2f(blurUniforms.direction, 1.0, 0.0); // Horizontal\n gl.uniform1f(blurUniforms.radius, blurRadius);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Second pass - vertical blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.direction, 0.0, 1.0); // Vertical\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Reset framebuffer\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return processTextures[1];\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\nexport const boxBlurFragmentShader = glsl`#version 300 es\nprecision mediump float;\n\nin vec2 texCoords;\n\nuniform sampler2D u_texture;\nuniform vec2 u_texelSize; // 1.0 / texture size\nuniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical\nuniform float u_radius; // blur radius in texels\n\nout vec4 fragColor;\n\nvoid main() {\n vec3 sum = vec3(0.0);\n float count = 0.0;\n\n // Limit radius to avoid excessive loop cost\n const int MAX_RADIUS = 16;\n int radius = int(min(float(MAX_RADIUS), u_radius));\n\n for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {\n if (abs(i) > radius) continue;\n\n vec2 offset = u_direction * u_texelSize * float(i);\n sum += texture(u_texture, texCoords + offset).rgb;\n count += 1.0;\n }\n\n fragColor = vec4(sum / count, 1.0);\n}\n`;\n\n/**\n * Create the box blur shader program\n */\nexport function createBoxBlurProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);\n\n const program = createProgram(gl, vertexShader, fragmentShader);\n\n // Get attribute and uniform locations\n const uniforms = {\n position: gl.getAttribLocation(program, 'position'),\n texture: gl.getUniformLocation(program, 'u_texture'),\n texelSize: gl.getUniformLocation(program, 'u_texelSize'),\n direction: gl.getUniformLocation(program, 'u_direction'),\n radius: gl.getUniformLocation(program, 'u_radius'),\n };\n\n return {\n program,\n vertexShader,\n fragmentShader,\n uniforms,\n };\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Fragment shader source for compositing\nexport const compositeFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D background;\n uniform sampler2D frame;\n uniform sampler2D mask;\n out vec4 fragColor;\n \n void main() {\n \n vec4 frameTex = texture(frame, texCoords);\n vec4 bgTex = texture(background, texCoords);\n\n float maskVal = texture(mask, texCoords).r;\n\n // Compute screen-space gradient to detect edge sharpness\n float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));\n\n float edgeSoftness = 2.0; // higher = softer\n \n // Create a smooth edge around binary transition\n float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);\n\n // Optional: preserve frame alpha, or override as fully opaque\n vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);\n \n fragColor = blended;\n \n }\n`;\n\n/**\n * Create the composite shader program\n */\nexport function createCompositeProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);\n\n const compositeProgram = createProgram(gl, vertexShader, compositeShader);\n\n // Get attribute and uniform locations\n const attribLocations = {\n position: gl.getAttribLocation(compositeProgram, 'position'),\n };\n\n const uniformLocations = {\n mask: gl.getUniformLocation(compositeProgram, 'mask')!,\n frame: gl.getUniformLocation(compositeProgram, 'frame')!,\n background: gl.getUniformLocation(compositeProgram, 'background')!,\n stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,\n };\n\n return {\n program: compositeProgram,\n vertexShader,\n fragmentShader: compositeShader,\n attribLocations,\n uniformLocations,\n };\n}\n","import { createProgram, createShader } from '../utils';\n\nexport function createDownSampler(\n gl: WebGL2RenderingContext,\n width: number,\n height: number,\n): {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n} {\n // Create texture\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n // Create framebuffer\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Create shader program for copying\n const vertexSource = `\n attribute vec2 position;\n varying vec2 v_uv;\n void main() {\n v_uv = (position + 1.0) * 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\n const fragmentSource = `\n precision mediump float;\n varying vec2 v_uv;\n uniform sampler2D u_texture;\n void main() {\n gl_FragColor = texture2D(u_texture, v_uv);\n }\n `;\n\n const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = createProgram(gl, vertShader, fragShader);\n\n const uniforms = {\n texture: gl.getUniformLocation(program, 'u_texture'),\n position: gl.getAttribLocation(program, 'position'),\n };\n\n return {\n framebuffer,\n texture,\n program,\n uniforms,\n };\n}\n\nexport function applyDownsampling(\n gl: WebGL2RenderingContext,\n inputTexture: WebGLTexture,\n downSampler: {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n },\n vertexBuffer: WebGLBuffer,\n width: number,\n height: number,\n): WebGLTexture {\n gl.useProgram(downSampler.program);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);\n gl.viewport(0, 0, width, height);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.enableVertexAttribArray(downSampler.uniforms.position);\n gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, inputTexture);\n gl.uniform1i(downSampler.uniforms.texture, 0);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return downSampler.texture;\n}\n","/**\n * WebGL setup for the mask processor\n * potential improvements:\n * - downsample the video texture in background blur scenario before applying the (gaussian) blur for better performance\n *\n */\nimport { applyBlur, createBlurProgram } from './shader-programs/blurShader';\nimport { createBoxBlurProgram } from './shader-programs/boxBlurShader';\nimport { createCompositeProgram } from './shader-programs/compositeShader';\nimport { applyDownsampling, createDownSampler } from './shader-programs/downSampler';\nimport {\n createFramebuffer,\n createVertexBuffer,\n getEmptyImageData,\n initTexture,\n resizeImageToCover,\n} from './utils';\n\nexport const setupWebGL = (canvas: OffscreenCanvas | HTMLCanvasElement) => {\n const gl = canvas.getContext('webgl2', {\n antialias: true,\n premultipliedAlpha: true,\n }) as WebGL2RenderingContext;\n\n let blurRadius: number | null = null;\n let maskBlurRadius: number | null = 8;\n const downsampleFactor = 4;\n\n if (!gl) {\n console.error('Failed to create WebGL context');\n return undefined;\n }\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n // Create the composite program\n const composite = createCompositeProgram(gl);\n const compositeProgram = composite.program;\n const positionLocation = composite.attribLocations.position;\n const {\n mask: maskTextureLocation,\n frame: frameTextureLocation,\n background: bgTextureLocation,\n } = composite.uniformLocations;\n\n // Create the blur program using the same vertex shader source\n const blur = createBlurProgram(gl);\n const blurProgram = blur.program;\n const blurUniforms = blur.uniforms;\n\n // Create the box blur program\n const boxBlur = createBoxBlurProgram(gl);\n const boxBlurProgram = boxBlur.program;\n const boxBlurUniforms = boxBlur.uniforms;\n\n const bgTexture = initTexture(gl, 0);\n const frameTexture = initTexture(gl, 1);\n const vertexBuffer = createVertexBuffer(gl);\n\n if (!vertexBuffer) {\n throw new Error('Failed to create vertex buffer');\n }\n\n // Create additional textures and framebuffers for processing\n let bgBlurTextures: WebGLTexture[] = [];\n let bgBlurFrameBuffers: WebGLFramebuffer[] = [];\n let blurredMaskTexture: WebGLTexture | null = null;\n\n // For double buffering the final mask\n let finalMaskTextures: WebGLTexture[] = [];\n let readMaskIndex = 0; // Index for renderFrame to read from\n let writeMaskIndex = 1; // Index for updateMask to write to\n\n // Create textures for background processing (blur)\n bgBlurTextures.push(initTexture(gl, 3)); // For blur pass 1\n bgBlurTextures.push(initTexture(gl, 4)); // For blur pass 2\n\n const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);\n const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);\n\n const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);\n\n // Create framebuffers for background processing\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n\n // Initialize texture for the first mask blur pass\n const tempMaskTexture = initTexture(gl, 5);\n const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);\n\n // Initialize two textures for double-buffering the final mask\n finalMaskTextures.push(initTexture(gl, 6)); // For reading in renderFrame\n finalMaskTextures.push(initTexture(gl, 7)); // For writing in updateMask\n\n // Create framebuffers for the final mask textures\n const finalMaskFrameBuffers = [\n createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),\n createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height),\n ];\n\n // Set up uniforms for the composite shader\n gl.useProgram(compositeProgram);\n gl.uniform1i(bgTextureLocation, 0);\n gl.uniform1i(frameTextureLocation, 1);\n gl.uniform1i(maskTextureLocation, 2);\n\n // Store custom background image\n let customBackgroundImage: ImageBitmap | ImageData = getEmptyImageData();\n\n function renderFrame(frame: VideoFrame) {\n if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {\n return;\n }\n\n const width = frame.displayWidth;\n const height = frame.displayHeight;\n\n // Prepare frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);\n\n // Apply blur if enabled (and no custom background is set)\n let backgroundTexture = bgTexture;\n\n if (blurRadius) {\n const downSampledFrameTexture = applyDownsampling(\n gl,\n frameTexture,\n downSampler,\n vertexBuffer!,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n );\n backgroundTexture = applyBlur(\n gl,\n downSampledFrameTexture,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n blurRadius,\n blurProgram,\n blurUniforms,\n vertexBuffer!,\n bgBlurFrameBuffers,\n bgBlurTextures,\n );\n } else {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n backgroundTexture = bgTexture;\n }\n\n // Render the final composite\n gl.viewport(0, 0, width, height);\n gl.clearColor(1.0, 1.0, 1.0, 1.0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n gl.useProgram(compositeProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(positionLocation);\n\n // Set background texture (either original, blurred or custom)\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);\n gl.uniform1i(bgTextureLocation, 0);\n\n // Set frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.uniform1i(frameTextureLocation, 1);\n\n // Set mask texture - always read from the current read index\n gl.activeTexture(gl.TEXTURE2);\n gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);\n gl.uniform1i(maskTextureLocation, 2);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n\n /**\n * Set or update the background image\n * @param image The background image to use, or null to clear\n */\n async function setBackgroundImage(image: ImageBitmap | null) {\n // Clear existing background\n customBackgroundImage = getEmptyImageData();\n\n if (image) {\n try {\n // Resize and crop the image to cover the canvas\n const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);\n\n // Store the cropped and resized image\n customBackgroundImage = croppedImage;\n } catch (error) {\n console.error(\n 'Error processing background image, falling back to black background:',\n error,\n );\n }\n }\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n }\n\n function setBlurRadius(radius: number | null) {\n blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null; // we are downsampling the blur texture, so decrease the radius here for better performance with a similar visual result\n setBackgroundImage(null);\n }\n\n function updateMask(mask: WebGLTexture) {\n // Use the existing applyBlur function to apply the first blur pass\n // The second blur pass will be written to finalMaskTextures[writeMaskIndex]\n\n // Create temporary arrays for the single blur operation\n const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];\n\n const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];\n\n // Apply the blur using the existing function\n applyBlur(\n gl,\n mask,\n canvas.width,\n canvas.height,\n maskBlurRadius || 1.0,\n boxBlurProgram,\n boxBlurUniforms,\n vertexBuffer!,\n tempFramebuffers,\n tempTextures,\n );\n\n // Swap indices for the next frame\n readMaskIndex = writeMaskIndex;\n writeMaskIndex = 1 - writeMaskIndex;\n }\n\n function cleanup() {\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(blurProgram);\n gl.deleteProgram(boxBlurProgram);\n gl.deleteTexture(bgTexture);\n gl.deleteTexture(frameTexture);\n gl.deleteTexture(tempMaskTexture);\n gl.deleteFramebuffer(tempMaskFrameBuffer);\n\n for (const texture of bgBlurTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of bgBlurFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n for (const texture of finalMaskTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of finalMaskFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n gl.deleteBuffer(vertexBuffer);\n\n if (blurredMaskTexture) {\n gl.deleteTexture(blurredMaskTexture);\n }\n\n if (downSampler) {\n gl.deleteTexture(downSampler.texture);\n gl.deleteFramebuffer(downSampler.framebuffer);\n gl.deleteProgram(downSampler.program);\n }\n\n // Release any ImageBitmap resources\n if (customBackgroundImage) {\n if (customBackgroundImage instanceof ImageBitmap) {\n customBackgroundImage.close();\n }\n customBackgroundImage = getEmptyImageData();\n }\n bgBlurTextures = [];\n bgBlurFrameBuffers = [];\n finalMaskTextures = [];\n }\n\n return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };\n};\n","import { createCanvas } from '../utils';\nimport { setupWebGL } from '../webgl/index';\nimport { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer<Options extends Record<string, unknown>>\n implements VideoTrackTransformer<Options>\n{\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n // ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\n\n gl?: ReturnType<typeof setupWebGL>;\n\n protected isDisabled?: Boolean = false;\n\n async init({\n outputCanvas,\n inputElement: inputVideo,\n }: VideoTransformerInitOptions): Promise<void> {\n if (!(inputVideo instanceof HTMLVideoElement)) {\n throw TypeError('Video transformer needs a HTMLVideoElement as input');\n }\n\n this.transformer = new TransformStream({\n transform: (frame, controller) => this.transform(frame, controller),\n });\n this.canvas = outputCanvas || null;\n if (outputCanvas) {\n // this.ctx = this.canvas?.getContext('2d') || undefined;\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.gl?.cleanup();\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.gl?.cleanup();\n this.gl = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n\n abstract update(options: Options): void;\n}\n","import ProcessorWrapper, { ProcessorWrapperOptions } from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n FrameProcessingStats,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport {\n ProcessorWrapper,\n type BackgroundOptions,\n type SegmenterOptions,\n BackgroundTransformer,\n type ProcessorWrapperOptions,\n};\n\n/**\n * Determines if the current browser supports background processors\n */\nexport const supportsBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.isSupported;\n\n/**\n * Determines if the current browser supports modern background processors, which yield better performance\n */\nexport const supportsModernBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.hasModernApiSupport;\n\nexport interface BackgroundProcessorOptions extends ProcessorWrapperOptions {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n}\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n blurRadius,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'background-blur',\n );\n};\n\nexport const VirtualBackground = (\n imagePath: string,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n imagePath,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'virtual-background',\n );\n};\n\nexport const BackgroundProcessor = (\n options: BackgroundProcessorOptions,\n name = 'background-processor',\n) => {\n const isTransformerSupported = BackgroundTransformer.isSupported;\n const isProcessorSupported = ProcessorWrapper.isSupported;\n\n if (!isTransformerSupported) {\n throw new Error('Background transformer is not supported in this browser');\n }\n\n if (!isProcessorSupported) {\n throw new Error(\n 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',\n );\n }\n\n // Extract transformer-specific options and processor options\n const {\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n ...processorOpts\n } = options;\n\n const transformer = new BackgroundTransformer({\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n });\n\n const processor = new ProcessorWrapper(transformer, name, processorOpts);\n\n return processor;\n};\n"],"mappings":"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","names":["_a","BackgroundProcessor"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/webgl/utils.ts","../src/webgl/shader-programs/vertexShader.ts","../src/webgl/shader-programs/blurShader.ts","../src/webgl/shader-programs/boxBlurShader.ts","../src/webgl/shader-programs/compositeShader.ts","../src/webgl/shader-programs/downSampler.ts","../src/webgl/index.ts","../src/transformers/VideoTransformer.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\nexport const supportsOffscreenCanvas = () => typeof OffscreenCanvas !== 'undefined';\n\nasync function sleep(time: number) {\n return new Promise((resolve) => setTimeout(resolve, time));\n}\n\nexport async function waitForTrackResolution(track: MediaStreamTrack) {\n const timeout = 500;\n\n // browsers report wrong initial resolution on iOS.\n // when slightly delaying the call to .getSettings(), the correct resolution is being reported\n await sleep(10);\n\n const started = Date.now();\n while (Date.now() - started < timeout) {\n const { width, height } = track.getSettings();\n if (width && height) {\n return { width, height };\n }\n await sleep(50);\n }\n return { width: undefined, height: undefined };\n}\n\nexport function createCanvas(width: number, height: number) {\n if (supportsOffscreenCanvas()) {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n","import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\nimport { createCanvas, waitForTrackResolution } from './utils';\n\nexport interface ProcessorWrapperOptions {\n /**\n * Maximum frame rate for fallback canvas.captureStream implementation\n * Default: 30\n */\n maxFps?: number;\n}\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n /**\n * Determines if the Processor is supported on the current browser\n */\n static get isSupported() {\n // Check for primary implementation support\n const hasStreamProcessor =\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined';\n\n // Check for fallback implementation support\n const hasFallbackSupport =\n typeof HTMLCanvasElement !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n 'captureStream' in HTMLCanvasElement.prototype;\n\n // We can work if either implementation is available\n return hasStreamProcessor || hasFallbackSupport;\n }\n\n /**\n * Determines if modern browser APIs are supported, which yield better performance\n */\n static get hasModernApiSupport() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n displayCanvas?: HTMLCanvasElement;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n // For tracking whether we're using the stream API fallback\n private useStreamFallback = false;\n\n // For fallback rendering with canvas.captureStream()\n private capturedStream?: MediaStream;\n\n private animationFrameId?: number;\n\n private renderContext?: CanvasRenderingContext2D;\n\n private frameCallback?: (frame: VideoFrame) => void;\n\n private processingEnabled = false;\n\n // FPS control for fallback implementation\n private maxFps: number;\n\n private symbol?: Symbol;\n\n constructor(\n transformer: TrackTransformer<TransformerOptions>,\n name: string,\n options: ProcessorWrapperOptions = {},\n ) {\n this.name = name;\n this.transformer = transformer;\n this.maxFps = options.maxFps ?? 30;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n\n const { width, height } = await waitForTrackResolution(this.source);\n this.sourceDummy = opts.element;\n\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = height ?? 300;\n this.sourceDummy.width = width ?? 300;\n }\n\n this.useStreamFallback = !ProcessorWrapper.hasModernApiSupport;\n\n if (this.useStreamFallback) {\n // Create a visible canvas for the fallback implementation or use an existing one if provided\n const existingCanvas = document.querySelector(\n 'canvas[data-livekit-processor=\"' + this.name + '\"]',\n ) as HTMLCanvasElement;\n\n if (existingCanvas) {\n this.displayCanvas = existingCanvas;\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n } else {\n this.displayCanvas = document.createElement('canvas');\n this.displayCanvas.width = width ?? 300;\n this.displayCanvas.height = height ?? 300;\n this.displayCanvas.style.display = 'none';\n this.displayCanvas.dataset.livekitProcessor = this.name;\n document.body.appendChild(this.displayCanvas);\n }\n\n this.renderContext = this.displayCanvas.getContext('2d')!;\n this.capturedStream = this.displayCanvas.captureStream();\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n } else {\n // Use MediaStreamTrackProcessor API\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n this.canvas = createCanvas(width ?? 300, height ?? 300);\n }\n }\n\n async init(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.setup(opts);\n\n if (!this.canvas) {\n throw new TypeError('Expected canvas to be defined after setup');\n }\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n\n if (this.useStreamFallback) {\n this.initFallbackPath();\n } else {\n this.initStreamProcessorPath();\n }\n }\n\n private initStreamProcessorPath() {\n if (!this.processor || !this.trackGenerator) {\n throw new TypeError(\n 'Expected processor and trackGenerator to be defined for stream processor path',\n );\n }\n\n const readableStream = this.processor.readable;\n const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n const symbol = Symbol('stream');\n this.symbol = symbol;\n\n pipedStream\n .pipeTo(this.trackGenerator.writable)\n // destroy processor if stream finishes\n .then(() => this.destroy(symbol))\n // destroy processor if stream errors - unless it's an abort error\n .catch((e) => {\n if (e instanceof DOMException && e.name === 'AbortError') {\n console.log('stream processor path aborted');\n } else {\n console.error('error when trying to pipe', e);\n this.destroy(symbol);\n }\n });\n\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n private initFallbackPath() {\n if (!this.capturedStream || !this.source || !this.canvas || !this.renderContext) {\n throw new TypeError('Missing required components for fallback implementation');\n }\n\n this.processedTrack = this.capturedStream.getVideoTracks()[0];\n this.processingEnabled = true;\n\n // Set up the frame callback for the transformer\n this.frameCallback = (frame: VideoFrame) => {\n if (!this.processingEnabled || !frame) {\n frame.close();\n return;\n }\n\n const controller = {\n enqueue: (processedFrame: VideoFrame) => {\n if (this.renderContext && this.displayCanvas) {\n // Draw the processed frame to the visible canvas\n this.renderContext.drawImage(\n processedFrame,\n 0,\n 0,\n this.displayCanvas.width,\n this.displayCanvas.height,\n );\n processedFrame.close();\n }\n },\n } as TransformStreamDefaultController<VideoFrame>;\n\n try {\n // Pass the frame through our transformer\n // @ts-ignore - The controller expects both VideoFrame & AudioData but we're only using VideoFrame\n this.transformer.transform(frame, controller);\n } catch (e) {\n console.error('Error in transform:', e);\n frame.close();\n }\n };\n\n // Start the rendering loop\n this.startRenderLoop();\n }\n\n private startRenderLoop() {\n if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {\n return;\n }\n\n // Store the last processed timestamp to avoid duplicate processing\n let lastVideoTimestamp = -1;\n let lastFrameTime = 0;\n const videoElement = this.sourceDummy as HTMLVideoElement;\n const minFrameInterval = 1000 / this.maxFps; // Minimum time between frames\n\n // Estimate the video's native frame rate\n let estimatedVideoFps = this.maxFps;\n let frameTimeHistory: number[] = [];\n let lastVideoTimeChange = 0;\n let frameCount = 0;\n let lastFpsLog = 0;\n\n const renderLoop = () => {\n if (\n !this.processingEnabled ||\n !this.sourceDummy ||\n !(this.sourceDummy instanceof HTMLVideoElement)\n ) {\n return;\n }\n\n if (this.sourceDummy.paused) {\n console.warn('Video is paused, trying to play');\n this.sourceDummy.play();\n return;\n }\n\n // Only process a new frame if the video has actually updated\n const videoTime = videoElement.currentTime;\n const now = performance.now();\n const timeSinceLastFrame = now - lastFrameTime;\n\n // Detect if video has a new frame\n const hasNewFrame = videoTime !== lastVideoTimestamp;\n\n // Update frame rate estimation if we have a new frame\n if (hasNewFrame) {\n if (lastVideoTimeChange > 0) {\n const timeBetweenFrames = now - lastVideoTimeChange;\n frameTimeHistory.push(timeBetweenFrames);\n\n // Keep a rolling window of the last 10 frame times\n if (frameTimeHistory.length > 10) {\n frameTimeHistory.shift();\n }\n\n // Calculate average frame interval\n if (frameTimeHistory.length > 2) {\n const avgFrameTime =\n frameTimeHistory.reduce((sum, time) => sum + time, 0) / frameTimeHistory.length;\n estimatedVideoFps = 1000 / avgFrameTime;\n\n // Log estimated FPS every 5 seconds in development environments\n // Use a simpler check that works in browsers without process.env\n const isDevelopment =\n (typeof window !== 'undefined' && window.location.hostname === 'localhost') ||\n window.location.hostname === '127.0.0.1';\n\n if (isDevelopment && now - lastFpsLog > 5000) {\n console.debug(\n `[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(\n 1,\n )}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`,\n );\n frameCount = 0;\n lastFpsLog = now;\n }\n }\n }\n lastVideoTimeChange = now;\n }\n\n // Determine if we should process this frame\n // We'll process if:\n // 1. The video has a new frame\n // 2. Enough time has passed since last frame (respecting maxFps)\n const timeThresholdMet = timeSinceLastFrame >= minFrameInterval;\n\n if (hasNewFrame && timeThresholdMet) {\n lastVideoTimestamp = videoTime;\n lastFrameTime = now;\n frameCount++;\n\n try {\n // Create a VideoFrame from the video element\n if (videoElement.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {\n const frame = new VideoFrame(videoElement);\n if (this.frameCallback) {\n this.frameCallback(frame);\n } else {\n frame.close();\n }\n }\n } catch (e) {\n console.error('Error in render loop:', e);\n }\n }\n this.animationFrameId = requestAnimationFrame(renderLoop);\n };\n\n this.animationFrameId = requestAnimationFrame(renderLoop);\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>): Promise<void> {\n await this.destroy();\n await this.init(opts);\n }\n\n async restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>) {\n // @ts-ignore unclear why the restart method only accepts VideoTransformerInitOptions instead of either those or AudioTransformerInitOptions\n await this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n await this.transformer.update(options[0]);\n }\n\n async destroy(symbol?: Symbol) {\n if (symbol && this.symbol !== symbol) {\n // If the symbol is provided, we only destroy if it matches the current symbol\n return;\n }\n if (this.useStreamFallback) {\n this.processingEnabled = false;\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n if (this.displayCanvas && this.displayCanvas.parentNode) {\n this.displayCanvas.parentNode.removeChild(this.displayCanvas);\n }\n this.capturedStream?.getTracks().forEach((track) => track.stop());\n } else {\n await this.processor?.writableControl?.close();\n this.trackGenerator?.stop();\n }\n await this.transformer.destroy();\n }\n}\n","import * as vision from '@mediapipe/tasks-vision';\nimport { dependencies } from '../../package.json';\nimport VideoTransformer from './VideoTransformer';\nimport { VideoTransformerInitOptions } from './types';\n\nexport type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport interface FrameProcessingStats {\n processingTimeMs: number;\n segmentationTimeMs: number;\n filterTimeMs: number;\n}\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n /** cannot be updated through the `update` method, needs a restart */\n segmenterOptions?: SegmenterOptions;\n /** cannot be updated through the `update` method, needs a restart */\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n /** called when a new frame is processed */\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return (\n typeof OffscreenCanvas !== 'undefined' &&\n typeof VideoFrame !== 'undefined' &&\n typeof createImageBitmap !== 'undefined' &&\n !!document.createElement('canvas').getContext('webgl2')\n );\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n options: BackgroundOptions;\n\n segmentationTimeMs: number = 0;\n\n isFirstFrame = true;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n this.update(opts);\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n // Initialize WebGL with appropriate options based on our current state\n\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\n this.options.assetPaths?.tasksVisionFileSet ??\n `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@${dependencies['@mediapipe/tasks-vision']}/wasm`,\n );\n\n this.imageSegmenter = await vision.ImageSegmenter.createFromOptions(fileSet, {\n baseOptions: {\n modelAssetPath:\n this.options.assetPaths?.modelAssetPath ??\n 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n canvas: this.canvas,\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // Skip loading the image here if update already loaded the image below\n if (this.options?.imagePath && !this.backgroundImage) {\n await this.loadBackground(this.options.imagePath).catch((err) =>\n console.error('Error while loading processor background image: ', err),\n );\n }\n if (this.options.blurRadius) {\n this.gl?.setBlurRadius(this.options.blurRadius);\n }\n }\n\n async destroy() {\n await super.destroy();\n await this.imageSegmenter?.close();\n this.backgroundImage = null;\n this.isFirstFrame = true;\n }\n\n async loadBackground(path: string) {\n const img = new Image();\n\n await new Promise((resolve, reject) => {\n img.crossOrigin = 'Anonymous';\n img.onload = () => resolve(img);\n img.onerror = (err) => reject(err);\n img.src = path;\n });\n const imageData = await createImageBitmap(img);\n this.gl?.setBackgroundImage(imageData);\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n let enqueuedFrame = false;\n try {\n if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {\n console.debug('empty frame detected, ignoring');\n return;\n }\n\n if (this.isDisabled) {\n controller.enqueue(frame);\n enqueuedFrame = true;\n return;\n }\n\n const frameTimeMs = Date.now();\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n this.canvas.width = frame.displayWidth;\n this.canvas.height = frame.displayHeight;\n\n // Render a copy of the first frame is rendered to the screen as soon as possible to act\n // as a less jarring initial state than a solid color while the synchronous work below\n // (segmentation + frame rendering) occurs.\n //\n // Ideally, these sync tasks could be offloaded to a webworker, but this is challenging\n // given WebGLTextures cannot be easily passed in a `postMessage`.\n if (this.isFirstFrame) {\n controller.enqueue(frame.clone());\n\n // Wait for the frame that was enqueued above to render before doing the sync work\n // below - otherwise, the sync work will take over the event loop and prevent the render\n // from occurring\n if (this.inputVideo) {\n await new Promise((resolve) => {\n this.inputVideo!.requestVideoFrameCallback((_now, e) => {\n const durationUntilFrameRenderedInMs = e.expectedDisplayTime - e.presentationTime;\n setTimeout(resolve, durationUntilFrameRenderedInMs);\n });\n });\n }\n }\n this.isFirstFrame = false;\n\n const filterStartTimeMs = performance.now();\n\n const segmentationPromise = new Promise<void>((resolve, reject) => {\n try {\n let segmentationStartTimeMs = performance.now();\n // NOTE: this.imageSegmenter?.segmentForVideo is synchronous, and blocks the event loop\n // for tens to ~100 ms! The promise wrapper is just used to flatten out the call hierarchy.\n this.imageSegmenter?.segmentForVideo(frame, segmentationStartTimeMs, (result) => {\n this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;\n this.segmentationResults = result;\n this.updateMask(result.categoryMask);\n result.close();\n resolve();\n });\n } catch (e) {\n reject(e);\n }\n });\n\n // NOTE: `this.drawFrame` is synchronous, and could take tens of ms to run!\n this.drawFrame(frame);\n if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || frameTimeMs,\n });\n controller.enqueue(newFrame);\n const filterTimeMs = performance.now() - filterStartTimeMs;\n const stats: FrameProcessingStats = {\n processingTimeMs: this.segmentationTimeMs + filterTimeMs,\n segmentationTimeMs: this.segmentationTimeMs,\n filterTimeMs,\n };\n this.options.onFrameProcessed?.(stats);\n } else {\n controller.enqueue(frame);\n }\n await segmentationPromise;\n } catch (e) {\n console.error('Error while processing frame: ', e);\n } finally {\n if (!enqueuedFrame) {\n frame.close();\n }\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = { ...this.options, ...opts };\n\n this.gl?.setBlurRadius(opts.blurRadius ?? null);\n if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n } else {\n this.gl?.setBackgroundImage(null);\n }\n }\n\n private async drawFrame(frame: VideoFrame) {\n if (!this.gl) return;\n this.gl?.renderFrame(frame);\n }\n\n private async updateMask(mask: vision.MPMask | undefined) {\n if (!mask) return;\n this.gl?.updateMask(mask.getAsWebGLTexture());\n }\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.6.0\",\n \"description\": \"LiveKit track processors\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"source\": \"src/index.ts\",\n \"types\": \"dist/src/index.d.ts\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/livekit/track-processors-js.git\"\n },\n \"author\": \"Lukas Seiler\",\n \"license\": \"Apache-2.0\",\n \"scripts\": {\n \"build\": \"tsup --onSuccess \\\"tsc --declaration --emitDeclarationOnly\\\"\",\n \"build-sample\": \"cd example && vite build\",\n \"lint\": \"eslint src\",\n \"release\": \"pnpm build && changeset publish\",\n \"test\": \"jest\",\n \"dev\": \"vite example -c vite.config.mjs --open\",\n \"sample\": \"pnpm run dev\"\n },\n \"files\": [\n \"dist\",\n \"src\"\n ],\n \"dependencies\": {\n \"@mediapipe/tasks-vision\": \"0.10.14\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.1.0\",\n \"@types/dom-mediacapture-transform\": \"^0.1.9\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.2.1\",\n \"@types/offscreencanvas\": \"^2019.7.3\",\n \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n \"eslint\": \"8.39.0\",\n \"eslint-config-airbnb-typescript\": \"17.0.0\",\n \"eslint-config-prettier\": \"8.8.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.2.0\",\n \"typescript\": \"^5.8.3\",\n \"vite\": \"^7.0.2\"\n },\n \"packageManager\": \"pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531\"\n}\n","/**\n * Initialize a WebGL texture\n */\nexport function initTexture(gl: WebGL2RenderingContext, texIndex: number) {\n const texRef = gl.TEXTURE0 + texIndex;\n gl.activeTexture(texRef);\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.bindTexture(gl.TEXTURE_2D, texture);\n\n return texture;\n}\n\nexport function createShader(\n gl: WebGL2RenderingContext,\n type: number,\n source: string,\n): WebGLShader {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n console.error('Shader compile failed:', gl.getShaderInfoLog(shader));\n gl.deleteShader(shader);\n throw new Error('Shader compile failed');\n }\n return shader;\n}\n\nexport function createProgram(\n gl: WebGL2RenderingContext,\n vs: WebGLShader,\n fs: WebGLShader,\n): WebGLProgram {\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('Program link failed:', gl.getProgramInfoLog(program));\n throw new Error('Program link failed');\n }\n return program;\n}\n\n/**\n * Create a WebGL framebuffer with the given texture as color attachment\n */\nexport function createFramebuffer(\n gl: WebGL2RenderingContext,\n texture: WebGLTexture,\n width: number,\n height: number,\n) {\n const framebuffer = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n\n // Set the texture as the color attachment\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Ensure texture dimensions match the provided width and height\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n\n // Check if framebuffer is complete\n const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);\n if (status !== gl.FRAMEBUFFER_COMPLETE) {\n throw new Error('Framebuffer not complete');\n }\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n return framebuffer;\n}\n\n/**\n * Create a vertex buffer for a full-screen quad\n */\nexport function createVertexBuffer(gl: WebGL2RenderingContext): WebGLBuffer | null {\n const vertexBuffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1]),\n gl.STATIC_DRAW,\n );\n return vertexBuffer;\n}\n\n/**\n * Resizes and crops an image to cover a target canvas while maintaining aspect ratio\n * @param image The source image\n * @param targetWidth The target width\n * @param targetHeight The target height\n * @returns A cropped and resized ImageBitmap\n */\nexport async function resizeImageToCover(\n image: ImageBitmap,\n targetWidth: number,\n targetHeight: number,\n): Promise<ImageBitmap> {\n // Calculate dimensions and crop for \"cover\" mode\n const imgAspect = image.width / image.height;\n const targetAspect = targetWidth / targetHeight;\n\n let sx = 0;\n let sy = 0;\n let sWidth = image.width;\n let sHeight = image.height;\n\n // For cover mode, we need to crop some parts of the image\n // to ensure it covers the canvas while maintaining aspect ratio\n if (imgAspect > targetAspect) {\n // Image is wider than target - crop the sides\n sWidth = Math.round(image.height * targetAspect);\n sx = Math.round((image.width - sWidth) / 2); // Center the crop horizontally\n } else if (imgAspect < targetAspect) {\n // Image is taller than target - crop the top/bottom\n sHeight = Math.round(image.width / targetAspect);\n sy = Math.round((image.height - sHeight) / 2); // Center the crop vertically\n }\n\n // Create a new ImageBitmap with the cropped portion\n return createImageBitmap(image, sx, sy, sWidth, sHeight, {\n resizeWidth: targetWidth,\n resizeHeight: targetHeight,\n resizeQuality: 'medium',\n });\n}\n\nlet emptyImageData: ImageData | undefined;\n\nfunction getEmptyImageData() {\n if (!emptyImageData) {\n emptyImageData = new ImageData(2, 2);\n emptyImageData.data[0] = 0;\n emptyImageData.data[1] = 0;\n emptyImageData.data[2] = 0;\n emptyImageData.data[3] = 0;\n }\n\n return emptyImageData;\n}\n\nconst glsl = (source: any) => source;\n\nexport { getEmptyImageData, glsl };\n","// Vertex shader source\nexport const vertexShaderSource = (flipY: boolean = true) => `#version 300 es\n in vec2 position;\n out vec2 texCoords;\n\n void main() {\n texCoords = (position + 1.0) / 2.0;\n texCoords.y = ${flipY ? '1.0 - texCoords.y' : 'texCoords.y'};\n gl_Position = vec4(position, 0, 1.0);\n }\n`;\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Define the blur fragment shader\nexport const blurFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D u_texture;\n uniform vec2 u_texelSize;\n uniform vec2 u_direction;\n uniform float u_radius;\n out vec4 fragColor;\n\n void main() {\n float sigma = u_radius;\n float twoSigmaSq = 2.0 * sigma * sigma;\n float totalWeight = 0.0;\n vec3 result = vec3(0.0);\n const int MAX_SAMPLES = 16;\n int radius = int(min(float(MAX_SAMPLES), ceil(u_radius)));\n\n for (int i = -MAX_SAMPLES; i <= MAX_SAMPLES; ++i) {\n float offset = float(i);\n if (abs(offset) > float(radius)) continue;\n float weight = exp(-(offset * offset) / twoSigmaSq);\n vec2 sampleCoord = texCoords + u_direction * u_texelSize * offset;\n result += texture(u_texture, sampleCoord).rgb * weight;\n totalWeight += weight;\n }\n\n fragColor = vec4(result / totalWeight, 1.0);\n }\n`;\n\nexport function createBlurProgram(gl: WebGL2RenderingContext) {\n const blurVertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const blurFrag = createShader(gl, gl.FRAGMENT_SHADER, blurFragmentShader);\n\n const blurProgram = createProgram(gl, blurVertexShader, blurFrag);\n\n // Get uniform locations\n const blurUniforms = {\n position: gl.getAttribLocation(blurProgram, 'position'),\n texture: gl.getUniformLocation(blurProgram, 'u_texture'),\n texelSize: gl.getUniformLocation(blurProgram, 'u_texelSize'),\n direction: gl.getUniformLocation(blurProgram, 'u_direction'),\n radius: gl.getUniformLocation(blurProgram, 'u_radius'),\n };\n\n return {\n program: blurProgram,\n shader: blurFrag,\n vertexShader: blurVertexShader,\n uniforms: blurUniforms,\n };\n}\n\nexport function applyBlur(\n gl: WebGL2RenderingContext,\n sourceTexture: WebGLTexture,\n width: number,\n height: number,\n blurRadius: number,\n blurProgram: WebGLProgram,\n blurUniforms: any,\n vertexBuffer: WebGLBuffer,\n processFramebuffers: WebGLFramebuffer[],\n processTextures: WebGLTexture[],\n) {\n gl.useProgram(blurProgram);\n\n // Set common attributes\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(blurUniforms.position, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(blurUniforms.position);\n\n const texelWidth = 1.0 / width;\n const texelHeight = 1.0 / height;\n\n // First pass - horizontal blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[0]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, sourceTexture);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.texelSize, texelWidth, texelHeight);\n gl.uniform2f(blurUniforms.direction, 1.0, 0.0); // Horizontal\n gl.uniform1f(blurUniforms.radius, blurRadius);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Second pass - vertical blur\n gl.bindFramebuffer(gl.FRAMEBUFFER, processFramebuffers[1]);\n gl.viewport(0, 0, width, height);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, processTextures[0]);\n gl.uniform1i(blurUniforms.texture, 0);\n gl.uniform2f(blurUniforms.direction, 0.0, 1.0); // Vertical\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n // Reset framebuffer\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return processTextures[1];\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\nexport const boxBlurFragmentShader = glsl`#version 300 es\nprecision mediump float;\n\nin vec2 texCoords;\n\nuniform sampler2D u_texture;\nuniform vec2 u_texelSize; // 1.0 / texture size\nuniform vec2 u_direction; // (1.0, 0.0) for horizontal, (0.0, 1.0) for vertical\nuniform float u_radius; // blur radius in texels\n\nout vec4 fragColor;\n\nvoid main() {\n vec3 sum = vec3(0.0);\n float count = 0.0;\n\n // Limit radius to avoid excessive loop cost\n const int MAX_RADIUS = 16;\n int radius = int(min(float(MAX_RADIUS), u_radius));\n\n for (int i = -MAX_RADIUS; i <= MAX_RADIUS; ++i) {\n if (abs(i) > radius) continue;\n\n vec2 offset = u_direction * u_texelSize * float(i);\n sum += texture(u_texture, texCoords + offset).rgb;\n count += 1.0;\n }\n\n fragColor = vec4(sum / count, 1.0);\n}\n`;\n\n/**\n * Create the box blur shader program\n */\nexport function createBoxBlurProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, boxBlurFragmentShader);\n\n const program = createProgram(gl, vertexShader, fragmentShader);\n\n // Get attribute and uniform locations\n const uniforms = {\n position: gl.getAttribLocation(program, 'position'),\n texture: gl.getUniformLocation(program, 'u_texture'),\n texelSize: gl.getUniformLocation(program, 'u_texelSize'),\n direction: gl.getUniformLocation(program, 'u_direction'),\n radius: gl.getUniformLocation(program, 'u_radius'),\n };\n\n return {\n program,\n vertexShader,\n fragmentShader,\n uniforms,\n };\n}\n","import { createProgram, createShader, glsl } from '../utils';\nimport { vertexShaderSource } from './vertexShader';\n\n// Fragment shader source for compositing\nexport const compositeFragmentShader = glsl`#version 300 es\n precision mediump float;\n in vec2 texCoords;\n uniform sampler2D background;\n uniform sampler2D frame;\n uniform sampler2D mask;\n out vec4 fragColor;\n \n void main() {\n \n vec4 frameTex = texture(frame, texCoords);\n vec4 bgTex = texture(background, texCoords);\n\n float maskVal = texture(mask, texCoords).r;\n\n // Compute screen-space gradient to detect edge sharpness\n float grad = length(vec2(dFdx(maskVal), dFdy(maskVal)));\n\n float edgeSoftness = 2.0; // higher = softer\n \n // Create a smooth edge around binary transition\n float smoothAlpha = smoothstep(0.5 - grad * edgeSoftness, 0.5 + grad * edgeSoftness, maskVal);\n\n // Optional: preserve frame alpha, or override as fully opaque\n vec4 blended = mix(bgTex, vec4(frameTex.rgb, 1.0), 1.0 - smoothAlpha);\n \n fragColor = blended;\n \n }\n`;\n\n/**\n * Create the composite shader program\n */\nexport function createCompositeProgram(gl: WebGL2RenderingContext) {\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource());\n const compositeShader = createShader(gl, gl.FRAGMENT_SHADER, compositeFragmentShader);\n\n const compositeProgram = createProgram(gl, vertexShader, compositeShader);\n\n // Get attribute and uniform locations\n const attribLocations = {\n position: gl.getAttribLocation(compositeProgram, 'position'),\n };\n\n const uniformLocations = {\n mask: gl.getUniformLocation(compositeProgram, 'mask')!,\n frame: gl.getUniformLocation(compositeProgram, 'frame')!,\n background: gl.getUniformLocation(compositeProgram, 'background')!,\n stepWidth: gl.getUniformLocation(compositeProgram, 'u_stepWidth')!,\n };\n\n return {\n program: compositeProgram,\n vertexShader,\n fragmentShader: compositeShader,\n attribLocations,\n uniformLocations,\n };\n}\n","import { createProgram, createShader } from '../utils';\n\nexport function createDownSampler(\n gl: WebGL2RenderingContext,\n width: number,\n height: number,\n): {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n} {\n // Create texture\n const texture = gl.createTexture()!;\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n // Create framebuffer\n const framebuffer = gl.createFramebuffer()!;\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);\n\n // Create shader program for copying\n const vertexSource = `\n attribute vec2 position;\n varying vec2 v_uv;\n void main() {\n v_uv = (position + 1.0) * 0.5;\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\n const fragmentSource = `\n precision mediump float;\n varying vec2 v_uv;\n uniform sampler2D u_texture;\n void main() {\n gl_FragColor = texture2D(u_texture, v_uv);\n }\n `;\n\n const vertShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n const program = createProgram(gl, vertShader, fragShader);\n\n const uniforms = {\n texture: gl.getUniformLocation(program, 'u_texture'),\n position: gl.getAttribLocation(program, 'position'),\n };\n\n return {\n framebuffer,\n texture,\n program,\n uniforms,\n };\n}\n\nexport function applyDownsampling(\n gl: WebGL2RenderingContext,\n inputTexture: WebGLTexture,\n downSampler: {\n framebuffer: WebGLFramebuffer;\n texture: WebGLTexture;\n program: WebGLProgram;\n uniforms: any;\n },\n vertexBuffer: WebGLBuffer,\n width: number,\n height: number,\n): WebGLTexture {\n gl.useProgram(downSampler.program);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, downSampler.framebuffer);\n gl.viewport(0, 0, width, height);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.enableVertexAttribArray(downSampler.uniforms.position);\n gl.vertexAttribPointer(downSampler.uniforms.position, 2, gl.FLOAT, false, 0, 0);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, inputTexture);\n gl.uniform1i(downSampler.uniforms.texture, 0);\n\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n\n return downSampler.texture;\n}\n","/**\n * WebGL setup for the mask processor\n * potential improvements:\n * - downsample the video texture in background blur scenario before applying the (gaussian) blur for better performance\n *\n */\nimport { applyBlur, createBlurProgram } from './shader-programs/blurShader';\nimport { createBoxBlurProgram } from './shader-programs/boxBlurShader';\nimport { createCompositeProgram } from './shader-programs/compositeShader';\nimport { applyDownsampling, createDownSampler } from './shader-programs/downSampler';\nimport {\n createFramebuffer,\n createVertexBuffer,\n getEmptyImageData,\n initTexture,\n resizeImageToCover,\n} from './utils';\n\nexport const setupWebGL = (canvas: OffscreenCanvas | HTMLCanvasElement) => {\n const gl = canvas.getContext('webgl2', {\n antialias: true,\n premultipliedAlpha: true,\n }) as WebGL2RenderingContext;\n\n let blurRadius: number | null = null;\n let maskBlurRadius: number | null = 8;\n const downsampleFactor = 4;\n\n if (!gl) {\n console.error('Failed to create WebGL context');\n return undefined;\n }\n\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n\n // Create the composite program\n const composite = createCompositeProgram(gl);\n const compositeProgram = composite.program;\n const positionLocation = composite.attribLocations.position;\n const {\n mask: maskTextureLocation,\n frame: frameTextureLocation,\n background: bgTextureLocation,\n } = composite.uniformLocations;\n\n // Create the blur program using the same vertex shader source\n const blur = createBlurProgram(gl);\n const blurProgram = blur.program;\n const blurUniforms = blur.uniforms;\n\n // Create the box blur program\n const boxBlur = createBoxBlurProgram(gl);\n const boxBlurProgram = boxBlur.program;\n const boxBlurUniforms = boxBlur.uniforms;\n\n const bgTexture = initTexture(gl, 0);\n const frameTexture = initTexture(gl, 1);\n const vertexBuffer = createVertexBuffer(gl);\n\n if (!vertexBuffer) {\n throw new Error('Failed to create vertex buffer');\n }\n\n // Create additional textures and framebuffers for processing\n let bgBlurTextures: WebGLTexture[] = [];\n let bgBlurFrameBuffers: WebGLFramebuffer[] = [];\n let blurredMaskTexture: WebGLTexture | null = null;\n\n // For double buffering the final mask\n let finalMaskTextures: WebGLTexture[] = [];\n let readMaskIndex = 0; // Index for renderFrame to read from\n let writeMaskIndex = 1; // Index for updateMask to write to\n\n // Create textures for background processing (blur)\n bgBlurTextures.push(initTexture(gl, 3)); // For blur pass 1\n bgBlurTextures.push(initTexture(gl, 4)); // For blur pass 2\n\n const bgBlurTextureWidth = Math.floor(canvas.width / downsampleFactor);\n const bgBlurTextureHeight = Math.floor(canvas.height / downsampleFactor);\n\n const downSampler = createDownSampler(gl, bgBlurTextureWidth, bgBlurTextureHeight);\n\n // Create framebuffers for background processing\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[0], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n bgBlurFrameBuffers.push(\n createFramebuffer(gl, bgBlurTextures[1], bgBlurTextureWidth, bgBlurTextureHeight),\n );\n\n // Initialize texture for the first mask blur pass\n const tempMaskTexture = initTexture(gl, 5);\n const tempMaskFrameBuffer = createFramebuffer(gl, tempMaskTexture, canvas.width, canvas.height);\n\n // Initialize two textures for double-buffering the final mask\n finalMaskTextures.push(initTexture(gl, 6)); // For reading in renderFrame\n finalMaskTextures.push(initTexture(gl, 7)); // For writing in updateMask\n\n // Create framebuffers for the final mask textures\n const finalMaskFrameBuffers = [\n createFramebuffer(gl, finalMaskTextures[0], canvas.width, canvas.height),\n createFramebuffer(gl, finalMaskTextures[1], canvas.width, canvas.height),\n ];\n\n // Set up uniforms for the composite shader\n gl.useProgram(compositeProgram);\n gl.uniform1i(bgTextureLocation, 0);\n gl.uniform1i(frameTextureLocation, 1);\n gl.uniform1i(maskTextureLocation, 2);\n\n // Store custom background image\n let customBackgroundImage: ImageBitmap | ImageData = getEmptyImageData();\n\n function renderFrame(frame: VideoFrame) {\n if (frame.codedWidth === 0 || finalMaskTextures.length === 0) {\n return;\n }\n\n const width = frame.displayWidth;\n const height = frame.displayHeight;\n\n // Prepare frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);\n\n // Apply blur if enabled (and no custom background is set)\n let backgroundTexture = bgTexture;\n\n if (blurRadius) {\n const downSampledFrameTexture = applyDownsampling(\n gl,\n frameTexture,\n downSampler,\n vertexBuffer!,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n );\n backgroundTexture = applyBlur(\n gl,\n downSampledFrameTexture,\n bgBlurTextureWidth,\n bgBlurTextureHeight,\n blurRadius,\n blurProgram,\n blurUniforms,\n vertexBuffer!,\n bgBlurFrameBuffers,\n bgBlurTextures,\n );\n } else {\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n backgroundTexture = bgTexture;\n }\n\n // Render the final composite\n gl.viewport(0, 0, width, height);\n gl.clearColor(1.0, 1.0, 1.0, 1.0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n\n gl.useProgram(compositeProgram);\n gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);\n gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);\n gl.enableVertexAttribArray(positionLocation);\n\n // Set background texture (either original, blurred or custom)\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, backgroundTexture);\n gl.uniform1i(bgTextureLocation, 0);\n\n // Set frame texture\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, frameTexture);\n gl.uniform1i(frameTextureLocation, 1);\n\n // Set mask texture - always read from the current read index\n gl.activeTexture(gl.TEXTURE2);\n gl.bindTexture(gl.TEXTURE_2D, finalMaskTextures[readMaskIndex]);\n gl.uniform1i(maskTextureLocation, 2);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n }\n\n /**\n * Set or update the background image\n * @param image The background image to use, or null to clear\n */\n async function setBackgroundImage(image: ImageBitmap | null) {\n // Clear existing background\n customBackgroundImage = getEmptyImageData();\n\n if (image) {\n try {\n // Resize and crop the image to cover the canvas\n const croppedImage = await resizeImageToCover(image, canvas.width, canvas.height);\n\n // Store the cropped and resized image\n customBackgroundImage = croppedImage;\n } catch (error) {\n console.error(\n 'Error processing background image, falling back to black background:',\n error,\n );\n }\n }\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, bgTexture);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, customBackgroundImage);\n }\n\n function setBlurRadius(radius: number | null) {\n blurRadius = radius ? Math.max(1, Math.floor(radius / downsampleFactor)) : null; // we are downsampling the blur texture, so decrease the radius here for better performance with a similar visual result\n setBackgroundImage(null);\n }\n\n function updateMask(mask: WebGLTexture) {\n // Use the existing applyBlur function to apply the first blur pass\n // The second blur pass will be written to finalMaskTextures[writeMaskIndex]\n\n // Create temporary arrays for the single blur operation\n const tempFramebuffers = [tempMaskFrameBuffer, finalMaskFrameBuffers[writeMaskIndex]];\n\n const tempTextures = [tempMaskTexture, finalMaskTextures[writeMaskIndex]];\n\n // Apply the blur using the existing function\n applyBlur(\n gl,\n mask,\n canvas.width,\n canvas.height,\n maskBlurRadius || 1.0,\n boxBlurProgram,\n boxBlurUniforms,\n vertexBuffer!,\n tempFramebuffers,\n tempTextures,\n );\n\n // Swap indices for the next frame\n readMaskIndex = writeMaskIndex;\n writeMaskIndex = 1 - writeMaskIndex;\n }\n\n function cleanup() {\n gl.deleteProgram(compositeProgram);\n gl.deleteProgram(blurProgram);\n gl.deleteProgram(boxBlurProgram);\n gl.deleteTexture(bgTexture);\n gl.deleteTexture(frameTexture);\n gl.deleteTexture(tempMaskTexture);\n gl.deleteFramebuffer(tempMaskFrameBuffer);\n\n for (const texture of bgBlurTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of bgBlurFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n for (const texture of finalMaskTextures) {\n gl.deleteTexture(texture);\n }\n for (const framebuffer of finalMaskFrameBuffers) {\n gl.deleteFramebuffer(framebuffer);\n }\n gl.deleteBuffer(vertexBuffer);\n\n if (blurredMaskTexture) {\n gl.deleteTexture(blurredMaskTexture);\n }\n\n if (downSampler) {\n gl.deleteTexture(downSampler.texture);\n gl.deleteFramebuffer(downSampler.framebuffer);\n gl.deleteProgram(downSampler.program);\n }\n\n // Release any ImageBitmap resources\n if (customBackgroundImage) {\n if (customBackgroundImage instanceof ImageBitmap) {\n customBackgroundImage.close();\n }\n customBackgroundImage = getEmptyImageData();\n }\n bgBlurTextures = [];\n bgBlurFrameBuffers = [];\n finalMaskTextures = [];\n }\n\n return { renderFrame, updateMask, setBackgroundImage, setBlurRadius, cleanup };\n};\n","import { createCanvas } from '../utils';\nimport { setupWebGL } from '../webgl/index';\nimport { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer<Options extends Record<string, unknown>>\n implements VideoTrackTransformer<Options>\n{\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas | HTMLCanvasElement;\n\n // ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\n\n gl?: ReturnType<typeof setupWebGL>;\n\n protected isDisabled?: Boolean = false;\n\n async init({\n outputCanvas,\n inputElement: inputVideo,\n }: VideoTransformerInitOptions): Promise<void> {\n if (!(inputVideo instanceof HTMLVideoElement)) {\n throw TypeError('Video transformer needs a HTMLVideoElement as input');\n }\n\n this.transformer = new TransformStream({\n transform: (frame, controller) => this.transform(frame, controller),\n });\n this.canvas = outputCanvas || null;\n if (outputCanvas) {\n // this.ctx = this.canvas?.getContext('2d') || undefined;\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.gl?.cleanup();\n this.gl = setupWebGL(\n this.canvas || createCanvas(inputVideo.videoWidth, inputVideo.videoHeight),\n );\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.gl?.cleanup();\n this.gl = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n\n abstract update(options: Options): void;\n}\n","import ProcessorWrapper, { ProcessorWrapperOptions } from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n FrameProcessingStats,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport {\n ProcessorWrapper,\n type BackgroundOptions,\n type SegmenterOptions,\n BackgroundTransformer,\n type ProcessorWrapperOptions,\n};\n\n/**\n * Determines if the current browser supports background processors\n */\nexport const supportsBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.isSupported;\n\n/**\n * Determines if the current browser supports modern background processors, which yield better performance\n */\nexport const supportsModernBackgroundProcessors = () =>\n BackgroundTransformer.isSupported && ProcessorWrapper.hasModernApiSupport;\n\nexport interface BackgroundProcessorOptions extends ProcessorWrapperOptions {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };\n onFrameProcessed?: (stats: FrameProcessingStats) => void;\n}\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n blurRadius,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'background-blur',\n );\n};\n\nexport const VirtualBackground = (\n imagePath: string,\n segmenterOptions?: SegmenterOptions,\n onFrameProcessed?: (stats: FrameProcessingStats) => void,\n processorOptions?: ProcessorWrapperOptions,\n) => {\n return BackgroundProcessor(\n {\n imagePath,\n segmenterOptions,\n onFrameProcessed,\n ...processorOptions,\n },\n 'virtual-background',\n );\n};\n\nexport const BackgroundProcessor = (\n options: BackgroundProcessorOptions,\n name = 'background-processor',\n) => {\n const isTransformerSupported = BackgroundTransformer.isSupported;\n const isProcessorSupported = ProcessorWrapper.isSupported;\n\n if (!isTransformerSupported) {\n throw new Error('Background transformer is not supported in this browser');\n }\n\n if (!isProcessorSupported) {\n throw new Error(\n 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',\n );\n }\n\n // Extract transformer-specific options and processor options\n const {\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n ...processorOpts\n } = options;\n\n const transformer = new BackgroundTransformer({\n blurRadius,\n imagePath,\n segmenterOptions,\n assetPaths,\n onFrameProcessed,\n });\n\n const processor = new ProcessorWrapper(transformer, name, processorOpts);\n\n return processor;\n};\n"],"mappings":"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","names":["_a","BackgroundProcessor"]}
@@ -32,6 +32,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
32
32
  private frameCallback?;
33
33
  private processingEnabled;
34
34
  private maxFps;
35
+ private symbol?;
35
36
  constructor(transformer: TrackTransformer<TransformerOptions>, name: string, options?: ProcessorWrapperOptions);
36
37
  private setup;
37
38
  init(opts: ProcessorOptions<Track.Kind>): Promise<void>;
@@ -41,5 +42,5 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
41
42
  restart(opts: ProcessorOptions<Track.Kind>): Promise<void>;
42
43
  restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>): Promise<void>;
43
44
  updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>): Promise<void>;
44
- destroy(): Promise<void>;
45
+ destroy(symbol?: Symbol): Promise<void>;
45
46
  }
@@ -27,6 +27,7 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
27
27
  backgroundImage: ImageBitmap | null;
28
28
  options: BackgroundOptions;
29
29
  segmentationTimeMs: number;
30
+ isFirstFrame: boolean;
30
31
  constructor(opts: BackgroundOptions);
31
32
  init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions): Promise<void>;
32
33
  destroy(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/track-processors",
3
- "version": "0.5.7",
3
+ "version": "0.6.0",
4
4
  "description": "LiveKit track processors",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -32,12 +32,11 @@
32
32
  "eslint": "8.39.0",
33
33
  "eslint-config-airbnb-typescript": "17.0.0",
34
34
  "eslint-config-prettier": "8.8.0",
35
- "eslint-plugin-ecmascript-compat": "^3.1.0",
36
35
  "eslint-plugin-import": "2.27.5",
37
36
  "prettier": "^2.8.8",
38
37
  "tsup": "^7.2.0",
39
38
  "typescript": "^5.8.3",
40
- "vite": "^4.5.0"
39
+ "vite": "^7.0.2"
41
40
  },
42
41
  "packageManager": "pnpm@9.15.9+sha512.68046141893c66fad01c079231128e9afb89ef87e2691d69e4d40eee228988295fd4682181bae55b58418c3a253bde65a505ec7c5f9403ece5cc3cd37dcf2531",
43
42
  "scripts": {
@@ -46,6 +45,7 @@
46
45
  "lint": "eslint src",
47
46
  "release": "pnpm build && changeset publish",
48
47
  "test": "jest",
49
- "sample": "vite serve example --port 8080 --open"
48
+ "dev": "vite example -c vite.config.mjs --open",
49
+ "sample": "pnpm run dev"
50
50
  }
51
51
  }
@@ -77,6 +77,8 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
77
77
  // FPS control for fallback implementation
78
78
  private maxFps: number;
79
79
 
80
+ private symbol?: Symbol;
81
+
80
82
  constructor(
81
83
  transformer: TrackTransformer<TransformerOptions>,
82
84
  name: string,
@@ -166,10 +168,22 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
166
168
  const readableStream = this.processor.readable;
167
169
  const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);
168
170
 
171
+ const symbol = Symbol('stream');
172
+ this.symbol = symbol;
173
+
169
174
  pipedStream
170
175
  .pipeTo(this.trackGenerator.writable)
171
- .catch((e) => console.error('error when trying to pipe', e))
172
- .finally(() => this.destroy());
176
+ // destroy processor if stream finishes
177
+ .then(() => this.destroy(symbol))
178
+ // destroy processor if stream errors - unless it's an abort error
179
+ .catch((e) => {
180
+ if (e instanceof DOMException && e.name === 'AbortError') {
181
+ console.log('stream processor path aborted');
182
+ } else {
183
+ console.error('error when trying to pipe', e);
184
+ this.destroy(symbol);
185
+ }
186
+ });
173
187
 
174
188
  this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;
175
189
  }
@@ -342,7 +356,11 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
342
356
  await this.transformer.update(options[0]);
343
357
  }
344
358
 
345
- async destroy() {
359
+ async destroy(symbol?: Symbol) {
360
+ if (symbol && this.symbol !== symbol) {
361
+ // If the symbol is provided, we only destroy if it matches the current symbol
362
+ return;
363
+ }
346
364
  if (this.useStreamFallback) {
347
365
  this.processingEnabled = false;
348
366
  if (this.animationFrameId) {
@@ -42,6 +42,8 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
42
42
 
43
43
  segmentationTimeMs: number = 0;
44
44
 
45
+ isFirstFrame = true;
46
+
45
47
  constructor(opts: BackgroundOptions) {
46
48
  super();
47
49
  this.options = opts;
@@ -87,6 +89,7 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
87
89
  await super.destroy();
88
90
  await this.imageSegmenter?.close();
89
91
  this.backgroundImage = null;
92
+ this.isFirstFrame = true;
90
93
  }
91
94
 
92
95
  async loadBackground(path: string) {
@@ -103,6 +106,7 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
103
106
  }
104
107
 
105
108
  async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {
109
+ let enqueuedFrame = false;
106
110
  try {
107
111
  if (!(frame instanceof VideoFrame) || frame.codedWidth === 0 || frame.codedHeight === 0) {
108
112
  console.debug('empty frame detected, ignoring');
@@ -111,17 +115,47 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
111
115
 
112
116
  if (this.isDisabled) {
113
117
  controller.enqueue(frame);
118
+ enqueuedFrame = true;
114
119
  return;
115
120
  }
121
+
116
122
  const frameTimeMs = Date.now();
117
123
  if (!this.canvas) {
118
124
  throw TypeError('Canvas needs to be initialized first');
119
125
  }
120
126
  this.canvas.width = frame.displayWidth;
121
127
  this.canvas.height = frame.displayHeight;
128
+
129
+ // Render a copy of the first frame is rendered to the screen as soon as possible to act
130
+ // as a less jarring initial state than a solid color while the synchronous work below
131
+ // (segmentation + frame rendering) occurs.
132
+ //
133
+ // Ideally, these sync tasks could be offloaded to a webworker, but this is challenging
134
+ // given WebGLTextures cannot be easily passed in a `postMessage`.
135
+ if (this.isFirstFrame) {
136
+ controller.enqueue(frame.clone());
137
+
138
+ // Wait for the frame that was enqueued above to render before doing the sync work
139
+ // below - otherwise, the sync work will take over the event loop and prevent the render
140
+ // from occurring
141
+ if (this.inputVideo) {
142
+ await new Promise((resolve) => {
143
+ this.inputVideo!.requestVideoFrameCallback((_now, e) => {
144
+ const durationUntilFrameRenderedInMs = e.expectedDisplayTime - e.presentationTime;
145
+ setTimeout(resolve, durationUntilFrameRenderedInMs);
146
+ });
147
+ });
148
+ }
149
+ }
150
+ this.isFirstFrame = false;
151
+
152
+ const filterStartTimeMs = performance.now();
153
+
122
154
  const segmentationPromise = new Promise<void>((resolve, reject) => {
123
155
  try {
124
156
  let segmentationStartTimeMs = performance.now();
157
+ // NOTE: this.imageSegmenter?.segmentForVideo is synchronous, and blocks the event loop
158
+ // for tens to ~100 ms! The promise wrapper is just used to flatten out the call hierarchy.
125
159
  this.imageSegmenter?.segmentForVideo(frame, segmentationStartTimeMs, (result) => {
126
160
  this.segmentationTimeMs = performance.now() - segmentationStartTimeMs;
127
161
  this.segmentationResults = result;
@@ -134,7 +168,7 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
134
168
  }
135
169
  });
136
170
 
137
- const filterStartTimeMs = performance.now();
171
+ // NOTE: `this.drawFrame` is synchronous, and could take tens of ms to run!
138
172
  this.drawFrame(frame);
139
173
  if (this.canvas && this.canvas.width > 0 && this.canvas.height > 0) {
140
174
  const newFrame = new VideoFrame(this.canvas, {
@@ -155,16 +189,20 @@ export default class BackgroundProcessor extends VideoTransformer<BackgroundOpti
155
189
  } catch (e) {
156
190
  console.error('Error while processing frame: ', e);
157
191
  } finally {
158
- frame.close();
192
+ if (!enqueuedFrame) {
193
+ frame.close();
194
+ }
159
195
  }
160
196
  }
161
197
 
162
198
  async update(opts: BackgroundOptions) {
163
199
  this.options = { ...this.options, ...opts };
164
- if (opts.blurRadius) {
165
- this.gl?.setBlurRadius(opts.blurRadius);
166
- } else if (opts.imagePath) {
200
+
201
+ this.gl?.setBlurRadius(opts.blurRadius ?? null);
202
+ if (opts.imagePath) {
167
203
  await this.loadBackground(opts.imagePath);
204
+ } else {
205
+ this.gl?.setBackgroundImage(null);
168
206
  }
169
207
  }
170
208