@stream-io/video-filters-web 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +38 -1
- package/dist/esm/src/VirtualBackground.js +25 -0
- package/dist/esm/src/VirtualBackground.js.map +1 -1
- package/dist/esm/src/types.js.map +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/index.cjs.js +26 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/src/VirtualBackground.d.ts +3 -2
- package/dist/src/types.d.ts +7 -5
- package/package.json +1 -1
- package/src/VirtualBackground.ts +29 -1
- package/src/types.ts +8 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [0.9.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.7...@stream-io/video-filters-web-0.9.0) (2026-09-04)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **video-filters-web:** apply background filter changes without re-registering the filter ([#2403](https://github.com/GetStream/stream-video-js/issues/2403)) ([4fbb30a](https://github.com/GetStream/stream-video-js/commit/4fbb30a640f87a61d400179b24f844b04f2f3bd5))
|
|
10
|
+
|
|
5
11
|
## [0.8.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.6...@stream-io/video-filters-web-0.8.7) (2026-08-28)
|
|
6
12
|
|
|
7
13
|
### Performance Improvements
|
package/README.md
CHANGED
|
@@ -43,10 +43,47 @@ const processor = new VirtualBackground(
|
|
|
43
43
|
// 4. Start the processor and use the processed track
|
|
44
44
|
const processedTrack = await processor.start();
|
|
45
45
|
|
|
46
|
-
// 5.
|
|
46
|
+
// 5. Change the effect at any time, without restarting the track
|
|
47
|
+
await processor.updateOptions({
|
|
48
|
+
backgroundFilter: 'blur',
|
|
49
|
+
backgroundBlurLevel: 'high',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 6. Stop the processor
|
|
47
53
|
processor.stop();
|
|
48
54
|
```
|
|
49
55
|
|
|
56
|
+
### Updating the effect
|
|
57
|
+
|
|
58
|
+
`updateOptions` changes the background effect of a running processor. The new
|
|
59
|
+
effect is applied on the next frame - the input track, renderer and segmenter
|
|
60
|
+
are preserved, so there is no interruption and the processed track doesn't
|
|
61
|
+
need to be re-published.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// switch between blur levels
|
|
65
|
+
await processor.updateOptions({
|
|
66
|
+
backgroundFilter: 'blur',
|
|
67
|
+
backgroundBlurLevel: 3,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// switch to an image background
|
|
71
|
+
await processor.updateOptions({
|
|
72
|
+
backgroundFilter: 'image',
|
|
73
|
+
backgroundImage: 'https://example.com/background.jpg',
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Notes:
|
|
78
|
+
|
|
79
|
+
- `updateOptions` replaces the effect options, so always pass the complete
|
|
80
|
+
set you want applied.
|
|
81
|
+
- `basePath` and `modelPath` are fixed for the lifetime of the processor;
|
|
82
|
+
changing the model requires a new instance.
|
|
83
|
+
- When several updates overlap, the last one wins.
|
|
84
|
+
- If the background image fails to load, the promise rejects and the current
|
|
85
|
+
background keeps rendering.
|
|
86
|
+
|
|
50
87
|
## Known limitations
|
|
51
88
|
|
|
52
89
|
- This library only works in a modern desktop browser that supports WebAssembly SIMD and WebGL.
|
|
@@ -22,6 +22,28 @@ class VirtualBackground extends BaseVideoProcessor {
|
|
|
22
22
|
super(track, hooks);
|
|
23
23
|
this.options = options;
|
|
24
24
|
}
|
|
25
|
+
async updateOptions(options) {
|
|
26
|
+
const { basePath, modelPath } = this.options;
|
|
27
|
+
const previous = this.options;
|
|
28
|
+
const next = { basePath, modelPath, ...options };
|
|
29
|
+
this.options = next;
|
|
30
|
+
try {
|
|
31
|
+
const opts = await this.initializeSegmenterOptions();
|
|
32
|
+
if (this.options === next) {
|
|
33
|
+
const previousMedia = this.opts?.backgroundSource?.media;
|
|
34
|
+
this.opts = opts;
|
|
35
|
+
if (previousMedia instanceof ImageBitmap &&
|
|
36
|
+
previousMedia !== opts.backgroundSource?.media) {
|
|
37
|
+
previousMedia.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (this.options === next)
|
|
43
|
+
this.options = previous;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
25
47
|
async initialize() {
|
|
26
48
|
this.webGlRenderer = new WebGLRenderer(this.canvas);
|
|
27
49
|
await this.initializeSegmenter();
|
|
@@ -112,6 +134,9 @@ class VirtualBackground extends BaseVideoProcessor {
|
|
|
112
134
|
async loadBackground(url) {
|
|
113
135
|
if (!url)
|
|
114
136
|
return null;
|
|
137
|
+
const current = this.opts?.backgroundSource;
|
|
138
|
+
if (current?.url === url)
|
|
139
|
+
return current;
|
|
115
140
|
const result = await fetch(url, { signal: this.abortController.signal });
|
|
116
141
|
if (!result.ok) {
|
|
117
142
|
throw new Error(`[virtual-background] Failed to load background image: ${result.status} ${result.statusText}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualBackground.js","sources":["../../../../../src/VirtualBackground.ts"],"sourcesContent":["import {\n BACKGROUND_BLUR_MAP,\n BackgroundOptions,\n SegmenterOptions,\n VideoTrackProcessorHooks,\n} from './types';\nimport { FilesetResolver, ImageSegmenter } from '@mediapipe/tasks-vision';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { packageName, version } from './version';\nimport { BaseVideoProcessor } from './BaseVideoProcessor';\n\n/**\n * Wraps a video MediaStreamTrack in a real-time processing pipeline.\n * Incoming frames are processed through a transformer and re-emitted\n * on a new MediaStreamVideoTrack for downstream consumption.\n */\nexport class VirtualBackground extends BaseVideoProcessor {\n private segmenter: ImageSegmenter | null = null;\n private isSegmenterReady = false;\n private webGlRenderer!: WebGLRenderer;\n\n private opts!: SegmenterOptions;\n\n private latestCategoryMask: WebGLTexture | undefined = undefined;\n private latestConfidenceMask: WebGLTexture | undefined = undefined;\n private lastFrameTime = -1;\n\n constructor(\n track: MediaStreamVideoTrack,\n private readonly options: BackgroundOptions = {},\n hooks: VideoTrackProcessorHooks = {},\n ) {\n super(track, hooks);\n }\n\n protected async initialize(): Promise<void> {\n this.webGlRenderer = new WebGLRenderer(this.canvas);\n\n await this.initializeSegmenter();\n }\n\n private async initializeSegmenter() {\n try {\n this.opts = await this.initializeSegmenterOptions();\n\n const basePath =\n this.options.basePath ||\n `https://unpkg.com/${packageName}@${version}/mediapipe`;\n\n const model =\n this.options.modelPath ||\n `${basePath}/models/selfie_segmenter_landscape.tflite`;\n\n const fileset = await FilesetResolver.forVisionTasks(`${basePath}/wasm`);\n\n this.segmenter = await ImageSegmenter.createFromOptions(fileset, {\n baseOptions: { modelAssetPath: model, delegate: 'GPU' },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: true,\n canvas: this.canvas,\n });\n\n this.isSegmenterReady = true;\n } catch (error) {\n this.isSegmenterReady = false;\n this.hooks.onError?.(error);\n throw error;\n }\n }\n\n protected async transform(frame: VideoFrame): Promise<VideoFrame> {\n const currentTime = frame.timestamp;\n const hasNewFrame = currentTime - this.lastFrameTime > 1_000;\n this.lastFrameTime = currentTime;\n\n if (hasNewFrame && this.isSegmenterReady && this.segmenter) {\n const start = performance.now();\n\n await this.runSegmentation(frame);\n\n this.webGlRenderer.render(\n frame,\n this.opts,\n this.latestCategoryMask,\n this.latestConfidenceMask,\n );\n\n this.updateStats(performance.now() - start);\n }\n\n return new VideoFrame(this.canvas, { timestamp: frame.timestamp });\n }\n\n private async runSegmentation(frame: VideoFrame): Promise<void> {\n if (!this.segmenter) return;\n\n return new Promise<void>((resolve) => {\n const timestamp = Math.floor(performance.now());\n this.segmenter!.segmentForVideo(frame, timestamp, (result) => {\n try {\n this.latestCategoryMask = result.categoryMask?.getAsWebGLTexture();\n this.latestConfidenceMask =\n result.confidenceMasks?.[0]?.getAsWebGLTexture();\n } catch (err) {\n console.error('[virtual-background] segmentation error:', err);\n this.hooks.onError?.(err);\n } finally {\n result.close();\n resolve();\n }\n });\n });\n }\n\n private async initializeSegmenterOptions(): Promise<SegmenterOptions> {\n const isSelfieMode = this.options.modelPath\n ? this.options.modelPath.includes('selfie_segmenter')\n : true;\n\n if (this.options.backgroundFilter === 'image') {\n const source = await this.loadBackground(this.options.backgroundImage);\n return {\n backgroundSource: source,\n bgBlur: 0,\n bgBlurRadius: 0,\n isSelfieMode,\n segmentationOptions: this.options.segmentationOptions,\n };\n }\n\n const blurLevel = this.options.backgroundBlurLevel;\n const strength =\n typeof blurLevel === 'string'\n ? BACKGROUND_BLUR_MAP[blurLevel]\n : Math.round(blurLevel ?? 5);\n\n return {\n backgroundSource: undefined,\n bgBlur: Math.min(strength * 1.5, 20),\n bgBlurRadius: Math.min(strength, 10),\n isSelfieMode,\n segmentationOptions: this.options.segmentationOptions,\n };\n }\n\n private async loadBackground(url?: string) {\n if (!url) return null;\n const result = await fetch(url, { signal: this.abortController.signal });\n if (!result.ok) {\n throw new Error(\n `[virtual-background] Failed to load background image: ${result.status} ${result.statusText}`,\n );\n }\n\n return {\n type: 'image',\n media: await createImageBitmap(await result.blob()),\n url,\n };\n }\n\n protected onFlush(): void {\n this.destroySegmenter();\n }\n\n protected onStop(): void {\n this.webGlRenderer?.close();\n this.destroySegmenter();\n }\n\n private destroySegmenter() {\n this.segmenter?.close();\n this.segmenter = null;\n this.isSegmenterReady = false;\n }\n\n protected get processorName() {\n return 'background-processor';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VirtualBackground.js","sources":["../../../../../src/VirtualBackground.ts"],"sourcesContent":["import {\n BACKGROUND_BLUR_MAP,\n BackgroundEffectOptions,\n BackgroundOptions,\n SegmenterOptions,\n VideoTrackProcessorHooks,\n} from './types';\nimport { FilesetResolver, ImageSegmenter } from '@mediapipe/tasks-vision';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { packageName, version } from './version';\nimport { BaseVideoProcessor } from './BaseVideoProcessor';\n\n/**\n * Wraps a video MediaStreamTrack in a real-time processing pipeline.\n * Incoming frames are processed through a transformer and re-emitted\n * on a new MediaStreamVideoTrack for downstream consumption.\n */\nexport class VirtualBackground extends BaseVideoProcessor {\n private segmenter: ImageSegmenter | null = null;\n private isSegmenterReady = false;\n private webGlRenderer!: WebGLRenderer;\n\n private opts!: SegmenterOptions;\n\n private latestCategoryMask: WebGLTexture | undefined = undefined;\n private latestConfidenceMask: WebGLTexture | undefined = undefined;\n private lastFrameTime = -1;\n\n constructor(\n track: MediaStreamVideoTrack,\n private options: BackgroundOptions = {},\n hooks: VideoTrackProcessorHooks = {},\n ) {\n super(track, hooks);\n }\n\n async updateOptions(options: BackgroundEffectOptions): Promise<void> {\n const { basePath, modelPath } = this.options;\n const previous = this.options;\n const next = { basePath, modelPath, ...options };\n this.options = next;\n try {\n const opts = await this.initializeSegmenterOptions();\n if (this.options === next) {\n const previousMedia = this.opts?.backgroundSource?.media;\n this.opts = opts;\n if (\n previousMedia instanceof ImageBitmap &&\n previousMedia !== opts.backgroundSource?.media\n ) {\n previousMedia.close();\n }\n }\n } catch (error) {\n if (this.options === next) this.options = previous;\n throw error;\n }\n }\n\n protected async initialize(): Promise<void> {\n this.webGlRenderer = new WebGLRenderer(this.canvas);\n\n await this.initializeSegmenter();\n }\n\n private async initializeSegmenter() {\n try {\n this.opts = await this.initializeSegmenterOptions();\n\n const basePath =\n this.options.basePath ||\n `https://unpkg.com/${packageName}@${version}/mediapipe`;\n\n const model =\n this.options.modelPath ||\n `${basePath}/models/selfie_segmenter_landscape.tflite`;\n\n const fileset = await FilesetResolver.forVisionTasks(`${basePath}/wasm`);\n\n this.segmenter = await ImageSegmenter.createFromOptions(fileset, {\n baseOptions: { modelAssetPath: model, delegate: 'GPU' },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: true,\n canvas: this.canvas,\n });\n\n this.isSegmenterReady = true;\n } catch (error) {\n this.isSegmenterReady = false;\n this.hooks.onError?.(error);\n throw error;\n }\n }\n\n protected async transform(frame: VideoFrame): Promise<VideoFrame> {\n const currentTime = frame.timestamp;\n const hasNewFrame = currentTime - this.lastFrameTime > 1_000;\n this.lastFrameTime = currentTime;\n\n if (hasNewFrame && this.isSegmenterReady && this.segmenter) {\n const start = performance.now();\n\n await this.runSegmentation(frame);\n\n this.webGlRenderer.render(\n frame,\n this.opts,\n this.latestCategoryMask,\n this.latestConfidenceMask,\n );\n\n this.updateStats(performance.now() - start);\n }\n\n return new VideoFrame(this.canvas, { timestamp: frame.timestamp });\n }\n\n private async runSegmentation(frame: VideoFrame): Promise<void> {\n if (!this.segmenter) return;\n\n return new Promise<void>((resolve) => {\n const timestamp = Math.floor(performance.now());\n this.segmenter!.segmentForVideo(frame, timestamp, (result) => {\n try {\n this.latestCategoryMask = result.categoryMask?.getAsWebGLTexture();\n this.latestConfidenceMask =\n result.confidenceMasks?.[0]?.getAsWebGLTexture();\n } catch (err) {\n console.error('[virtual-background] segmentation error:', err);\n this.hooks.onError?.(err);\n } finally {\n result.close();\n resolve();\n }\n });\n });\n }\n\n private async initializeSegmenterOptions(): Promise<SegmenterOptions> {\n const isSelfieMode = this.options.modelPath\n ? this.options.modelPath.includes('selfie_segmenter')\n : true;\n\n if (this.options.backgroundFilter === 'image') {\n const source = await this.loadBackground(this.options.backgroundImage);\n return {\n backgroundSource: source,\n bgBlur: 0,\n bgBlurRadius: 0,\n isSelfieMode,\n segmentationOptions: this.options.segmentationOptions,\n };\n }\n\n const blurLevel = this.options.backgroundBlurLevel;\n const strength =\n typeof blurLevel === 'string'\n ? BACKGROUND_BLUR_MAP[blurLevel]\n : Math.round(blurLevel ?? 5);\n\n return {\n backgroundSource: undefined,\n bgBlur: Math.min(strength * 1.5, 20),\n bgBlurRadius: Math.min(strength, 10),\n isSelfieMode,\n segmentationOptions: this.options.segmentationOptions,\n };\n }\n\n private async loadBackground(url?: string) {\n if (!url) return null;\n\n const current = this.opts?.backgroundSource;\n if (current?.url === url) return current;\n\n const result = await fetch(url, { signal: this.abortController.signal });\n if (!result.ok) {\n throw new Error(\n `[virtual-background] Failed to load background image: ${result.status} ${result.statusText}`,\n );\n }\n\n return {\n type: 'image',\n media: await createImageBitmap(await result.blob()),\n url,\n };\n }\n\n protected onFlush(): void {\n this.destroySegmenter();\n }\n\n protected onStop(): void {\n this.webGlRenderer?.close();\n this.destroySegmenter();\n }\n\n private destroySegmenter() {\n this.segmenter?.close();\n this.segmenter = null;\n this.isSegmenterReady = false;\n }\n\n protected get processorName() {\n return 'background-processor';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../../../src/types.ts"],"sourcesContent":["export type BackgroundSource = {\n type: string;\n media?: ImageBitmap | ReadableStream;\n url: string;\n video?: HTMLVideoElement;\n track?: MediaStreamTrack;\n};\nexport type BackgroundFilter = 'blur' | 'image';\nexport type BackgroundBlurLevel = 'low' | 'medium' | 'high' | number;\n\n/**\n * Options for controlling the segmentation mask smoothing.\n * These values are passed to the WebGL shader that blends\n * consecutive segmentation masks over time.\n */\nexport interface SegmentationOptions {\n /**\n * Controls how fast the mask adapts to new segmentation results.\n * Higher values make the mask react faster but may cause flickering.\n * Lower values produce smoother transitions but increase latency.\n * Value should be between 0 and 1.\n * @default 0.8\n */\n smoothingFactor?: number;\n\n /**\n * Lower edge of the smoothstep function applied to the confidence mask.\n * Confidence values below this are mapped to 0 (background).\n * Value should be between 0 and 1, and less than smoothstepMax.\n * @default 0.6\n */\n smoothstepMin?: number;\n\n /**\n * Upper edge of the smoothstep function applied to the confidence mask.\n * Confidence values above this are mapped to 1 (foreground).\n * Value should be between 0 and 1, and greater than smoothstepMin.\n * @default 0.9\n */\n smoothstepMax?: number;\n}\n\nexport interface SegmenterOptions {\n backgroundSource?: BackgroundSource | null;\n bgBlur: number;\n bgBlurRadius: number;\n isSelfieMode: boolean;\n segmentationOptions?: SegmentationOptions;\n}\n/**\n * Static configuration for the processor, defining which background\n * effect should be applied and how it should behave.\n */\nexport interface BackgroundOptions {\n basePath?: string;\n modelPath?: string;\n
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../../src/types.ts"],"sourcesContent":["export type BackgroundSource = {\n type: string;\n media?: ImageBitmap | ReadableStream;\n url: string;\n video?: HTMLVideoElement;\n track?: MediaStreamTrack;\n};\nexport type BackgroundFilter = 'blur' | 'image';\nexport type BackgroundBlurLevel = 'low' | 'medium' | 'high' | number;\n\n/**\n * Options for controlling the segmentation mask smoothing.\n * These values are passed to the WebGL shader that blends\n * consecutive segmentation masks over time.\n */\nexport interface SegmentationOptions {\n /**\n * Controls how fast the mask adapts to new segmentation results.\n * Higher values make the mask react faster but may cause flickering.\n * Lower values produce smoother transitions but increase latency.\n * Value should be between 0 and 1.\n * @default 0.8\n */\n smoothingFactor?: number;\n\n /**\n * Lower edge of the smoothstep function applied to the confidence mask.\n * Confidence values below this are mapped to 0 (background).\n * Value should be between 0 and 1, and less than smoothstepMax.\n * @default 0.6\n */\n smoothstepMin?: number;\n\n /**\n * Upper edge of the smoothstep function applied to the confidence mask.\n * Confidence values above this are mapped to 1 (foreground).\n * Value should be between 0 and 1, and greater than smoothstepMin.\n * @default 0.9\n */\n smoothstepMax?: number;\n}\n\nexport interface SegmenterOptions {\n backgroundSource?: BackgroundSource | null;\n bgBlur: number;\n bgBlurRadius: number;\n isSelfieMode: boolean;\n segmentationOptions?: SegmentationOptions;\n}\nexport interface BackgroundEffectOptions {\n backgroundFilter?: BackgroundFilter;\n backgroundBlurLevel?: BackgroundBlurLevel;\n backgroundImage?: string | undefined;\n segmentationOptions?: SegmentationOptions;\n}\n\n/**\n * Static configuration for the processor, defining which background\n * effect should be applied and how it should behave.\n */\nexport interface BackgroundOptions extends BackgroundEffectOptions {\n basePath?: string;\n modelPath?: string;\n}\n\n/**\n * Performance statistics for video processing.\n */\nexport interface PerformanceStats {\n delay: number;\n fps: number;\n timestamp: number;\n}\n\n/**\n * Runtime hooks for handling lifecycle or error events.\n */\nexport interface VideoTrackProcessorHooks {\n onError?: (error: unknown) => void;\n onStats?: (stats: PerformanceStats) => void;\n}\n\n/**\n * Maps blur level to blur strength values.\n */\nexport const BACKGROUND_BLUR_MAP: Record<'low' | 'medium' | 'high', number> = {\n low: 3,\n medium: 5,\n high: 7,\n};\n"],"names":[],"mappings":"AAkFA;;AAEG;AACI,MAAM,mBAAmB,GAA8C;AAC5E,IAAA,GAAG,EAAE,CAAC;AACN,IAAA,MAAM,EAAE,CAAC;AACT,IAAA,IAAI,EAAE,CAAC;;;;;"}
|
package/dist/esm/src/version.js
CHANGED
package/dist/index.cjs.js
CHANGED
|
@@ -1561,7 +1561,7 @@ const createTFLiteSIMDModule = (__Module) => {
|
|
|
1561
1561
|
return __Module.ready;
|
|
1562
1562
|
};
|
|
1563
1563
|
|
|
1564
|
-
const version = "0.
|
|
1564
|
+
const version = "0.9.0";
|
|
1565
1565
|
const packageName = "@stream-io/video-filters-web";
|
|
1566
1566
|
|
|
1567
1567
|
// @ts-expect-error - module is not declared
|
|
@@ -2593,6 +2593,28 @@ class VirtualBackground extends BaseVideoProcessor {
|
|
|
2593
2593
|
super(track, hooks);
|
|
2594
2594
|
this.options = options;
|
|
2595
2595
|
}
|
|
2596
|
+
async updateOptions(options) {
|
|
2597
|
+
const { basePath, modelPath } = this.options;
|
|
2598
|
+
const previous = this.options;
|
|
2599
|
+
const next = { basePath, modelPath, ...options };
|
|
2600
|
+
this.options = next;
|
|
2601
|
+
try {
|
|
2602
|
+
const opts = await this.initializeSegmenterOptions();
|
|
2603
|
+
if (this.options === next) {
|
|
2604
|
+
const previousMedia = this.opts?.backgroundSource?.media;
|
|
2605
|
+
this.opts = opts;
|
|
2606
|
+
if (previousMedia instanceof ImageBitmap &&
|
|
2607
|
+
previousMedia !== opts.backgroundSource?.media) {
|
|
2608
|
+
previousMedia.close();
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
catch (error) {
|
|
2613
|
+
if (this.options === next)
|
|
2614
|
+
this.options = previous;
|
|
2615
|
+
throw error;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2596
2618
|
async initialize() {
|
|
2597
2619
|
this.webGlRenderer = new WebGLRenderer(this.canvas);
|
|
2598
2620
|
await this.initializeSegmenter();
|
|
@@ -2683,6 +2705,9 @@ class VirtualBackground extends BaseVideoProcessor {
|
|
|
2683
2705
|
async loadBackground(url) {
|
|
2684
2706
|
if (!url)
|
|
2685
2707
|
return null;
|
|
2708
|
+
const current = this.opts?.backgroundSource;
|
|
2709
|
+
if (current?.url === url)
|
|
2710
|
+
return current;
|
|
2686
2711
|
const result = await fetch(url, { signal: this.abortController.signal });
|
|
2687
2712
|
if (!result.ok) {
|
|
2688
2713
|
throw new Error(`[virtual-background] Failed to load background image: ${result.status} ${result.statusText}`);
|