@stream-io/video-filters-web 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +38 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/src/BaseVideoProcessor.js +107 -0
- package/dist/esm/src/BaseVideoProcessor.js.map +1 -0
- package/dist/esm/src/FallbackGenerator.js +58 -0
- package/dist/esm/src/FallbackGenerator.js.map +1 -0
- package/dist/esm/src/FallbackProcessor.js +89 -0
- package/dist/esm/src/FallbackProcessor.js.map +1 -0
- package/dist/esm/src/FullScreenBlur.js +41 -0
- package/dist/esm/src/FullScreenBlur.js.map +1 -0
- package/dist/esm/src/FullScreenBlurRenderer.js +276 -0
- package/dist/esm/src/FullScreenBlurRenderer.js.map +1 -0
- package/dist/esm/src/VirtualBackground.js +168 -0
- package/dist/esm/src/VirtualBackground.js.map +1 -0
- package/dist/esm/src/WebGLRenderer.js +723 -0
- package/dist/esm/src/WebGLRenderer.js.map +1 -0
- package/dist/esm/src/compatibility.js +48 -0
- package/dist/esm/src/compatibility.js.map +1 -0
- package/dist/esm/src/legacy/createRenderer.js +33 -0
- package/dist/esm/src/legacy/createRenderer.js.map +1 -0
- package/dist/esm/src/legacy/helpers/webglHelper.js +94 -0
- package/dist/esm/src/legacy/helpers/webglHelper.js.map +1 -0
- package/dist/esm/src/legacy/segmentation.js +14 -0
- package/dist/esm/src/legacy/segmentation.js.map +1 -0
- package/dist/esm/src/legacy/tflite-simd.js +728 -0
- package/dist/esm/src/legacy/tflite-simd.js.map +1 -0
- package/dist/esm/src/legacy/tflite.js +31 -0
- package/dist/esm/src/legacy/tflite.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/backgroundBlurStage.js +202 -0
- package/dist/esm/src/legacy/webgl2/backgroundBlurStage.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/backgroundImageStage.js +157 -0
- package/dist/esm/src/legacy/webgl2/backgroundImageStage.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/jointBilateralFilterStage.js +113 -0
- package/dist/esm/src/legacy/webgl2/jointBilateralFilterStage.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/resizingStage.js +57 -0
- package/dist/esm/src/legacy/webgl2/resizingStage.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/softmaxStage.js +51 -0
- package/dist/esm/src/legacy/webgl2/softmaxStage.js.map +1 -0
- package/dist/esm/src/legacy/webgl2/webgl2Pipeline.js +105 -0
- package/dist/esm/src/legacy/webgl2/webgl2Pipeline.js.map +1 -0
- package/dist/esm/src/mediapipe.js +16 -0
- package/dist/esm/src/mediapipe.js.map +1 -0
- package/dist/esm/src/types.js +11 -0
- package/dist/esm/src/types.js.map +1 -0
- package/dist/esm/src/version.js +5 -0
- package/dist/esm/src/version.js.map +1 -0
- package/dist/index.cjs.js +26 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/src/VirtualBackground.d.ts +3 -2
- package/dist/src/types.d.ts +7 -5
- package/package.json +3 -3
- package/src/VirtualBackground.ts +29 -1
- package/src/types.ts +8 -5
- package/dist/index.es.js +0 -3022
- package/dist/index.es.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [0.9.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.7...@stream-io/video-filters-web-0.9.0) (2026-09-04)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **video-filters-web:** apply background filter changes without re-registering the filter ([#2403](https://github.com/GetStream/stream-video-js/issues/2403)) ([4fbb30a](https://github.com/GetStream/stream-video-js/commit/4fbb30a640f87a61d400179b24f844b04f2f3bd5))
|
|
10
|
+
|
|
11
|
+
## [0.8.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.6...@stream-io/video-filters-web-0.8.7) (2026-08-28)
|
|
12
|
+
|
|
13
|
+
### Performance Improvements
|
|
14
|
+
|
|
15
|
+
- emit per-module ESM builds for the React packages ([#2378](https://github.com/GetStream/stream-video-js/issues/2378)) ([9c937f4](https://github.com/GetStream/stream-video-js/commit/9c937f4b5845aa0e8108a218b1c84b7acb249d77))
|
|
16
|
+
|
|
5
17
|
## [0.8.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.5...@stream-io/video-filters-web-0.8.6) (2026-08-07)
|
|
6
18
|
|
|
7
19
|
## [0.8.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-filters-web-0.8.4...@stream-io/video-filters-web-0.8.5) (2026-07-30)
|
package/README.md
CHANGED
|
@@ -43,10 +43,47 @@ const processor = new VirtualBackground(
|
|
|
43
43
|
// 4. Start the processor and use the processed track
|
|
44
44
|
const processedTrack = await processor.start();
|
|
45
45
|
|
|
46
|
-
// 5.
|
|
46
|
+
// 5. Change the effect at any time, without restarting the track
|
|
47
|
+
await processor.updateOptions({
|
|
48
|
+
backgroundFilter: 'blur',
|
|
49
|
+
backgroundBlurLevel: 'high',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// 6. Stop the processor
|
|
47
53
|
processor.stop();
|
|
48
54
|
```
|
|
49
55
|
|
|
56
|
+
### Updating the effect
|
|
57
|
+
|
|
58
|
+
`updateOptions` changes the background effect of a running processor. The new
|
|
59
|
+
effect is applied on the next frame - the input track, renderer and segmenter
|
|
60
|
+
are preserved, so there is no interruption and the processed track doesn't
|
|
61
|
+
need to be re-published.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// switch between blur levels
|
|
65
|
+
await processor.updateOptions({
|
|
66
|
+
backgroundFilter: 'blur',
|
|
67
|
+
backgroundBlurLevel: 3,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// switch to an image background
|
|
71
|
+
await processor.updateOptions({
|
|
72
|
+
backgroundFilter: 'image',
|
|
73
|
+
backgroundImage: 'https://example.com/background.jpg',
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Notes:
|
|
78
|
+
|
|
79
|
+
- `updateOptions` replaces the effect options, so always pass the complete
|
|
80
|
+
set you want applied.
|
|
81
|
+
- `basePath` and `modelPath` are fixed for the lifetime of the processor;
|
|
82
|
+
changing the model requires a new instance.
|
|
83
|
+
- When several updates overlap, the last one wins.
|
|
84
|
+
- If the background image fails to load, the promise rejects and the current
|
|
85
|
+
background keeps rendering.
|
|
86
|
+
|
|
50
87
|
## Known limitations
|
|
51
88
|
|
|
52
89
|
- This library only works in a modern desktop browser that supports WebAssembly SIMD and WebGL.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { isMediaPipePlatformSupported, isPlatformSupported } from './src/compatibility.js';
|
|
2
|
+
export { createRenderer } from './src/legacy/createRenderer.js';
|
|
3
|
+
export { SegmentationLevel } from './src/legacy/segmentation.js';
|
|
4
|
+
export { loadTFLite } from './src/legacy/tflite.js';
|
|
5
|
+
export { loadMediaPipe } from './src/mediapipe.js';
|
|
6
|
+
export { BACKGROUND_BLUR_MAP } from './src/types.js';
|
|
7
|
+
export { BaseVideoProcessor } from './src/BaseVideoProcessor.js';
|
|
8
|
+
export { VirtualBackground } from './src/VirtualBackground.js';
|
|
9
|
+
export { FullScreenBlur } from './src/FullScreenBlur.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { TrackGenerator } from './FallbackGenerator.js';
|
|
2
|
+
import { TrackProcessor } from './FallbackProcessor.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base class for real-time video filters.
|
|
6
|
+
*
|
|
7
|
+
* It sets up the full pipeline that reads frames from the input track,
|
|
8
|
+
* processes them, and outputs a new track with your effect applied. Subclasses
|
|
9
|
+
* only need to implement `initialize` (run once before processing starts) and
|
|
10
|
+
* `transform` (called for every frame).
|
|
11
|
+
*
|
|
12
|
+
* Everything else—canvas setup, performance tracking, error handling, and
|
|
13
|
+
* clean shutdown is handled for you. Calling `start()` returns a processed
|
|
14
|
+
* `MediaStreamTrack` ready to use.
|
|
15
|
+
*/
|
|
16
|
+
class BaseVideoProcessor {
|
|
17
|
+
track;
|
|
18
|
+
processor;
|
|
19
|
+
generator;
|
|
20
|
+
hooks;
|
|
21
|
+
abortController = new AbortController();
|
|
22
|
+
canvas;
|
|
23
|
+
frames = 0;
|
|
24
|
+
delayTotal = 0;
|
|
25
|
+
lastStatsTime = 0;
|
|
26
|
+
/**
|
|
27
|
+
* Constructs a new instance.
|
|
28
|
+
*/
|
|
29
|
+
constructor(track, hooks = {}) {
|
|
30
|
+
this.track = track;
|
|
31
|
+
this.processor = new TrackProcessor({ track });
|
|
32
|
+
this.generator = new TrackGenerator({
|
|
33
|
+
kind: 'video',
|
|
34
|
+
signalTarget: track,
|
|
35
|
+
});
|
|
36
|
+
this.hooks = hooks;
|
|
37
|
+
}
|
|
38
|
+
async start() {
|
|
39
|
+
const { readable } = this.processor;
|
|
40
|
+
const { writable } = this.generator;
|
|
41
|
+
const { width = 1280, height = 720 } = this.track.getSettings();
|
|
42
|
+
this.canvas = new OffscreenCanvas(width, height);
|
|
43
|
+
await this.initialize();
|
|
44
|
+
const transformStream = new TransformStream({
|
|
45
|
+
transform: async (frame, controller) => {
|
|
46
|
+
try {
|
|
47
|
+
if (this.abortController.signal.aborted)
|
|
48
|
+
return frame.close();
|
|
49
|
+
if (this.canvas.width !== frame.displayWidth ||
|
|
50
|
+
this.canvas.height !== frame.displayHeight) {
|
|
51
|
+
this.canvas.width = frame.displayWidth;
|
|
52
|
+
this.canvas.height = frame.displayHeight;
|
|
53
|
+
}
|
|
54
|
+
const processed = await this.transform(frame);
|
|
55
|
+
controller.enqueue(processed);
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
this.hooks.onError?.(e);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
frame.close();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
flush: () => this.onFlush(),
|
|
65
|
+
});
|
|
66
|
+
readable
|
|
67
|
+
.pipeThrough(transformStream, { signal: this.abortController.signal })
|
|
68
|
+
.pipeTo(writable, { signal: this.abortController.signal })
|
|
69
|
+
.catch((e) => {
|
|
70
|
+
if (e.name !== 'AbortError' && e.name !== 'InvalidStateError') {
|
|
71
|
+
console.error(`[${this.processorName}] Error processing track:`, e);
|
|
72
|
+
this.hooks.onError?.(e);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return this.generator;
|
|
76
|
+
}
|
|
77
|
+
stop() {
|
|
78
|
+
this.abortController.abort();
|
|
79
|
+
this.generator.stop();
|
|
80
|
+
this.onStop();
|
|
81
|
+
}
|
|
82
|
+
updateStats(delay) {
|
|
83
|
+
this.frames++;
|
|
84
|
+
this.delayTotal += delay;
|
|
85
|
+
const now = performance.now();
|
|
86
|
+
if (this.lastStatsTime === 0) {
|
|
87
|
+
this.lastStatsTime = now;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (now - this.lastStatsTime >= 1000) {
|
|
91
|
+
const avgDelay = Math.round((this.delayTotal / this.frames) * 100) / 100;
|
|
92
|
+
const fps = Math.round((1000 * this.frames) / (now - this.lastStatsTime));
|
|
93
|
+
this.hooks.onStats?.({ delay: avgDelay, fps, timestamp: now });
|
|
94
|
+
this.frames = 0;
|
|
95
|
+
this.delayTotal = 0;
|
|
96
|
+
this.lastStatsTime = now;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
onFlush() { }
|
|
100
|
+
onStop() { }
|
|
101
|
+
get processorName() {
|
|
102
|
+
return 'base-processor';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { BaseVideoProcessor };
|
|
107
|
+
//# sourceMappingURL=BaseVideoProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseVideoProcessor.js","sources":["../../../../../src/BaseVideoProcessor.ts"],"sourcesContent":["import { VideoTrackProcessorHooks } from './types';\nimport { TrackGenerator, MediaStreamTrackGenerator } from './FallbackGenerator';\nimport { MediaStreamTrackProcessor, TrackProcessor } from './FallbackProcessor';\n\n/**\n * Base class for real-time video filters.\n *\n * It sets up the full pipeline that reads frames from the input track,\n * processes them, and outputs a new track with your effect applied. Subclasses\n * only need to implement `initialize` (run once before processing starts) and\n * `transform` (called for every frame).\n *\n * Everything else—canvas setup, performance tracking, error handling, and\n * clean shutdown is handled for you. Calling `start()` returns a processed\n * `MediaStreamTrack` ready to use.\n */\nexport abstract class BaseVideoProcessor {\n protected readonly processor: MediaStreamTrackProcessor<VideoFrame>;\n protected readonly generator: MediaStreamTrackGenerator<VideoFrame>;\n\n protected readonly hooks: VideoTrackProcessorHooks;\n\n protected readonly abortController = new AbortController();\n protected canvas!: OffscreenCanvas;\n\n private frames = 0;\n private delayTotal = 0;\n private lastStatsTime = 0;\n\n /**\n * Constructs a new instance.\n */\n protected constructor(\n protected readonly track: MediaStreamVideoTrack,\n hooks: VideoTrackProcessorHooks = {},\n ) {\n this.processor = new TrackProcessor({ track });\n this.generator = new TrackGenerator({\n kind: 'video',\n signalTarget: track,\n });\n this.hooks = hooks;\n }\n\n public async start(): Promise<MediaStreamTrack> {\n const { readable } = this.processor;\n const { writable } = this.generator;\n\n const { width = 1280, height = 720 } = this.track.getSettings();\n this.canvas = new OffscreenCanvas(width, height);\n\n await this.initialize();\n\n const transformStream = new TransformStream<VideoFrame, VideoFrame>({\n transform: async (frame, controller) => {\n try {\n if (this.abortController.signal.aborted) return frame.close();\n\n if (\n this.canvas.width !== frame.displayWidth ||\n this.canvas.height !== frame.displayHeight\n ) {\n this.canvas.width = frame.displayWidth;\n this.canvas.height = frame.displayHeight;\n }\n\n const processed = await this.transform(frame);\n\n controller.enqueue(processed);\n } catch (e) {\n this.hooks.onError?.(e);\n } finally {\n frame.close();\n }\n },\n flush: () => this.onFlush(),\n });\n\n readable\n .pipeThrough(transformStream, { signal: this.abortController.signal })\n .pipeTo(writable, { signal: this.abortController.signal })\n .catch((e) => {\n if (e.name !== 'AbortError' && e.name !== 'InvalidStateError') {\n console.error(`[${this.processorName}] Error processing track:`, e);\n this.hooks.onError?.(e);\n }\n });\n\n return this.generator;\n }\n\n public stop(): void {\n this.abortController.abort();\n this.generator.stop();\n this.onStop();\n }\n\n protected updateStats(delay: number): void {\n this.frames++;\n this.delayTotal += delay;\n\n const now = performance.now();\n if (this.lastStatsTime === 0) {\n this.lastStatsTime = now;\n return;\n }\n\n if (now - this.lastStatsTime >= 1000) {\n const avgDelay = Math.round((this.delayTotal / this.frames) * 100) / 100;\n const fps = Math.round((1000 * this.frames) / (now - this.lastStatsTime));\n\n this.hooks.onStats?.({ delay: avgDelay, fps, timestamp: now });\n\n this.frames = 0;\n this.delayTotal = 0;\n this.lastStatsTime = now;\n }\n }\n\n protected abstract initialize(): Promise<void>;\n protected abstract transform(frame: VideoFrame): Promise<VideoFrame>;\n\n protected onFlush(): void {}\n protected onStop(): void {}\n\n protected get processorName(): string {\n return 'base-processor';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback video processor for browsers that do not support MediaStreamTrackGenerator.
|
|
3
|
+
*
|
|
4
|
+
* Produces a video MediaStreamTrack sourced from a canvas and exposes
|
|
5
|
+
* a WritableStream<VideoFrame> on track.writable for writing frames.
|
|
6
|
+
*/
|
|
7
|
+
class FallbackGenerator {
|
|
8
|
+
constructor({ kind, signalTarget }) {
|
|
9
|
+
if (kind !== 'video') {
|
|
10
|
+
throw new Error('Only video tracks are supported');
|
|
11
|
+
}
|
|
12
|
+
const canvas = document.createElement('canvas');
|
|
13
|
+
const ctx = canvas.getContext('2d', { desynchronized: true });
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error('Failed to get 2D context from canvas');
|
|
16
|
+
}
|
|
17
|
+
const mediaStream = canvas.captureStream();
|
|
18
|
+
const track = mediaStream.getVideoTracks()[0];
|
|
19
|
+
const height = signalTarget?.getSettings().height;
|
|
20
|
+
const width = signalTarget?.getSettings().width;
|
|
21
|
+
if (height && width) {
|
|
22
|
+
canvas.height = height;
|
|
23
|
+
canvas.width = width;
|
|
24
|
+
}
|
|
25
|
+
if (!track) {
|
|
26
|
+
throw new Error('Failed to create canvas track');
|
|
27
|
+
}
|
|
28
|
+
if (signalTarget) {
|
|
29
|
+
signalTarget.addEventListener('ended', () => {
|
|
30
|
+
track.stop();
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
track.writable = new WritableStream({
|
|
34
|
+
write: (frame) => {
|
|
35
|
+
if (canvas.width !== frame.displayWidth ||
|
|
36
|
+
canvas.height !== frame.displayHeight) {
|
|
37
|
+
canvas.width = frame.displayWidth;
|
|
38
|
+
canvas.height = frame.displayHeight;
|
|
39
|
+
}
|
|
40
|
+
ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
|
|
41
|
+
frame.close();
|
|
42
|
+
},
|
|
43
|
+
abort: () => {
|
|
44
|
+
track.stop();
|
|
45
|
+
},
|
|
46
|
+
close: () => {
|
|
47
|
+
track.stop();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
return track;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const TrackGenerator = typeof MediaStreamTrackGenerator !== 'undefined'
|
|
54
|
+
? MediaStreamTrackGenerator
|
|
55
|
+
: FallbackGenerator;
|
|
56
|
+
|
|
57
|
+
export { TrackGenerator };
|
|
58
|
+
//# sourceMappingURL=FallbackGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FallbackGenerator.js","sources":["../../../../../src/FallbackGenerator.ts"],"sourcesContent":["/**\n * Type representing a video track generator that can be either the native\n * MediaStreamTrackGenerator or the fallback implementation.\n */\nexport interface MediaStreamTrackGenerator<T> extends MediaStreamTrack {\n writable: WritableStream<T>;\n}\n\n/**\n * Configuration options for creating a track generator.\n */\nexport interface TrackGeneratorOptions {\n readonly kind: 'video';\n readonly signalTarget?: MediaStreamTrack;\n}\n\n/**\n * Fallback video processor for browsers that do not support MediaStreamTrackGenerator.\n *\n * Produces a video MediaStreamTrack sourced from a canvas and exposes\n * a WritableStream<VideoFrame> on track.writable for writing frames.\n */\nclass FallbackGenerator {\n constructor({ kind, signalTarget }: TrackGeneratorOptions) {\n if (kind !== 'video') {\n throw new Error('Only video tracks are supported');\n }\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d', { desynchronized: true });\n if (!ctx) {\n throw new Error('Failed to get 2D context from canvas');\n }\n\n const mediaStream = canvas.captureStream();\n const track =\n mediaStream.getVideoTracks()[0] as MediaStreamTrackGenerator<VideoFrame>;\n\n const height = signalTarget?.getSettings().height;\n const width = signalTarget?.getSettings().width;\n if (height && width) {\n canvas.height = height;\n canvas.width = width;\n }\n\n if (!track) {\n throw new Error('Failed to create canvas track');\n }\n\n if (signalTarget) {\n signalTarget.addEventListener('ended', () => {\n track.stop();\n });\n }\n\n track.writable = new WritableStream({\n write: (frame: VideoFrame) => {\n if (\n canvas.width !== frame.displayWidth ||\n canvas.height !== frame.displayHeight\n ) {\n canvas.width = frame.displayWidth;\n canvas.height = frame.displayHeight;\n }\n\n ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);\n frame.close();\n },\n abort: () => {\n track.stop();\n },\n close: () => {\n track.stop();\n },\n });\n\n return track as MediaStreamTrackGenerator<VideoFrame>;\n }\n}\n\ntype VideoTrackGeneratorConstructor = new (\n options: TrackGeneratorOptions,\n) => MediaStreamTrackGenerator<VideoFrame>;\n\nconst TrackGenerator: VideoTrackGeneratorConstructor =\n typeof MediaStreamTrackGenerator !== 'undefined'\n ? (MediaStreamTrackGenerator as unknown as VideoTrackGeneratorConstructor)\n : (FallbackGenerator as unknown as VideoTrackGeneratorConstructor);\n\nexport { TrackGenerator };\n"],"names":[],"mappings":"AAgBA;;;;;AAKG;AACH,MAAM,iBAAiB,CAAA;AACrB,IAAA,WAAA,CAAY,EAAE,IAAI,EAAE,YAAY,EAAyB,EAAA;AACvD,QAAA,IAAI,IAAI,KAAK,OAAO,EAAE;AACpB,YAAA,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC;QACpD;QAEA,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;AAC/C,QAAA,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;QAC7D,IAAI,CAAC,GAAG,EAAE;AACR,YAAA,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC;QACzD;AAEA,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,EAAE;QAC1C,MAAM,KAAK,GACT,WAAW,CAAC,cAAc,EAAE,CAAC,CAAC,CAA0C;QAE1E,MAAM,MAAM,GAAG,YAAY,EAAE,WAAW,EAAE,CAAC,MAAM;QACjD,MAAM,KAAK,GAAG,YAAY,EAAE,WAAW,EAAE,CAAC,KAAK;AAC/C,QAAA,IAAI,MAAM,IAAI,KAAK,EAAE;AACnB,YAAA,MAAM,CAAC,MAAM,GAAG,MAAM;AACtB,YAAA,MAAM,CAAC,KAAK,GAAG,KAAK;QACtB;QAEA,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC;QAClD;QAEA,IAAI,YAAY,EAAE;AAChB,YAAA,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAK;gBAC1C,KAAK,CAAC,IAAI,EAAE;AACd,YAAA,CAAC,CAAC;QACJ;AAEA,QAAA,KAAK,CAAC,QAAQ,GAAG,IAAI,cAAc,CAAC;AAClC,YAAA,KAAK,EAAE,CAAC,KAAiB,KAAI;AAC3B,gBAAA,IACE,MAAM,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY;AACnC,oBAAA,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,aAAa,EACrC;AACA,oBAAA,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,YAAY;AACjC,oBAAA,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,aAAa;gBACrC;AAEA,gBAAA,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;gBACvD,KAAK,CAAC,KAAK,EAAE;YACf,CAAC;YACD,KAAK,EAAE,MAAK;gBACV,KAAK,CAAC,IAAI,EAAE;YACd,CAAC;YACD,KAAK,EAAE,MAAK;gBACV,KAAK,CAAC,IAAI,EAAE;YACd,CAAC;AACF,SAAA,CAAC;AAEF,QAAA,OAAO,KAA8C;IACvD;AACD;AAMD,MAAM,cAAc,GAClB,OAAO,yBAAyB,KAAK;AACnC,MAAG;MACA;;;;"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { WorkerTimer } from '@stream-io/worker-timer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fallback implementation for browsers without MediaStreamTrackGenerator.
|
|
5
|
+
*
|
|
6
|
+
* Produces a video MediaStreamTrack sourced from a canvas and exposes a
|
|
7
|
+
* WritableStream<VideoFrame> on track.writable. Written frames are drawn
|
|
8
|
+
* into the canvas and update the underlying track automatically.
|
|
9
|
+
*/
|
|
10
|
+
class FallbackProcessor {
|
|
11
|
+
readable;
|
|
12
|
+
workerTimer;
|
|
13
|
+
video;
|
|
14
|
+
constructor({ track }) {
|
|
15
|
+
if (!track)
|
|
16
|
+
throw new Error('MediaStreamTrack is required');
|
|
17
|
+
if (track.kind !== 'video') {
|
|
18
|
+
throw new Error('MediaStreamTrack must be video');
|
|
19
|
+
}
|
|
20
|
+
let running = true;
|
|
21
|
+
this.video = document.createElement('video');
|
|
22
|
+
this.video.muted = true;
|
|
23
|
+
this.video.playsInline = true;
|
|
24
|
+
this.video.srcObject = new MediaStream([track]);
|
|
25
|
+
const canvas = new OffscreenCanvas(1, 1);
|
|
26
|
+
const ctx = canvas.getContext('2d');
|
|
27
|
+
if (!ctx)
|
|
28
|
+
throw new Error('Failed to get 2D context from OffscreenCanvas');
|
|
29
|
+
let timestamp = 0;
|
|
30
|
+
const frameRate = track.getSettings().frameRate || 30;
|
|
31
|
+
let frameDuration = 1000 / frameRate;
|
|
32
|
+
this.workerTimer = new WorkerTimer({ useWorker: true });
|
|
33
|
+
this.readable = new ReadableStream({
|
|
34
|
+
start: async () => {
|
|
35
|
+
await Promise.all([
|
|
36
|
+
this.video.play(),
|
|
37
|
+
new Promise((r) => this.video.addEventListener('loadeddata', r, { once: true })),
|
|
38
|
+
]);
|
|
39
|
+
frameDuration = 1000 / (track.getSettings().frameRate || 30);
|
|
40
|
+
timestamp = performance.now();
|
|
41
|
+
},
|
|
42
|
+
pull: async (controller) => {
|
|
43
|
+
if (!running) {
|
|
44
|
+
controller.close();
|
|
45
|
+
this.close();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const delta = performance.now() - timestamp;
|
|
49
|
+
if (delta <= frameDuration) {
|
|
50
|
+
await new Promise((r) => this.workerTimer.setTimeout(r, frameDuration - delta));
|
|
51
|
+
}
|
|
52
|
+
timestamp = performance.now();
|
|
53
|
+
if (canvas.width !== this.video.videoWidth ||
|
|
54
|
+
canvas.height !== this.video.videoHeight) {
|
|
55
|
+
canvas.width = this.video.videoWidth;
|
|
56
|
+
canvas.height = this.video.videoHeight;
|
|
57
|
+
}
|
|
58
|
+
ctx.drawImage(this.video, 0, 0);
|
|
59
|
+
try {
|
|
60
|
+
const frame = new VideoFrame(canvas, {
|
|
61
|
+
timestamp: Math.round(this.video.currentTime * 1000000),
|
|
62
|
+
});
|
|
63
|
+
controller.enqueue(frame);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
running = false;
|
|
67
|
+
controller.error(err);
|
|
68
|
+
this.close();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
cancel: () => {
|
|
72
|
+
running = false;
|
|
73
|
+
this.close();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
close = () => {
|
|
78
|
+
this.video.pause();
|
|
79
|
+
this.video.srcObject = null;
|
|
80
|
+
this.video.src = '';
|
|
81
|
+
this.workerTimer.destroy();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const TrackProcessor = typeof MediaStreamTrackProcessor !== 'undefined'
|
|
85
|
+
? MediaStreamTrackProcessor
|
|
86
|
+
: FallbackProcessor;
|
|
87
|
+
|
|
88
|
+
export { TrackProcessor };
|
|
89
|
+
//# sourceMappingURL=FallbackProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FallbackProcessor.js","sources":["../../../../../src/FallbackProcessor.ts"],"sourcesContent":["import { WorkerTimer } from '@stream-io/worker-timer';\n\n/**\n * Representing a video track processor that can be either the native\n * MediaStreamTrackProcessor or the fallback implementation.\n */\nexport interface MediaStreamTrackProcessor<T> {\n readonly readable: ReadableStream<T>;\n}\n\n/**\n * Configuration options for creating a track processor.\n */\nexport interface TrackProcessorOptions {\n readonly track: MediaStreamTrack;\n}\n\n/**\n * Fallback implementation for browsers without MediaStreamTrackGenerator.\n *\n * Produces a video MediaStreamTrack sourced from a canvas and exposes a\n * WritableStream<VideoFrame> on track.writable. Written frames are drawn\n * into the canvas and update the underlying track automatically.\n */\nclass FallbackProcessor implements MediaStreamTrackProcessor<VideoFrame> {\n readonly readable: ReadableStream<VideoFrame>;\n\n readonly workerTimer: WorkerTimer;\n readonly video: HTMLVideoElement;\n\n constructor({ track }: TrackProcessorOptions) {\n if (!track) throw new Error('MediaStreamTrack is required');\n if (track.kind !== 'video') {\n throw new Error('MediaStreamTrack must be video');\n }\n let running = true;\n\n this.video = document.createElement('video');\n\n this.video.muted = true;\n this.video.playsInline = true;\n this.video.srcObject = new MediaStream([track]);\n\n const canvas = new OffscreenCanvas(1, 1);\n const ctx = canvas.getContext('2d');\n\n if (!ctx) throw new Error('Failed to get 2D context from OffscreenCanvas');\n\n let timestamp = 0;\n const frameRate = track.getSettings().frameRate || 30;\n let frameDuration = 1000 / frameRate;\n\n this.workerTimer = new WorkerTimer({ useWorker: true });\n this.readable = new ReadableStream({\n start: async () => {\n await Promise.all([\n this.video.play(),\n new Promise((r) =>\n this.video.addEventListener('loadeddata', r, { once: true }),\n ),\n ]);\n frameDuration = 1000 / (track.getSettings().frameRate || 30);\n timestamp = performance.now();\n },\n pull: async (controller) => {\n if (!running) {\n controller.close();\n this.close();\n return;\n }\n const delta = performance.now() - timestamp;\n if (delta <= frameDuration) {\n await new Promise((r: (value?: unknown) => void) =>\n this.workerTimer.setTimeout(r, frameDuration - delta),\n );\n }\n timestamp = performance.now();\n\n if (\n canvas.width !== this.video.videoWidth ||\n canvas.height !== this.video.videoHeight\n ) {\n canvas.width = this.video.videoWidth;\n canvas.height = this.video.videoHeight;\n }\n\n ctx.drawImage(this.video, 0, 0);\n\n try {\n const frame = new VideoFrame(canvas, {\n timestamp: Math.round(this.video.currentTime * 1000000),\n });\n\n controller.enqueue(frame);\n } catch (err) {\n running = false;\n controller.error(err);\n this.close();\n }\n },\n cancel: () => {\n running = false;\n this.close();\n },\n });\n }\n\n public close = () => {\n this.video.pause();\n this.video.srcObject = null;\n this.video.src = '';\n\n this.workerTimer.destroy();\n };\n}\n\nconst TrackProcessor =\n typeof MediaStreamTrackProcessor !== 'undefined'\n ? MediaStreamTrackProcessor\n : FallbackProcessor;\n\nexport { TrackProcessor };\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BaseVideoProcessor } from './BaseVideoProcessor.js';
|
|
2
|
+
import { FullScreenBlurRenderer } from './FullScreenBlurRenderer.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A video filter that applies a full-screen blur to each frame.
|
|
6
|
+
*
|
|
7
|
+
* It uses a WebGL renderer to blur the incoming camera track and outputs
|
|
8
|
+
* a new track with the effect applied. Setup and frame handling are managed
|
|
9
|
+
* by the base processor.
|
|
10
|
+
*/
|
|
11
|
+
class FullScreenBlur extends BaseVideoProcessor {
|
|
12
|
+
blurRenderer;
|
|
13
|
+
blurRadius;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new full-screen blur processor for the given video track.
|
|
16
|
+
*
|
|
17
|
+
* @param track - The input camera track to blur.
|
|
18
|
+
* @param options - Optional settings such as the blur radius.
|
|
19
|
+
* @param hooks - Optional callbacks for stats and error reporting.
|
|
20
|
+
*/
|
|
21
|
+
constructor(track, options = {}, hooks = {}) {
|
|
22
|
+
super(track, hooks);
|
|
23
|
+
this.blurRadius = options.blurRadius ?? 6;
|
|
24
|
+
}
|
|
25
|
+
async initialize() {
|
|
26
|
+
this.blurRenderer = new FullScreenBlurRenderer(this.canvas);
|
|
27
|
+
}
|
|
28
|
+
async transform(frame) {
|
|
29
|
+
this.blurRenderer.render(frame, this.blurRadius);
|
|
30
|
+
return new VideoFrame(this.canvas, { timestamp: frame.timestamp });
|
|
31
|
+
}
|
|
32
|
+
onStop() {
|
|
33
|
+
this.blurRenderer?.close();
|
|
34
|
+
}
|
|
35
|
+
get processorName() {
|
|
36
|
+
return 'fullscreen-blur';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { FullScreenBlur };
|
|
41
|
+
//# sourceMappingURL=FullScreenBlur.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FullScreenBlur.js","sources":["../../../../../src/FullScreenBlur.ts"],"sourcesContent":["import { VideoTrackProcessorHooks } from './types';\nimport { BaseVideoProcessor } from './BaseVideoProcessor';\nimport { FullScreenBlurRenderer } from './FullScreenBlurRenderer';\n\nexport interface FullScreenBlurOptions {\n blurRadius?: number;\n}\n\n/**\n * A video filter that applies a full-screen blur to each frame.\n *\n * It uses a WebGL renderer to blur the incoming camera track and outputs\n * a new track with the effect applied. Setup and frame handling are managed\n * by the base processor.\n */\nexport class FullScreenBlur extends BaseVideoProcessor {\n private blurRenderer!: FullScreenBlurRenderer;\n private readonly blurRadius: number;\n\n /**\n * Creates a new full-screen blur processor for the given video track.\n *\n * @param track - The input camera track to blur.\n * @param options - Optional settings such as the blur radius.\n * @param hooks - Optional callbacks for stats and error reporting.\n */\n constructor(\n track: MediaStreamVideoTrack,\n options: FullScreenBlurOptions = {},\n hooks: VideoTrackProcessorHooks = {},\n ) {\n super(track, hooks);\n this.blurRadius = options.blurRadius ?? 6;\n }\n\n protected async initialize(): Promise<void> {\n this.blurRenderer = new FullScreenBlurRenderer(this.canvas);\n }\n\n protected async transform(frame: VideoFrame): Promise<VideoFrame> {\n this.blurRenderer.render(frame, this.blurRadius);\n return new VideoFrame(this.canvas, { timestamp: frame.timestamp });\n }\n\n protected onStop(): void {\n this.blurRenderer?.close();\n }\n\n protected get processorName(): string {\n return 'fullscreen-blur';\n }\n}\n"],"names":[],"mappings":";;;AAQA;;;;;;AAMG;AACG,MAAO,cAAe,SAAQ,kBAAkB,CAAA;AAC5C,IAAA,YAAY;AACH,IAAA,UAAU;AAE3B;;;;;;AAMG;AACH,IAAA,WAAA,CACE,KAA4B,EAC5B,OAAA,GAAiC,EAAE,EACnC,QAAkC,EAAE,EAAA;AAEpC,QAAA,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC;IAC3C;AAEU,IAAA,MAAM,UAAU,GAAA;QACxB,IAAI,CAAC,YAAY,GAAG,IAAI,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC;IAC7D;IAEU,MAAM,SAAS,CAAC,KAAiB,EAAA;QACzC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC;AAChD,QAAA,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;IACpE;IAEU,MAAM,GAAA;AACd,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;IAC5B;AAEA,IAAA,IAAc,aAAa,GAAA;AACzB,QAAA,OAAO,iBAAiB;IAC1B;AACD;;;;"}
|