@livekit/track-processors 0.2.7 → 0.3.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
@@ -3,33 +3,47 @@
3
3
  ## Install
4
4
 
5
5
  ```
6
- yarn add @livekit/track-processors
6
+ npm add @livekit/track-processors
7
7
  ```
8
8
 
9
9
  ## Usage of prebuilt processors
10
10
 
11
+ ### Available processors
12
+
11
13
  This package exposes `BackgroundBlur` and `VirtualBackground` as pre-prepared processor pipelines.
12
14
 
15
+ - `BackgroundBlur(blurRadius)`
16
+ - `VirtualBackground(imagePath)`
17
+
18
+ ### Usage example
19
+
13
20
  ```ts
14
21
  import { BackgroundBlur } from '@livekit/track-processors';
15
22
 
16
23
  const videoTrack = await createLocalVideoTrack();
17
- await videoTrack.setProcessor(BackgroundBlur(10));
24
+ const blur = BackgroundBlur(10);
25
+ await videoTrack.setProcessor(blur);
18
26
  room.localParticipant.publishTrack(videoTrack);
19
27
 
20
28
  async function disableBackgroundBlur() {
21
29
  await videoTrack.stopProcessor();
22
30
  }
31
+
32
+ async updateBlurRadius(radius) {
33
+ return blur.updateTransformerOptions({blurRadius: blur})
34
+ }
35
+
36
+
23
37
  ```
24
38
 
25
- ## Building your own processors
39
+ ## Developing your own processors
26
40
 
27
- A track processor consists of one or multiple transformers.
41
+ A track processor is instantiated with a Transformer.
28
42
 
29
43
  ```ts
30
44
  // src/index.ts
31
45
  export const VirtualBackground = (imagePath: string) => {
32
- const pipeline = new ProcessorPipeline([new BackgroundTransformer({ imagePath })]);
46
+ const pipeline = new ProcessorWrapper(new BackgroundTransformer({ imagePath }));
33
47
  return pipeline;
34
48
  };
35
49
  ```
package/dist/index.js CHANGED
@@ -18,14 +18,15 @@ var __spreadValues = (a, b) => {
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
 
21
- // src/ProcessorPipeline.ts
22
- var ProcessorPipeline = class {
21
+ // src/ProcessorWrapper.ts
22
+ var ProcessorWrapper = class {
23
23
  static get isSupported() {
24
24
  return typeof MediaStreamTrackGenerator !== "undefined" && typeof MediaStreamTrackProcessor !== "undefined";
25
25
  }
26
- constructor(transformers, name) {
26
+ constructor(transformer, name) {
27
27
  this.name = name;
28
- this.transformers = transformers;
28
+ this.transformer = transformer;
29
+ this.transformer.restart;
29
30
  }
30
31
  async setup(opts) {
31
32
  var _a, _b, _c, _d;
@@ -63,13 +64,11 @@ var ProcessorPipeline = class {
63
64
  throw new TypeError("Expected both canvas and processor to be defined after setup");
64
65
  }
65
66
  let readableStream = this.processor.readable;
66
- for (const transformer of this.transformers) {
67
- await transformer.init({
68
- outputCanvas: this.canvas,
69
- inputElement: this.sourceDummy
70
- });
71
- readableStream = readableStream.pipeThrough(transformer.transformer);
72
- }
67
+ await this.transformer.init({
68
+ outputCanvas: this.canvas,
69
+ inputElement: this.sourceDummy
70
+ });
71
+ readableStream = readableStream.pipeThrough(this.transformer.transformer);
73
72
  readableStream.pipeTo(this.trackGenerator.writable).catch((e) => console.error("error when trying to pipe", e)).finally(() => this.destroy());
74
73
  this.processedTrack = this.trackGenerator;
75
74
  }
@@ -77,11 +76,15 @@ var ProcessorPipeline = class {
77
76
  await this.destroy();
78
77
  return this.init(opts);
79
78
  }
79
+ async restartTransformer(...options) {
80
+ this.transformer.restart(options[0]);
81
+ }
82
+ async updateTransformerOptions(...options) {
83
+ this.transformer.update(options[0]);
84
+ }
80
85
  async destroy() {
81
86
  var _a;
82
- for (const transformer of this.transformers) {
83
- await transformer.destroy();
84
- }
87
+ await this.transformer.destroy();
85
88
  (_a = this.trackGenerator) == null ? void 0 : _a.stop();
86
89
  }
87
90
  };
@@ -92,7 +95,7 @@ var _tasksvision = require('@mediapipe/tasks-vision'); var vision = _interopRequ
92
95
  // package.json
93
96
  var dependencies = {
94
97
  "@mediapipe/holistic": "0.5.1675471629",
95
- "@mediapipe/tasks-vision": "0.10.5"
98
+ "@mediapipe/tasks-vision": "0.10.9"
96
99
  };
97
100
 
98
101
  // src/transformers/VideoTransformer.ts
@@ -137,11 +140,7 @@ var BackgroundProcessor = class extends VideoTransformer {
137
140
  super();
138
141
  this.backgroundImage = null;
139
142
  this.options = opts;
140
- if (opts.blurRadius) {
141
- this.blurRadius = opts.blurRadius;
142
- } else if (opts.imagePath) {
143
- this.loadBackground(opts.imagePath);
144
- }
143
+ this.update(opts);
145
144
  }
146
145
  static get isSupported() {
147
146
  return typeof OffscreenCanvas !== "undefined";
@@ -207,6 +206,14 @@ var BackgroundProcessor = class extends VideoTransformer {
207
206
  frame.close();
208
207
  }
209
208
  }
209
+ async update(opts) {
210
+ this.options = opts;
211
+ if (opts.blurRadius) {
212
+ this.blurRadius = opts.blurRadius;
213
+ } else if (opts.imagePath) {
214
+ await this.loadBackground(opts.imagePath);
215
+ }
216
+ }
210
217
  async drawVirtualBackground(frame) {
211
218
  var _a;
212
219
  if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo)
@@ -279,45 +286,20 @@ function maskToBitmap(mask, videoWidth, videoHeight) {
279
286
  return createImageBitmap(dataNew);
280
287
  }
281
288
 
282
- // src/transformers/DummyTransformer.ts
283
- var DummyTransformer = class extends VideoTransformer {
284
- async transform(frame, controller) {
285
- controller.enqueue(frame);
286
- }
287
- async destroy() {
288
- }
289
- };
290
-
291
289
  // src/index.ts
292
290
  var BackgroundBlur = (blurRadius = 10, segmenterOptions) => {
293
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
294
- if (!isPipelineSupported) {
295
- throw new Error("pipeline is not supported in this browser");
296
- }
297
- const pipeline = new ProcessorPipeline(
298
- [new BackgroundProcessor({ blurRadius, segmenterOptions })],
299
- "background-blur"
300
- );
301
- return pipeline;
291
+ return BackgroundProcessor2({ blurRadius, segmenterOptions }, "background-blur");
302
292
  };
303
293
  var VirtualBackground = (imagePath, segmenterOptions) => {
304
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
305
- if (!isPipelineSupported) {
306
- throw new Error("pipeline is not supported in this browser");
307
- }
308
- const pipeline = new ProcessorPipeline(
309
- [new BackgroundProcessor({ imagePath, segmenterOptions })],
310
- "virtual-background"
311
- );
312
- return pipeline;
294
+ return BackgroundProcessor2({ imagePath, segmenterOptions }, "virtual-background");
313
295
  };
314
- var Dummy = () => {
315
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
316
- if (!isPipelineSupported) {
317
- throw new Error("pipeline is not supported in this browser");
296
+ var BackgroundProcessor2 = (options, name = "background-processor") => {
297
+ const isProcessorSupported = ProcessorWrapper.isSupported && BackgroundProcessor.isSupported;
298
+ if (!isProcessorSupported) {
299
+ throw new Error("processor is not supported in this browser");
318
300
  }
319
- const pipeline = new ProcessorPipeline([new DummyTransformer()], "dummy");
320
- return pipeline;
301
+ const processor = new ProcessorWrapper(new BackgroundProcessor(options), name);
302
+ return processor;
321
303
  };
322
304
 
323
305
 
@@ -325,5 +307,6 @@ var Dummy = () => {
325
307
 
326
308
 
327
309
 
328
- exports.BackgroundBlur = BackgroundBlur; exports.Dummy = Dummy; exports.ProcessorPipeline = ProcessorPipeline; exports.VideoTransformer = VideoTransformer; exports.VirtualBackground = VirtualBackground;
310
+
311
+ exports.BackgroundBlur = BackgroundBlur; exports.BackgroundProcessor = BackgroundProcessor2; exports.BackgroundTransformer = BackgroundProcessor; exports.ProcessorWrapper = ProcessorWrapper; exports.VideoTransformer = VideoTransformer; exports.VirtualBackground = VirtualBackground;
329
312
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ProcessorPipeline.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/transformers/VideoTransformer.ts","../src/transformers/DummyTransformer.ts","../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { VideoTrackTransformer } from './transformers';\n\nexport default class ProcessorPipeline implements TrackProcessor<Track.Kind> {\n static get isSupported() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n sourceSettings?: MediaTrackSettings;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformers: Array<VideoTrackTransformer>;\n\n constructor(transformers: Array<VideoTrackTransformer>, name: string) {\n this.name = name;\n this.transformers = transformers;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n const origConstraints = this.source.getConstraints();\n await this.source.applyConstraints({\n ...origConstraints,\n // @ts-expect-error when a mediastream track is resized and/or cropped, the `VideoFrame` will have a coded height/width of the original video size\n // this leads to a shift of the underlying video as the frame itself is being rendered with the coded size\n // but image segmentation is based on the display dimensions (-> the cropped version)\n // in order to prevent this, we force the resize mode to \"none\"\n resizeMode: 'none',\n });\n this.sourceSettings = this.source.getSettings();\n this.sourceDummy = opts.element;\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = this.sourceSettings.height ?? 300;\n this.sourceDummy.width = this.sourceSettings.width ?? 300;\n }\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n // TODO explore if we can do all the processing work in a webworker\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n\n this.canvas = new OffscreenCanvas(\n this.sourceSettings.width ?? 300,\n this.sourceSettings.height ?? 300,\n );\n }\n\n async init(opts: ProcessorOptions<Track.Kind>) {\n await this.setup(opts);\n if (!this.canvas || !this.processor || !this.trackGenerator) {\n throw new TypeError('Expected both canvas and processor to be defined after setup');\n }\n\n let readableStream = this.processor.readable;\n for (const transformer of this.transformers) {\n await transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n readableStream = readableStream.pipeThrough(transformer!.transformer!);\n }\n readableStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>) {\n await this.destroy();\n return this.init(opts);\n }\n\n async destroy() {\n for (const transformer of this.transformers) {\n await transformer.destroy();\n }\n this.trackGenerator?.stop();\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 SegmenterBaseOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterBaseOptions;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer {\n static get isSupported() {\n return typeof OffscreenCanvas !== 'undefined';\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n blurRadius?: number;\n\n options: BackgroundOptions;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n if (opts.blurRadius) {\n this.blurRadius = opts.blurRadius;\n } else if (opts.imagePath) {\n this.loadBackground(opts.imagePath);\n }\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\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 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // this.loadBackground(opts.backgroundUrl).catch((e) => console.error(e));\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.backgroundImage = imageData;\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n let startTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(\n this.inputVideo!,\n startTimeMs,\n (result) => (this.segmentationResults = result),\n );\n\n if (this.blurRadius) {\n await this.blurBackground(frame);\n } else {\n await this.drawVirtualBackground(frame);\n }\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || Date.now(),\n });\n controller.enqueue(newFrame);\n } finally {\n frame.close();\n }\n }\n\n async drawVirtualBackground(frame: VideoFrame) {\n if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo) return;\n // this.ctx.save();\n // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n if (this.segmentationResults?.categoryMask) {\n this.ctx.filter = 'blur(10px)';\n this.ctx.globalCompositeOperation = 'copy';\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-in';\n if (this.backgroundImage) {\n this.ctx.drawImage(\n this.backgroundImage,\n 0,\n 0,\n this.backgroundImage.width,\n this.backgroundImage.height,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n } else {\n this.ctx.fillStyle = '#00FF00';\n this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.ctx.globalCompositeOperation = 'destination-over';\n }\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n // this.ctx.restore();\n }\n\n async blurBackground(frame: VideoFrame) {\n if (\n !this.ctx ||\n !this.canvas ||\n !this.segmentationResults?.categoryMask?.canvas ||\n !this.inputVideo\n ) {\n return;\n }\n\n this.ctx.save();\n this.ctx.globalCompositeOperation = 'copy';\n\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n\n this.ctx.filter = 'blur(3px)';\n this.ctx.globalCompositeOperation = 'copy';\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-out';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.globalCompositeOperation = 'destination-over';\n this.ctx.filter = `blur(${this.blurRadius}px)`;\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.restore();\n }\n}\n\nfunction maskToBitmap(\n mask: vision.MPMask,\n videoWidth: number,\n videoHeight: number,\n): Promise<ImageBitmap> {\n const dataArray: Uint8ClampedArray = new Uint8ClampedArray(videoWidth * videoHeight * 4);\n const result = mask.getAsUint8Array();\n for (let i = 0; i < result.length; i += 1) {\n dataArray[i * 4] = result[i];\n dataArray[i * 4 + 1] = result[i];\n dataArray[i * 4 + 2] = result[i];\n dataArray[i * 4 + 3] = result[i];\n }\n const dataNew = new ImageData(dataArray, videoWidth, videoHeight);\n\n return createImageBitmap(dataNew);\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.2.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\": \"git@github.com:livekit/livekit-track-processors.git\",\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\": \"yarn 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/holistic\": \"0.5.1675471629\",\n \"@mediapipe/tasks-vision\": \"0.10.5\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.1.1\",\n \"@types/dom-mediacapture-transform\": \"^0.1.6\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@typescript-eslint/eslint-plugin\": \"^4.31.2\",\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.0.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"livekit-client\": \"^1.12.0\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.1.0\",\n \"typescript\": \"^5.0.4\",\n \"vite\": \"^4.3.8\"\n }\n}\n","import { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer implements VideoTrackTransformer {\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas;\n\n ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\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 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 }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.ctx = this.canvas.getContext('2d') || undefined;\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.ctx = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n}\n","import VideoTransformer from './VideoTransformer';\n\nexport default class DummyTransformer extends VideoTransformer {\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n controller.enqueue(frame);\n }\n\n async destroy() {\n // nothing to do\n }\n}\n","import ProcessorPipeline from './ProcessorPipeline';\nimport BackgroundTransformer, { SegmenterBaseOptions } from './transformers/BackgroundTransformer';\nimport DummyTransformer from './transformers/DummyTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport { ProcessorPipeline };\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterBaseOptions,\n) => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline(\n [new BackgroundTransformer({ blurRadius, segmenterOptions })],\n 'background-blur',\n );\n return pipeline;\n};\n\nexport const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterBaseOptions) => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline(\n [new BackgroundTransformer({ imagePath, segmenterOptions })],\n 'virtual-background',\n );\n return pipeline;\n};\n\nexport const Dummy = () => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline([new DummyTransformer()], 'dummy');\n return pipeline;\n};\n"]}
1
+ {"version":3,"sources":["../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/transformers/VideoTransformer.ts","../src/index.ts"],"names":["BackgroundProcessor"],"mappings":"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","sourcesContent":["import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n static get isSupported() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n sourceSettings?: MediaTrackSettings;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n constructor(transformer: TrackTransformer<TransformerOptions>, name: string) {\n this.name = name;\n this.transformer = transformer;\n this.transformer.restart;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n const origConstraints = this.source.getConstraints();\n await this.source.applyConstraints({\n ...origConstraints,\n // @ts-expect-error when a mediastream track is resized and/or cropped, the `VideoFrame` will have a coded height/width of the original video size\n // this leads to a shift of the underlying video as the frame itself is being rendered with the coded size\n // but image segmentation is based on the display dimensions (-> the cropped version)\n // in order to prevent this, we force the resize mode to \"none\"\n resizeMode: 'none',\n });\n this.sourceSettings = this.source.getSettings();\n this.sourceDummy = opts.element;\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = this.sourceSettings.height ?? 300;\n this.sourceDummy.width = this.sourceSettings.width ?? 300;\n }\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n // TODO explore if we can do all the processing work in a webworker\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n\n this.canvas = new OffscreenCanvas(\n this.sourceSettings.width ?? 300,\n this.sourceSettings.height ?? 300,\n );\n }\n\n async init(opts: ProcessorOptions<Track.Kind>) {\n await this.setup(opts);\n if (!this.canvas || !this.processor || !this.trackGenerator) {\n throw new TypeError('Expected both canvas and processor to be defined after setup');\n }\n\n let readableStream = this.processor.readable;\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n readableStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n readableStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>) {\n await this.destroy();\n return 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 this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n this.transformer.update(options[0]);\n }\n\n async destroy() {\n await this.transformer.destroy();\n this.trackGenerator?.stop();\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 type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return typeof OffscreenCanvas !== 'undefined';\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n blurRadius?: number;\n\n options: BackgroundOptions;\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 await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\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 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // this.loadBackground(opts.backgroundUrl).catch((e) => console.error(e));\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.backgroundImage = imageData;\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n let startTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(\n this.inputVideo!,\n startTimeMs,\n (result) => (this.segmentationResults = result),\n );\n\n if (this.blurRadius) {\n await this.blurBackground(frame);\n } else {\n await this.drawVirtualBackground(frame);\n }\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || Date.now(),\n });\n controller.enqueue(newFrame);\n } finally {\n frame.close();\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = opts;\n if (opts.blurRadius) {\n this.blurRadius = opts.blurRadius;\n } else if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n }\n }\n\n async drawVirtualBackground(frame: VideoFrame) {\n if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo) return;\n // this.ctx.save();\n // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n if (this.segmentationResults?.categoryMask) {\n this.ctx.filter = 'blur(10px)';\n this.ctx.globalCompositeOperation = 'copy';\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-in';\n if (this.backgroundImage) {\n this.ctx.drawImage(\n this.backgroundImage,\n 0,\n 0,\n this.backgroundImage.width,\n this.backgroundImage.height,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n } else {\n this.ctx.fillStyle = '#00FF00';\n this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.ctx.globalCompositeOperation = 'destination-over';\n }\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n // this.ctx.restore();\n }\n\n async blurBackground(frame: VideoFrame) {\n if (\n !this.ctx ||\n !this.canvas ||\n !this.segmentationResults?.categoryMask?.canvas ||\n !this.inputVideo\n ) {\n return;\n }\n\n this.ctx.save();\n this.ctx.globalCompositeOperation = 'copy';\n\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n\n this.ctx.filter = 'blur(3px)';\n this.ctx.globalCompositeOperation = 'copy';\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-out';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.globalCompositeOperation = 'destination-over';\n this.ctx.filter = `blur(${this.blurRadius}px)`;\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.restore();\n }\n}\n\nfunction maskToBitmap(\n mask: vision.MPMask,\n videoWidth: number,\n videoHeight: number,\n): Promise<ImageBitmap> {\n const dataArray: Uint8ClampedArray = new Uint8ClampedArray(videoWidth * videoHeight * 4);\n const result = mask.getAsUint8Array();\n for (let i = 0; i < result.length; i += 1) {\n dataArray[i * 4] = result[i];\n dataArray[i * 4 + 1] = result[i];\n dataArray[i * 4 + 2] = result[i];\n dataArray[i * 4 + 3] = result[i];\n }\n const dataNew = new ImageData(dataArray, videoWidth, videoHeight);\n\n return createImageBitmap(dataNew);\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.3.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\": \"git@github.com:livekit/livekit-track-processors.git\",\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/holistic\": \"0.5.1675471629\",\n \"@mediapipe/tasks-vision\": \"0.10.9\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.1.1\",\n \"@types/dom-mediacapture-transform\": \"^0.1.6\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@typescript-eslint/eslint-plugin\": \"^4.31.2\",\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.0.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"livekit-client\": \"^1.12.0\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.1.0\",\n \"typescript\": \"^5.0.4\",\n \"vite\": \"^4.3.8\"\n }\n}\n","import { 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;\n\n ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\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 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 }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.ctx = this.canvas.getContext('2d') || undefined;\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.ctx = 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 from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport { ProcessorWrapper, type BackgroundOptions, type SegmenterOptions, BackgroundTransformer };\n\nexport const BackgroundBlur = (blurRadius: number = 10, segmenterOptions?: SegmenterOptions) => {\n return BackgroundProcessor({ blurRadius, segmenterOptions }, 'background-blur');\n};\n\nexport const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterOptions) => {\n return BackgroundProcessor({ imagePath, segmenterOptions }, 'virtual-background');\n};\n\nexport const BackgroundProcessor = (options: BackgroundOptions, name = 'background-processor') => {\n const isProcessorSupported = ProcessorWrapper.isSupported && BackgroundTransformer.isSupported;\n if (!isProcessorSupported) {\n throw new Error('processor is not supported in this browser');\n }\n const processor = new ProcessorWrapper(new BackgroundTransformer(options), name);\n return processor;\n};\n"]}
package/dist/index.mjs CHANGED
@@ -18,14 +18,15 @@ var __spreadValues = (a, b) => {
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
20
 
21
- // src/ProcessorPipeline.ts
22
- var ProcessorPipeline = class {
21
+ // src/ProcessorWrapper.ts
22
+ var ProcessorWrapper = class {
23
23
  static get isSupported() {
24
24
  return typeof MediaStreamTrackGenerator !== "undefined" && typeof MediaStreamTrackProcessor !== "undefined";
25
25
  }
26
- constructor(transformers, name) {
26
+ constructor(transformer, name) {
27
27
  this.name = name;
28
- this.transformers = transformers;
28
+ this.transformer = transformer;
29
+ this.transformer.restart;
29
30
  }
30
31
  async setup(opts) {
31
32
  var _a, _b, _c, _d;
@@ -63,13 +64,11 @@ var ProcessorPipeline = class {
63
64
  throw new TypeError("Expected both canvas and processor to be defined after setup");
64
65
  }
65
66
  let readableStream = this.processor.readable;
66
- for (const transformer of this.transformers) {
67
- await transformer.init({
68
- outputCanvas: this.canvas,
69
- inputElement: this.sourceDummy
70
- });
71
- readableStream = readableStream.pipeThrough(transformer.transformer);
72
- }
67
+ await this.transformer.init({
68
+ outputCanvas: this.canvas,
69
+ inputElement: this.sourceDummy
70
+ });
71
+ readableStream = readableStream.pipeThrough(this.transformer.transformer);
73
72
  readableStream.pipeTo(this.trackGenerator.writable).catch((e) => console.error("error when trying to pipe", e)).finally(() => this.destroy());
74
73
  this.processedTrack = this.trackGenerator;
75
74
  }
@@ -77,11 +76,15 @@ var ProcessorPipeline = class {
77
76
  await this.destroy();
78
77
  return this.init(opts);
79
78
  }
79
+ async restartTransformer(...options) {
80
+ this.transformer.restart(options[0]);
81
+ }
82
+ async updateTransformerOptions(...options) {
83
+ this.transformer.update(options[0]);
84
+ }
80
85
  async destroy() {
81
86
  var _a;
82
- for (const transformer of this.transformers) {
83
- await transformer.destroy();
84
- }
87
+ await this.transformer.destroy();
85
88
  (_a = this.trackGenerator) == null ? void 0 : _a.stop();
86
89
  }
87
90
  };
@@ -92,7 +95,7 @@ import * as vision from "@mediapipe/tasks-vision";
92
95
  // package.json
93
96
  var dependencies = {
94
97
  "@mediapipe/holistic": "0.5.1675471629",
95
- "@mediapipe/tasks-vision": "0.10.5"
98
+ "@mediapipe/tasks-vision": "0.10.9"
96
99
  };
97
100
 
98
101
  // src/transformers/VideoTransformer.ts
@@ -137,11 +140,7 @@ var BackgroundProcessor = class extends VideoTransformer {
137
140
  super();
138
141
  this.backgroundImage = null;
139
142
  this.options = opts;
140
- if (opts.blurRadius) {
141
- this.blurRadius = opts.blurRadius;
142
- } else if (opts.imagePath) {
143
- this.loadBackground(opts.imagePath);
144
- }
143
+ this.update(opts);
145
144
  }
146
145
  static get isSupported() {
147
146
  return typeof OffscreenCanvas !== "undefined";
@@ -207,6 +206,14 @@ var BackgroundProcessor = class extends VideoTransformer {
207
206
  frame.close();
208
207
  }
209
208
  }
209
+ async update(opts) {
210
+ this.options = opts;
211
+ if (opts.blurRadius) {
212
+ this.blurRadius = opts.blurRadius;
213
+ } else if (opts.imagePath) {
214
+ await this.loadBackground(opts.imagePath);
215
+ }
216
+ }
210
217
  async drawVirtualBackground(frame) {
211
218
  var _a;
212
219
  if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo)
@@ -279,50 +286,26 @@ function maskToBitmap(mask, videoWidth, videoHeight) {
279
286
  return createImageBitmap(dataNew);
280
287
  }
281
288
 
282
- // src/transformers/DummyTransformer.ts
283
- var DummyTransformer = class extends VideoTransformer {
284
- async transform(frame, controller) {
285
- controller.enqueue(frame);
286
- }
287
- async destroy() {
288
- }
289
- };
290
-
291
289
  // src/index.ts
292
290
  var BackgroundBlur = (blurRadius = 10, segmenterOptions) => {
293
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
294
- if (!isPipelineSupported) {
295
- throw new Error("pipeline is not supported in this browser");
296
- }
297
- const pipeline = new ProcessorPipeline(
298
- [new BackgroundProcessor({ blurRadius, segmenterOptions })],
299
- "background-blur"
300
- );
301
- return pipeline;
291
+ return BackgroundProcessor2({ blurRadius, segmenterOptions }, "background-blur");
302
292
  };
303
293
  var VirtualBackground = (imagePath, segmenterOptions) => {
304
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
305
- if (!isPipelineSupported) {
306
- throw new Error("pipeline is not supported in this browser");
307
- }
308
- const pipeline = new ProcessorPipeline(
309
- [new BackgroundProcessor({ imagePath, segmenterOptions })],
310
- "virtual-background"
311
- );
312
- return pipeline;
294
+ return BackgroundProcessor2({ imagePath, segmenterOptions }, "virtual-background");
313
295
  };
314
- var Dummy = () => {
315
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundProcessor.isSupported;
316
- if (!isPipelineSupported) {
317
- throw new Error("pipeline is not supported in this browser");
296
+ var BackgroundProcessor2 = (options, name = "background-processor") => {
297
+ const isProcessorSupported = ProcessorWrapper.isSupported && BackgroundProcessor.isSupported;
298
+ if (!isProcessorSupported) {
299
+ throw new Error("processor is not supported in this browser");
318
300
  }
319
- const pipeline = new ProcessorPipeline([new DummyTransformer()], "dummy");
320
- return pipeline;
301
+ const processor = new ProcessorWrapper(new BackgroundProcessor(options), name);
302
+ return processor;
321
303
  };
322
304
  export {
323
305
  BackgroundBlur,
324
- Dummy,
325
- ProcessorPipeline,
306
+ BackgroundProcessor2 as BackgroundProcessor,
307
+ BackgroundProcessor as BackgroundTransformer,
308
+ ProcessorWrapper,
326
309
  VideoTransformer,
327
310
  VirtualBackground
328
311
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ProcessorPipeline.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/transformers/VideoTransformer.ts","../src/transformers/DummyTransformer.ts","../src/index.ts"],"sourcesContent":["import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { VideoTrackTransformer } from './transformers';\n\nexport default class ProcessorPipeline implements TrackProcessor<Track.Kind> {\n static get isSupported() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n sourceSettings?: MediaTrackSettings;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformers: Array<VideoTrackTransformer>;\n\n constructor(transformers: Array<VideoTrackTransformer>, name: string) {\n this.name = name;\n this.transformers = transformers;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n const origConstraints = this.source.getConstraints();\n await this.source.applyConstraints({\n ...origConstraints,\n // @ts-expect-error when a mediastream track is resized and/or cropped, the `VideoFrame` will have a coded height/width of the original video size\n // this leads to a shift of the underlying video as the frame itself is being rendered with the coded size\n // but image segmentation is based on the display dimensions (-> the cropped version)\n // in order to prevent this, we force the resize mode to \"none\"\n resizeMode: 'none',\n });\n this.sourceSettings = this.source.getSettings();\n this.sourceDummy = opts.element;\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = this.sourceSettings.height ?? 300;\n this.sourceDummy.width = this.sourceSettings.width ?? 300;\n }\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n // TODO explore if we can do all the processing work in a webworker\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n\n this.canvas = new OffscreenCanvas(\n this.sourceSettings.width ?? 300,\n this.sourceSettings.height ?? 300,\n );\n }\n\n async init(opts: ProcessorOptions<Track.Kind>) {\n await this.setup(opts);\n if (!this.canvas || !this.processor || !this.trackGenerator) {\n throw new TypeError('Expected both canvas and processor to be defined after setup');\n }\n\n let readableStream = this.processor.readable;\n for (const transformer of this.transformers) {\n await transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n readableStream = readableStream.pipeThrough(transformer!.transformer!);\n }\n readableStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>) {\n await this.destroy();\n return this.init(opts);\n }\n\n async destroy() {\n for (const transformer of this.transformers) {\n await transformer.destroy();\n }\n this.trackGenerator?.stop();\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 SegmenterBaseOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;\n\nexport type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterBaseOptions;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer {\n static get isSupported() {\n return typeof OffscreenCanvas !== 'undefined';\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n blurRadius?: number;\n\n options: BackgroundOptions;\n\n constructor(opts: BackgroundOptions) {\n super();\n this.options = opts;\n if (opts.blurRadius) {\n this.blurRadius = opts.blurRadius;\n } else if (opts.imagePath) {\n this.loadBackground(opts.imagePath);\n }\n }\n\n async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\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 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // this.loadBackground(opts.backgroundUrl).catch((e) => console.error(e));\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.backgroundImage = imageData;\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n let startTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(\n this.inputVideo!,\n startTimeMs,\n (result) => (this.segmentationResults = result),\n );\n\n if (this.blurRadius) {\n await this.blurBackground(frame);\n } else {\n await this.drawVirtualBackground(frame);\n }\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || Date.now(),\n });\n controller.enqueue(newFrame);\n } finally {\n frame.close();\n }\n }\n\n async drawVirtualBackground(frame: VideoFrame) {\n if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo) return;\n // this.ctx.save();\n // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n if (this.segmentationResults?.categoryMask) {\n this.ctx.filter = 'blur(10px)';\n this.ctx.globalCompositeOperation = 'copy';\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-in';\n if (this.backgroundImage) {\n this.ctx.drawImage(\n this.backgroundImage,\n 0,\n 0,\n this.backgroundImage.width,\n this.backgroundImage.height,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n } else {\n this.ctx.fillStyle = '#00FF00';\n this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.ctx.globalCompositeOperation = 'destination-over';\n }\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n // this.ctx.restore();\n }\n\n async blurBackground(frame: VideoFrame) {\n if (\n !this.ctx ||\n !this.canvas ||\n !this.segmentationResults?.categoryMask?.canvas ||\n !this.inputVideo\n ) {\n return;\n }\n\n this.ctx.save();\n this.ctx.globalCompositeOperation = 'copy';\n\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n\n this.ctx.filter = 'blur(3px)';\n this.ctx.globalCompositeOperation = 'copy';\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-out';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.globalCompositeOperation = 'destination-over';\n this.ctx.filter = `blur(${this.blurRadius}px)`;\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.restore();\n }\n}\n\nfunction maskToBitmap(\n mask: vision.MPMask,\n videoWidth: number,\n videoHeight: number,\n): Promise<ImageBitmap> {\n const dataArray: Uint8ClampedArray = new Uint8ClampedArray(videoWidth * videoHeight * 4);\n const result = mask.getAsUint8Array();\n for (let i = 0; i < result.length; i += 1) {\n dataArray[i * 4] = result[i];\n dataArray[i * 4 + 1] = result[i];\n dataArray[i * 4 + 2] = result[i];\n dataArray[i * 4 + 3] = result[i];\n }\n const dataNew = new ImageData(dataArray, videoWidth, videoHeight);\n\n return createImageBitmap(dataNew);\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.2.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\": \"git@github.com:livekit/livekit-track-processors.git\",\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\": \"yarn 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/holistic\": \"0.5.1675471629\",\n \"@mediapipe/tasks-vision\": \"0.10.5\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.1.1\",\n \"@types/dom-mediacapture-transform\": \"^0.1.6\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@typescript-eslint/eslint-plugin\": \"^4.31.2\",\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.0.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"livekit-client\": \"^1.12.0\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.1.0\",\n \"typescript\": \"^5.0.4\",\n \"vite\": \"^4.3.8\"\n }\n}\n","import { VideoTrackTransformer, VideoTransformerInitOptions } from './types';\n\nexport default abstract class VideoTransformer implements VideoTrackTransformer {\n transformer?: TransformStream;\n\n canvas?: OffscreenCanvas;\n\n ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\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 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 }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.ctx = this.canvas.getContext('2d') || undefined;\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.ctx = undefined;\n }\n\n abstract transform(\n frame: VideoFrame,\n controller: TransformStreamDefaultController<VideoFrame>,\n ): void;\n}\n","import VideoTransformer from './VideoTransformer';\n\nexport default class DummyTransformer extends VideoTransformer {\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n controller.enqueue(frame);\n }\n\n async destroy() {\n // nothing to do\n }\n}\n","import ProcessorPipeline from './ProcessorPipeline';\nimport BackgroundTransformer, { SegmenterBaseOptions } from './transformers/BackgroundTransformer';\nimport DummyTransformer from './transformers/DummyTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport { ProcessorPipeline };\n\nexport const BackgroundBlur = (\n blurRadius: number = 10,\n segmenterOptions?: SegmenterBaseOptions,\n) => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline(\n [new BackgroundTransformer({ blurRadius, segmenterOptions })],\n 'background-blur',\n );\n return pipeline;\n};\n\nexport const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterBaseOptions) => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline(\n [new BackgroundTransformer({ imagePath, segmenterOptions })],\n 'virtual-background',\n );\n return pipeline;\n};\n\nexport const Dummy = () => {\n const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;\n if (!isPipelineSupported) {\n throw new Error('pipeline is not supported in this browser');\n }\n const pipeline = new ProcessorPipeline([new DummyTransformer()], 'dummy');\n return pipeline;\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/ProcessorWrapper.ts","../src/transformers/BackgroundTransformer.ts","../package.json","../src/transformers/VideoTransformer.ts","../src/index.ts"],"sourcesContent":["import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';\nimport { TrackTransformer } from './transformers';\n\nexport default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>\n implements TrackProcessor<Track.Kind>\n{\n static get isSupported() {\n return (\n typeof MediaStreamTrackGenerator !== 'undefined' &&\n typeof MediaStreamTrackProcessor !== 'undefined'\n );\n }\n\n name: string;\n\n source?: MediaStreamVideoTrack;\n\n sourceSettings?: MediaTrackSettings;\n\n processor?: MediaStreamTrackProcessor<VideoFrame>;\n\n trackGenerator?: MediaStreamTrackGenerator<VideoFrame>;\n\n canvas?: OffscreenCanvas;\n\n sourceDummy?: HTMLMediaElement;\n\n processedTrack?: MediaStreamTrack;\n\n transformer: TrackTransformer<TransformerOptions>;\n\n constructor(transformer: TrackTransformer<TransformerOptions>, name: string) {\n this.name = name;\n this.transformer = transformer;\n this.transformer.restart;\n }\n\n private async setup(opts: ProcessorOptions<Track.Kind>) {\n this.source = opts.track as MediaStreamVideoTrack;\n const origConstraints = this.source.getConstraints();\n await this.source.applyConstraints({\n ...origConstraints,\n // @ts-expect-error when a mediastream track is resized and/or cropped, the `VideoFrame` will have a coded height/width of the original video size\n // this leads to a shift of the underlying video as the frame itself is being rendered with the coded size\n // but image segmentation is based on the display dimensions (-> the cropped version)\n // in order to prevent this, we force the resize mode to \"none\"\n resizeMode: 'none',\n });\n this.sourceSettings = this.source.getSettings();\n this.sourceDummy = opts.element;\n if (this.sourceDummy instanceof HTMLVideoElement) {\n this.sourceDummy.height = this.sourceSettings.height ?? 300;\n this.sourceDummy.width = this.sourceSettings.width ?? 300;\n }\n if (!(this.sourceDummy instanceof HTMLVideoElement)) {\n throw TypeError('Currently only video transformers are supported');\n }\n // TODO explore if we can do all the processing work in a webworker\n this.processor = new MediaStreamTrackProcessor({ track: this.source });\n\n this.trackGenerator = new MediaStreamTrackGenerator({\n kind: 'video',\n signalTarget: this.source,\n });\n\n this.canvas = new OffscreenCanvas(\n this.sourceSettings.width ?? 300,\n this.sourceSettings.height ?? 300,\n );\n }\n\n async init(opts: ProcessorOptions<Track.Kind>) {\n await this.setup(opts);\n if (!this.canvas || !this.processor || !this.trackGenerator) {\n throw new TypeError('Expected both canvas and processor to be defined after setup');\n }\n\n let readableStream = this.processor.readable;\n\n await this.transformer.init({\n outputCanvas: this.canvas,\n inputElement: this.sourceDummy as HTMLVideoElement,\n });\n readableStream = readableStream.pipeThrough(this.transformer!.transformer!);\n\n readableStream\n .pipeTo(this.trackGenerator.writable)\n .catch((e) => console.error('error when trying to pipe', e))\n .finally(() => this.destroy());\n this.processedTrack = this.trackGenerator as MediaStreamVideoTrack;\n }\n\n async restart(opts: ProcessorOptions<Track.Kind>) {\n await this.destroy();\n return 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 this.transformer.restart(options[0]);\n }\n\n async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {\n this.transformer.update(options[0]);\n }\n\n async destroy() {\n await this.transformer.destroy();\n this.trackGenerator?.stop();\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 type BackgroundOptions = {\n blurRadius?: number;\n imagePath?: string;\n segmenterOptions?: SegmenterOptions;\n};\n\nexport default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {\n static get isSupported() {\n return typeof OffscreenCanvas !== 'undefined';\n }\n\n imageSegmenter?: vision.ImageSegmenter;\n\n segmentationResults: vision.ImageSegmenterResult | undefined;\n\n backgroundImage: ImageBitmap | null = null;\n\n blurRadius?: number;\n\n options: BackgroundOptions;\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 await super.init({ outputCanvas, inputElement: inputVideo });\n\n const fileSet = await vision.FilesetResolver.forVisionTasks(\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 'https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite',\n delegate: 'GPU',\n ...this.options.segmenterOptions,\n },\n runningMode: 'VIDEO',\n outputCategoryMask: true,\n outputConfidenceMasks: false,\n });\n\n // this.loadBackground(opts.backgroundUrl).catch((e) => console.error(e));\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.backgroundImage = imageData;\n }\n\n async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {\n try {\n if (this.isDisabled) {\n controller.enqueue(frame);\n return;\n }\n if (!this.canvas) {\n throw TypeError('Canvas needs to be initialized first');\n }\n let startTimeMs = performance.now();\n this.imageSegmenter?.segmentForVideo(\n this.inputVideo!,\n startTimeMs,\n (result) => (this.segmentationResults = result),\n );\n\n if (this.blurRadius) {\n await this.blurBackground(frame);\n } else {\n await this.drawVirtualBackground(frame);\n }\n const newFrame = new VideoFrame(this.canvas, {\n timestamp: frame.timestamp || Date.now(),\n });\n controller.enqueue(newFrame);\n } finally {\n frame.close();\n }\n }\n\n async update(opts: BackgroundOptions) {\n this.options = opts;\n if (opts.blurRadius) {\n this.blurRadius = opts.blurRadius;\n } else if (opts.imagePath) {\n await this.loadBackground(opts.imagePath);\n }\n }\n\n async drawVirtualBackground(frame: VideoFrame) {\n if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo) return;\n // this.ctx.save();\n // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n if (this.segmentationResults?.categoryMask) {\n this.ctx.filter = 'blur(10px)';\n this.ctx.globalCompositeOperation = 'copy';\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-in';\n if (this.backgroundImage) {\n this.ctx.drawImage(\n this.backgroundImage,\n 0,\n 0,\n this.backgroundImage.width,\n this.backgroundImage.height,\n 0,\n 0,\n this.canvas.width,\n this.canvas.height,\n );\n } else {\n this.ctx.fillStyle = '#00FF00';\n this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.ctx.globalCompositeOperation = 'destination-over';\n }\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n // this.ctx.restore();\n }\n\n async blurBackground(frame: VideoFrame) {\n if (\n !this.ctx ||\n !this.canvas ||\n !this.segmentationResults?.categoryMask?.canvas ||\n !this.inputVideo\n ) {\n return;\n }\n\n this.ctx.save();\n this.ctx.globalCompositeOperation = 'copy';\n\n const bitmap = await maskToBitmap(\n this.segmentationResults.categoryMask,\n this.segmentationResults.categoryMask.width,\n this.segmentationResults.categoryMask.height,\n );\n\n this.ctx.filter = 'blur(3px)';\n this.ctx.globalCompositeOperation = 'copy';\n this.ctx.drawImage(bitmap, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.filter = 'none';\n this.ctx.globalCompositeOperation = 'source-out';\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.globalCompositeOperation = 'destination-over';\n this.ctx.filter = `blur(${this.blurRadius}px)`;\n this.ctx.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height);\n this.ctx.restore();\n }\n}\n\nfunction maskToBitmap(\n mask: vision.MPMask,\n videoWidth: number,\n videoHeight: number,\n): Promise<ImageBitmap> {\n const dataArray: Uint8ClampedArray = new Uint8ClampedArray(videoWidth * videoHeight * 4);\n const result = mask.getAsUint8Array();\n for (let i = 0; i < result.length; i += 1) {\n dataArray[i * 4] = result[i];\n dataArray[i * 4 + 1] = result[i];\n dataArray[i * 4 + 2] = result[i];\n dataArray[i * 4 + 3] = result[i];\n }\n const dataNew = new ImageData(dataArray, videoWidth, videoHeight);\n\n return createImageBitmap(dataNew);\n}\n","{\n \"name\": \"@livekit/track-processors\",\n \"version\": \"0.3.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\": \"git@github.com:livekit/livekit-track-processors.git\",\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/holistic\": \"0.5.1675471629\",\n \"@mediapipe/tasks-vision\": \"0.10.9\"\n },\n \"peerDependencies\": {\n \"livekit-client\": \"^1.12.0 || ^2.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.26.2\",\n \"@livekit/changesets-changelog-github\": \"^0.0.4\",\n \"@trivago/prettier-plugin-sort-imports\": \"^4.1.1\",\n \"@types/dom-mediacapture-transform\": \"^0.1.6\",\n \"@types/offscreencanvas\": \"^2019.7.0\",\n \"@typescript-eslint/eslint-plugin\": \"^4.31.2\",\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.0.0\",\n \"eslint-plugin-import\": \"2.27.5\",\n \"livekit-client\": \"^1.12.0\",\n \"prettier\": \"^2.8.8\",\n \"tsup\": \"^7.1.0\",\n \"typescript\": \"^5.0.4\",\n \"vite\": \"^4.3.8\"\n }\n}\n","import { 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;\n\n ctx?: OffscreenCanvasRenderingContext2D;\n\n inputVideo?: HTMLVideoElement;\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 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 }\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {\n this.canvas = outputCanvas || null;\n this.ctx = this.canvas.getContext('2d') || undefined;\n\n this.inputVideo = inputVideo;\n this.isDisabled = false;\n }\n\n async destroy() {\n this.isDisabled = true;\n this.canvas = undefined;\n this.ctx = 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 from './ProcessorWrapper';\nimport BackgroundTransformer, {\n BackgroundOptions,\n SegmenterOptions,\n} from './transformers/BackgroundTransformer';\n\nexport * from './transformers/types';\nexport { default as VideoTransformer } from './transformers/VideoTransformer';\nexport { ProcessorWrapper, type BackgroundOptions, type SegmenterOptions, BackgroundTransformer };\n\nexport const BackgroundBlur = (blurRadius: number = 10, segmenterOptions?: SegmenterOptions) => {\n return BackgroundProcessor({ blurRadius, segmenterOptions }, 'background-blur');\n};\n\nexport const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterOptions) => {\n return BackgroundProcessor({ imagePath, segmenterOptions }, 'virtual-background');\n};\n\nexport const BackgroundProcessor = (options: BackgroundOptions, name = 'background-processor') => {\n const isProcessorSupported = ProcessorWrapper.isSupported && BackgroundTransformer.isSupported;\n if (!isProcessorSupported) {\n throw new Error('processor is not supported in this browser');\n }\n const processor = new ProcessorWrapper(new BackgroundTransformer(options), name);\n return processor;\n};\n"],"mappings":"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","names":["BackgroundProcessor"]}
@@ -1,8 +1,7 @@
1
1
  /// <reference types="dom-mediacapture-transform" />
2
- /// <reference types="dom-webcodecs" />
3
2
  import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';
4
- import { VideoTrackTransformer } from './transformers';
5
- export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
3
+ import { TrackTransformer } from './transformers';
4
+ export default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>> implements TrackProcessor<Track.Kind> {
6
5
  static get isSupported(): boolean;
7
6
  name: string;
8
7
  source?: MediaStreamVideoTrack;
@@ -12,10 +11,12 @@ export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
12
11
  canvas?: OffscreenCanvas;
13
12
  sourceDummy?: HTMLMediaElement;
14
13
  processedTrack?: MediaStreamTrack;
15
- transformers: Array<VideoTrackTransformer>;
16
- constructor(transformers: Array<VideoTrackTransformer>, name: string);
14
+ transformer: TrackTransformer<TransformerOptions>;
15
+ constructor(transformer: TrackTransformer<TransformerOptions>, name: string);
17
16
  private setup;
18
17
  init(opts: ProcessorOptions<Track.Kind>): Promise<void>;
19
18
  restart(opts: ProcessorOptions<Track.Kind>): Promise<void>;
19
+ restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>): Promise<void>;
20
+ updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>): Promise<void>;
20
21
  destroy(): Promise<void>;
21
22
  }
@@ -1,8 +1,8 @@
1
- import ProcessorPipeline from './ProcessorPipeline';
2
- import { SegmenterBaseOptions } from './transformers/BackgroundTransformer';
1
+ import ProcessorWrapper from './ProcessorWrapper';
2
+ import BackgroundTransformer, { BackgroundOptions, SegmenterOptions } from './transformers/BackgroundTransformer';
3
3
  export * from './transformers/types';
4
4
  export { default as VideoTransformer } from './transformers/VideoTransformer';
5
- export { ProcessorPipeline };
6
- export declare const BackgroundBlur: (blurRadius?: number, segmenterOptions?: SegmenterBaseOptions) => ProcessorPipeline;
7
- export declare const VirtualBackground: (imagePath: string, segmenterOptions?: SegmenterBaseOptions) => ProcessorPipeline;
8
- export declare const Dummy: () => ProcessorPipeline;
5
+ export { ProcessorWrapper, type BackgroundOptions, type SegmenterOptions, BackgroundTransformer };
6
+ export declare const BackgroundBlur: (blurRadius?: number, segmenterOptions?: SegmenterOptions) => ProcessorWrapper<BackgroundOptions>;
7
+ export declare const VirtualBackground: (imagePath: string, segmenterOptions?: SegmenterOptions) => ProcessorWrapper<BackgroundOptions>;
8
+ export declare const BackgroundProcessor: (options: BackgroundOptions, name?: string) => ProcessorWrapper<BackgroundOptions>;
@@ -1,14 +1,13 @@
1
- /// <reference types="dom-webcodecs" />
2
1
  import * as vision from '@mediapipe/tasks-vision';
3
2
  import VideoTransformer from './VideoTransformer';
4
3
  import { VideoTransformerInitOptions } from './types';
5
- export type SegmenterBaseOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;
4
+ export type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;
6
5
  export type BackgroundOptions = {
7
6
  blurRadius?: number;
8
7
  imagePath?: string;
9
- segmenterOptions?: SegmenterBaseOptions;
8
+ segmenterOptions?: SegmenterOptions;
10
9
  };
11
- export default class BackgroundProcessor extends VideoTransformer {
10
+ export default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {
12
11
  static get isSupported(): boolean;
13
12
  imageSegmenter?: vision.ImageSegmenter;
14
13
  segmentationResults: vision.ImageSegmenterResult | undefined;
@@ -20,6 +19,7 @@ export default class BackgroundProcessor extends VideoTransformer {
20
19
  destroy(): Promise<void>;
21
20
  loadBackground(path: string): Promise<void>;
22
21
  transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>): Promise<void>;
22
+ update(opts: BackgroundOptions): Promise<void>;
23
23
  drawVirtualBackground(frame: VideoFrame): Promise<void>;
24
24
  blurBackground(frame: VideoFrame): Promise<void>;
25
25
  }
@@ -5,7 +5,7 @@ export type MediaPipeHolisticTrackerTransformerOptions = {
5
5
  holisticOptions?: Options;
6
6
  callback?: (results: Results) => void;
7
7
  };
8
- export default class MediaPipeHolisticTrackerTransformer extends VideoTransformer {
8
+ export default class MediaPipeHolisticTrackerTransformer extends VideoTransformer<MediaPipeHolisticTrackerTransformerOptions> {
9
9
  holistic?: Holistic;
10
10
  holisticOptions: Options;
11
11
  callback: (results: Results) => void;
@@ -13,6 +13,7 @@ export default class MediaPipeHolisticTrackerTransformer extends VideoTransforme
13
13
  constructor({ holisticOptions, callback }: MediaPipeHolisticTrackerTransformerOptions);
14
14
  init({ inputElement: inputVideo, outputCanvas, }: VideoTransformerInitOptions): Promise<void>;
15
15
  destroy(): Promise<void>;
16
+ update(): Promise<void>;
16
17
  transform(): Promise<void>;
17
18
  sendFramesContinuouslyForTracking(videoEl: HTMLVideoElement): Promise<void>;
18
19
  }
@@ -1,6 +1,5 @@
1
- /// <reference types="dom-webcodecs" />
2
1
  import { VideoTrackTransformer, VideoTransformerInitOptions } from './types';
3
- export default abstract class VideoTransformer implements VideoTrackTransformer {
2
+ export default abstract class VideoTransformer<Options extends Record<string, unknown>> implements VideoTrackTransformer<Options> {
4
3
  transformer?: TransformStream;
5
4
  canvas?: OffscreenCanvas;
6
5
  ctx?: OffscreenCanvasRenderingContext2D;
@@ -10,4 +9,5 @@ export default abstract class VideoTransformer implements VideoTrackTransformer
10
9
  restart({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions): Promise<void>;
11
10
  destroy(): Promise<void>;
12
11
  abstract transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>): void;
12
+ abstract update(options: Options): void;
13
13
  }
@@ -6,10 +6,29 @@ export interface VideoTransformerInitOptions extends TrackTransformerInitOptions
6
6
  outputCanvas: OffscreenCanvas;
7
7
  inputElement: HTMLVideoElement;
8
8
  }
9
- export interface VideoTrackTransformer<T extends TrackTransformerInitOptions = VideoTransformerInitOptions> {
9
+ export interface AudioTransformerInitOptions extends TrackTransformerInitOptions {
10
+ }
11
+ export interface VideoTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<VideoTransformerInitOptions, VideoFrame> {
12
+ init: (options: VideoTransformerInitOptions) => void;
13
+ destroy: () => void;
14
+ restart: (options: VideoTransformerInitOptions) => void;
15
+ transform: (frame: VideoFrame, controller: TransformStreamDefaultController) => void;
16
+ transformer?: TransformStream;
17
+ update: (options: Options) => void;
18
+ }
19
+ export interface AudioTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<AudioTransformerInitOptions, AudioData> {
20
+ init: (options: AudioTransformerInitOptions) => void;
21
+ destroy: () => void;
22
+ restart: (options: AudioTransformerInitOptions) => void;
23
+ transform: (frame: AudioData, controller: TransformStreamDefaultController) => void;
24
+ transformer?: TransformStream;
25
+ update: (options: Options) => void;
26
+ }
27
+ export type TrackTransformer<Options extends Record<string, unknown>> = VideoTrackTransformer<Options> | AudioTrackTransformer<Options>;
28
+ export interface BaseTrackTransformer<T extends TrackTransformerInitOptions, DataType extends VideoFrame | AudioData> {
10
29
  init: (options: T) => void;
11
30
  destroy: () => void;
12
31
  restart: (options: T) => void;
13
- transform: (frame: VideoFrame, controller: TransformStreamDefaultController) => void;
32
+ transform: (frame: DataType, controller: TransformStreamDefaultController) => void;
14
33
  transformer?: TransformStream;
15
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/track-processors",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "LiveKit track processors",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -9,24 +9,16 @@
9
9
  "repository": "git@github.com:livekit/livekit-track-processors.git",
10
10
  "author": "Lukas Seiler",
11
11
  "license": "Apache-2.0",
12
- "scripts": {
13
- "build": "tsup --onSuccess \"tsc --declaration --emitDeclarationOnly\"",
14
- "build-sample": "cd example && vite build",
15
- "lint": "eslint src",
16
- "release": "yarn build && changeset publish",
17
- "test": "jest",
18
- "sample": "vite serve example --port 8080 --open"
19
- },
20
12
  "files": [
21
13
  "dist",
22
14
  "src"
23
15
  ],
24
16
  "dependencies": {
25
17
  "@mediapipe/holistic": "0.5.1675471629",
26
- "@mediapipe/tasks-vision": "0.10.5"
18
+ "@mediapipe/tasks-vision": "0.10.9"
27
19
  },
28
20
  "peerDependencies": {
29
- "livekit-client": "^1.12.0"
21
+ "livekit-client": "^1.12.0 || ^2.0.0"
30
22
  },
31
23
  "devDependencies": {
32
24
  "@changesets/cli": "^2.26.2",
@@ -45,5 +37,13 @@
45
37
  "tsup": "^7.1.0",
46
38
  "typescript": "^5.0.4",
47
39
  "vite": "^4.3.8"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup --onSuccess \"tsc --declaration --emitDeclarationOnly\"",
43
+ "build-sample": "cd example && vite build",
44
+ "lint": "eslint src",
45
+ "release": "pnpm build && changeset publish",
46
+ "test": "jest",
47
+ "sample": "vite serve example --port 8080 --open"
48
48
  }
49
- }
49
+ }
@@ -1,7 +1,9 @@
1
1
  import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';
2
- import { VideoTrackTransformer } from './transformers';
2
+ import { TrackTransformer } from './transformers';
3
3
 
4
- export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
4
+ export default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>
5
+ implements TrackProcessor<Track.Kind>
6
+ {
5
7
  static get isSupported() {
6
8
  return (
7
9
  typeof MediaStreamTrackGenerator !== 'undefined' &&
@@ -25,11 +27,12 @@ export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
25
27
 
26
28
  processedTrack?: MediaStreamTrack;
27
29
 
28
- transformers: Array<VideoTrackTransformer>;
30
+ transformer: TrackTransformer<TransformerOptions>;
29
31
 
30
- constructor(transformers: Array<VideoTrackTransformer>, name: string) {
32
+ constructor(transformer: TrackTransformer<TransformerOptions>, name: string) {
31
33
  this.name = name;
32
- this.transformers = transformers;
34
+ this.transformer = transformer;
35
+ this.transformer.restart;
33
36
  }
34
37
 
35
38
  private async setup(opts: ProcessorOptions<Track.Kind>) {
@@ -73,13 +76,13 @@ export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
73
76
  }
74
77
 
75
78
  let readableStream = this.processor.readable;
76
- for (const transformer of this.transformers) {
77
- await transformer.init({
78
- outputCanvas: this.canvas,
79
- inputElement: this.sourceDummy as HTMLVideoElement,
80
- });
81
- readableStream = readableStream.pipeThrough(transformer!.transformer!);
82
- }
79
+
80
+ await this.transformer.init({
81
+ outputCanvas: this.canvas,
82
+ inputElement: this.sourceDummy as HTMLVideoElement,
83
+ });
84
+ readableStream = readableStream.pipeThrough(this.transformer!.transformer!);
85
+
83
86
  readableStream
84
87
  .pipeTo(this.trackGenerator.writable)
85
88
  .catch((e) => console.error('error when trying to pipe', e))
@@ -92,10 +95,17 @@ export default class ProcessorPipeline implements TrackProcessor<Track.Kind> {
92
95
  return this.init(opts);
93
96
  }
94
97
 
98
+ async restartTransformer(...options: Parameters<(typeof this.transformer)['restart']>) {
99
+ // @ts-ignore unclear why the restart method only accepts VideoTransformerInitOptions instead of either those or AudioTransformerInitOptions
100
+ this.transformer.restart(options[0]);
101
+ }
102
+
103
+ async updateTransformerOptions(...options: Parameters<(typeof this.transformer)['update']>) {
104
+ this.transformer.update(options[0]);
105
+ }
106
+
95
107
  async destroy() {
96
- for (const transformer of this.transformers) {
97
- await transformer.destroy();
98
- }
108
+ await this.transformer.destroy();
99
109
  this.trackGenerator?.stop();
100
110
  }
101
111
  }
package/src/index.ts CHANGED
@@ -1,43 +1,26 @@
1
- import ProcessorPipeline from './ProcessorPipeline';
2
- import BackgroundTransformer, { SegmenterBaseOptions } from './transformers/BackgroundTransformer';
3
- import DummyTransformer from './transformers/DummyTransformer';
1
+ import ProcessorWrapper from './ProcessorWrapper';
2
+ import BackgroundTransformer, {
3
+ BackgroundOptions,
4
+ SegmenterOptions,
5
+ } from './transformers/BackgroundTransformer';
4
6
 
5
7
  export * from './transformers/types';
6
8
  export { default as VideoTransformer } from './transformers/VideoTransformer';
7
- export { ProcessorPipeline };
9
+ export { ProcessorWrapper, type BackgroundOptions, type SegmenterOptions, BackgroundTransformer };
8
10
 
9
- export const BackgroundBlur = (
10
- blurRadius: number = 10,
11
- segmenterOptions?: SegmenterBaseOptions,
12
- ) => {
13
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;
14
- if (!isPipelineSupported) {
15
- throw new Error('pipeline is not supported in this browser');
16
- }
17
- const pipeline = new ProcessorPipeline(
18
- [new BackgroundTransformer({ blurRadius, segmenterOptions })],
19
- 'background-blur',
20
- );
21
- return pipeline;
11
+ export const BackgroundBlur = (blurRadius: number = 10, segmenterOptions?: SegmenterOptions) => {
12
+ return BackgroundProcessor({ blurRadius, segmenterOptions }, 'background-blur');
22
13
  };
23
14
 
24
- export const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterBaseOptions) => {
25
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;
26
- if (!isPipelineSupported) {
27
- throw new Error('pipeline is not supported in this browser');
28
- }
29
- const pipeline = new ProcessorPipeline(
30
- [new BackgroundTransformer({ imagePath, segmenterOptions })],
31
- 'virtual-background',
32
- );
33
- return pipeline;
15
+ export const VirtualBackground = (imagePath: string, segmenterOptions?: SegmenterOptions) => {
16
+ return BackgroundProcessor({ imagePath, segmenterOptions }, 'virtual-background');
34
17
  };
35
18
 
36
- export const Dummy = () => {
37
- const isPipelineSupported = ProcessorPipeline.isSupported && BackgroundTransformer.isSupported;
38
- if (!isPipelineSupported) {
39
- throw new Error('pipeline is not supported in this browser');
19
+ export const BackgroundProcessor = (options: BackgroundOptions, name = 'background-processor') => {
20
+ const isProcessorSupported = ProcessorWrapper.isSupported && BackgroundTransformer.isSupported;
21
+ if (!isProcessorSupported) {
22
+ throw new Error('processor is not supported in this browser');
40
23
  }
41
- const pipeline = new ProcessorPipeline([new DummyTransformer()], 'dummy');
42
- return pipeline;
24
+ const processor = new ProcessorWrapper(new BackgroundTransformer(options), name);
25
+ return processor;
43
26
  };
@@ -3,15 +3,15 @@ import { dependencies } from '../../package.json';
3
3
  import VideoTransformer from './VideoTransformer';
4
4
  import { VideoTransformerInitOptions } from './types';
5
5
 
6
- export type SegmenterBaseOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;
6
+ export type SegmenterOptions = Partial<vision.ImageSegmenterOptions['baseOptions']>;
7
7
 
8
8
  export type BackgroundOptions = {
9
9
  blurRadius?: number;
10
10
  imagePath?: string;
11
- segmenterOptions?: SegmenterBaseOptions;
11
+ segmenterOptions?: SegmenterOptions;
12
12
  };
13
13
 
14
- export default class BackgroundProcessor extends VideoTransformer {
14
+ export default class BackgroundProcessor extends VideoTransformer<BackgroundOptions> {
15
15
  static get isSupported() {
16
16
  return typeof OffscreenCanvas !== 'undefined';
17
17
  }
@@ -29,11 +29,7 @@ export default class BackgroundProcessor extends VideoTransformer {
29
29
  constructor(opts: BackgroundOptions) {
30
30
  super();
31
31
  this.options = opts;
32
- if (opts.blurRadius) {
33
- this.blurRadius = opts.blurRadius;
34
- } else if (opts.imagePath) {
35
- this.loadBackground(opts.imagePath);
36
- }
32
+ this.update(opts);
37
33
  }
38
34
 
39
35
  async init({ outputCanvas, inputElement: inputVideo }: VideoTransformerInitOptions) {
@@ -107,6 +103,15 @@ export default class BackgroundProcessor extends VideoTransformer {
107
103
  }
108
104
  }
109
105
 
106
+ async update(opts: BackgroundOptions) {
107
+ this.options = opts;
108
+ if (opts.blurRadius) {
109
+ this.blurRadius = opts.blurRadius;
110
+ } else if (opts.imagePath) {
111
+ await this.loadBackground(opts.imagePath);
112
+ }
113
+ }
114
+
110
115
  async drawVirtualBackground(frame: VideoFrame) {
111
116
  if (!this.canvas || !this.ctx || !this.segmentationResults || !this.inputVideo) return;
112
117
  // this.ctx.save();
@@ -7,7 +7,7 @@ export type MediaPipeHolisticTrackerTransformerOptions = {
7
7
  callback?: (results: Results) => void;
8
8
  };
9
9
 
10
- export default class MediaPipeHolisticTrackerTransformer extends VideoTransformer {
10
+ export default class MediaPipeHolisticTrackerTransformer extends VideoTransformer<MediaPipeHolisticTrackerTransformerOptions> {
11
11
  holistic?: Holistic;
12
12
  holisticOptions: Options;
13
13
  callback: (results: Results) => void;
@@ -45,6 +45,8 @@ export default class MediaPipeHolisticTrackerTransformer extends VideoTransforme
45
45
  await this.holistic?.close();
46
46
  }
47
47
 
48
+ async update() {}
49
+
48
50
  async transform(): Promise<void> {
49
51
  return;
50
52
  }
@@ -1,6 +1,8 @@
1
1
  import { VideoTrackTransformer, VideoTransformerInitOptions } from './types';
2
2
 
3
- export default abstract class VideoTransformer implements VideoTrackTransformer {
3
+ export default abstract class VideoTransformer<Options extends Record<string, unknown>>
4
+ implements VideoTrackTransformer<Options>
5
+ {
4
6
  transformer?: TransformStream;
5
7
 
6
8
  canvas?: OffscreenCanvas;
@@ -47,4 +49,6 @@ export default abstract class VideoTransformer implements VideoTrackTransformer
47
49
  frame: VideoFrame,
48
50
  controller: TransformStreamDefaultController<VideoFrame>,
49
51
  ): void;
52
+
53
+ abstract update(options: Options): void;
50
54
  }
@@ -7,12 +7,39 @@ export interface VideoTransformerInitOptions extends TrackTransformerInitOptions
7
7
  inputElement: HTMLVideoElement;
8
8
  }
9
9
 
10
- export interface VideoTrackTransformer<
11
- T extends TrackTransformerInitOptions = VideoTransformerInitOptions,
10
+ export interface AudioTransformerInitOptions extends TrackTransformerInitOptions {}
11
+
12
+ export interface VideoTrackTransformer<Options extends Record<string, unknown>>
13
+ extends BaseTrackTransformer<VideoTransformerInitOptions, VideoFrame> {
14
+ init: (options: VideoTransformerInitOptions) => void;
15
+ destroy: () => void;
16
+ restart: (options: VideoTransformerInitOptions) => void;
17
+ transform: (frame: VideoFrame, controller: TransformStreamDefaultController) => void;
18
+ transformer?: TransformStream;
19
+ update: (options: Options) => void;
20
+ }
21
+
22
+ export interface AudioTrackTransformer<Options extends Record<string, unknown>>
23
+ extends BaseTrackTransformer<AudioTransformerInitOptions, AudioData> {
24
+ init: (options: AudioTransformerInitOptions) => void;
25
+ destroy: () => void;
26
+ restart: (options: AudioTransformerInitOptions) => void;
27
+ transform: (frame: AudioData, controller: TransformStreamDefaultController) => void;
28
+ transformer?: TransformStream;
29
+ update: (options: Options) => void;
30
+ }
31
+
32
+ export type TrackTransformer<Options extends Record<string, unknown>> =
33
+ | VideoTrackTransformer<Options>
34
+ | AudioTrackTransformer<Options>;
35
+
36
+ export interface BaseTrackTransformer<
37
+ T extends TrackTransformerInitOptions,
38
+ DataType extends VideoFrame | AudioData,
12
39
  > {
13
40
  init: (options: T) => void;
14
41
  destroy: () => void;
15
42
  restart: (options: T) => void;
16
- transform: (frame: VideoFrame, controller: TransformStreamDefaultController) => void;
43
+ transform: (frame: DataType, controller: TransformStreamDefaultController) => void;
17
44
  transformer?: TransformStream;
18
45
  }
@@ -1,6 +0,0 @@
1
- /// <reference types="dom-webcodecs" />
2
- import VideoTransformer from './VideoTransformer';
3
- export default class DummyTransformer extends VideoTransformer {
4
- transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>): Promise<void>;
5
- destroy(): Promise<void>;
6
- }
@@ -1,11 +0,0 @@
1
- import VideoTransformer from './VideoTransformer';
2
-
3
- export default class DummyTransformer extends VideoTransformer {
4
- async transform(frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) {
5
- controller.enqueue(frame);
6
- }
7
-
8
- async destroy() {
9
- // nothing to do
10
- }
11
- }