@stream-io/video-filters-web 0.8.2 → 0.8.4
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 +16 -0
- package/dist/index.cjs.js +245 -244
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +245 -245
- package/dist/index.es.js.map +1 -1
- package/index.ts +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export { SegmentationLevel } from './src/legacy/segmentation';
|
|
|
4
4
|
export * from './src/legacy/tflite';
|
|
5
5
|
export * from './src/mediapipe';
|
|
6
6
|
export * from './src/types';
|
|
7
|
+
export * from './src/BaseVideoProcessor';
|
|
7
8
|
export * from './src/VirtualBackground';
|
|
8
9
|
export * from './src/FullScreenBlur';
|
package/dist/index.es.js
CHANGED
|
@@ -1559,7 +1559,7 @@ const createTFLiteSIMDModule = (__Module) => {
|
|
|
1559
1559
|
return __Module.ready;
|
|
1560
1560
|
};
|
|
1561
1561
|
|
|
1562
|
-
const version = "0.8.
|
|
1562
|
+
const version = "0.8.4";
|
|
1563
1563
|
const packageName = "@stream-io/video-filters-web";
|
|
1564
1564
|
|
|
1565
1565
|
// @ts-expect-error - module is not declared
|
|
@@ -1609,6 +1609,249 @@ const BACKGROUND_BLUR_MAP = {
|
|
|
1609
1609
|
high: 7,
|
|
1610
1610
|
};
|
|
1611
1611
|
|
|
1612
|
+
/**
|
|
1613
|
+
* Fallback video processor for browsers that do not support MediaStreamTrackGenerator.
|
|
1614
|
+
*
|
|
1615
|
+
* Produces a video MediaStreamTrack sourced from a canvas and exposes
|
|
1616
|
+
* a WritableStream<VideoFrame> on track.writable for writing frames.
|
|
1617
|
+
*/
|
|
1618
|
+
class FallbackGenerator {
|
|
1619
|
+
constructor({ kind, signalTarget }) {
|
|
1620
|
+
if (kind !== 'video') {
|
|
1621
|
+
throw new Error('Only video tracks are supported');
|
|
1622
|
+
}
|
|
1623
|
+
const canvas = document.createElement('canvas');
|
|
1624
|
+
const ctx = canvas.getContext('2d', { desynchronized: true });
|
|
1625
|
+
if (!ctx) {
|
|
1626
|
+
throw new Error('Failed to get 2D context from canvas');
|
|
1627
|
+
}
|
|
1628
|
+
const mediaStream = canvas.captureStream();
|
|
1629
|
+
const track = mediaStream.getVideoTracks()[0];
|
|
1630
|
+
const height = signalTarget?.getSettings().height;
|
|
1631
|
+
const width = signalTarget?.getSettings().width;
|
|
1632
|
+
if (height && width) {
|
|
1633
|
+
canvas.height = height;
|
|
1634
|
+
canvas.width = width;
|
|
1635
|
+
}
|
|
1636
|
+
if (!track) {
|
|
1637
|
+
throw new Error('Failed to create canvas track');
|
|
1638
|
+
}
|
|
1639
|
+
if (signalTarget) {
|
|
1640
|
+
signalTarget.addEventListener('ended', () => {
|
|
1641
|
+
track.stop();
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
track.writable = new WritableStream({
|
|
1645
|
+
write: (frame) => {
|
|
1646
|
+
if (canvas.width !== frame.displayWidth ||
|
|
1647
|
+
canvas.height !== frame.displayHeight) {
|
|
1648
|
+
canvas.width = frame.displayWidth;
|
|
1649
|
+
canvas.height = frame.displayHeight;
|
|
1650
|
+
}
|
|
1651
|
+
ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
|
|
1652
|
+
frame.close();
|
|
1653
|
+
},
|
|
1654
|
+
abort: () => {
|
|
1655
|
+
track.stop();
|
|
1656
|
+
},
|
|
1657
|
+
close: () => {
|
|
1658
|
+
track.stop();
|
|
1659
|
+
},
|
|
1660
|
+
});
|
|
1661
|
+
return track;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
const TrackGenerator = typeof MediaStreamTrackGenerator !== 'undefined'
|
|
1665
|
+
? MediaStreamTrackGenerator
|
|
1666
|
+
: FallbackGenerator;
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* Fallback implementation for browsers without MediaStreamTrackGenerator.
|
|
1670
|
+
*
|
|
1671
|
+
* Produces a video MediaStreamTrack sourced from a canvas and exposes a
|
|
1672
|
+
* WritableStream<VideoFrame> on track.writable. Written frames are drawn
|
|
1673
|
+
* into the canvas and update the underlying track automatically.
|
|
1674
|
+
*/
|
|
1675
|
+
class FallbackProcessor {
|
|
1676
|
+
readable;
|
|
1677
|
+
workerTimer;
|
|
1678
|
+
video;
|
|
1679
|
+
constructor({ track }) {
|
|
1680
|
+
if (!track)
|
|
1681
|
+
throw new Error('MediaStreamTrack is required');
|
|
1682
|
+
if (track.kind !== 'video') {
|
|
1683
|
+
throw new Error('MediaStreamTrack must be video');
|
|
1684
|
+
}
|
|
1685
|
+
let running = true;
|
|
1686
|
+
this.video = document.createElement('video');
|
|
1687
|
+
this.video.muted = true;
|
|
1688
|
+
this.video.playsInline = true;
|
|
1689
|
+
this.video.srcObject = new MediaStream([track]);
|
|
1690
|
+
const canvas = new OffscreenCanvas(1, 1);
|
|
1691
|
+
const ctx = canvas.getContext('2d');
|
|
1692
|
+
if (!ctx)
|
|
1693
|
+
throw new Error('Failed to get 2D context from OffscreenCanvas');
|
|
1694
|
+
let timestamp = 0;
|
|
1695
|
+
const frameRate = track.getSettings().frameRate || 30;
|
|
1696
|
+
let frameDuration = 1000 / frameRate;
|
|
1697
|
+
this.workerTimer = new WorkerTimer({ useWorker: true });
|
|
1698
|
+
this.readable = new ReadableStream({
|
|
1699
|
+
start: async () => {
|
|
1700
|
+
await Promise.all([
|
|
1701
|
+
this.video.play(),
|
|
1702
|
+
new Promise((r) => this.video.addEventListener('loadeddata', r, { once: true })),
|
|
1703
|
+
]);
|
|
1704
|
+
frameDuration = 1000 / (track.getSettings().frameRate || 30);
|
|
1705
|
+
timestamp = performance.now();
|
|
1706
|
+
},
|
|
1707
|
+
pull: async (controller) => {
|
|
1708
|
+
if (!running) {
|
|
1709
|
+
controller.close();
|
|
1710
|
+
this.close();
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const delta = performance.now() - timestamp;
|
|
1714
|
+
if (delta <= frameDuration) {
|
|
1715
|
+
await new Promise((r) => this.workerTimer.setTimeout(r, frameDuration - delta));
|
|
1716
|
+
}
|
|
1717
|
+
timestamp = performance.now();
|
|
1718
|
+
if (canvas.width !== this.video.videoWidth ||
|
|
1719
|
+
canvas.height !== this.video.videoHeight) {
|
|
1720
|
+
canvas.width = this.video.videoWidth;
|
|
1721
|
+
canvas.height = this.video.videoHeight;
|
|
1722
|
+
}
|
|
1723
|
+
ctx.drawImage(this.video, 0, 0);
|
|
1724
|
+
try {
|
|
1725
|
+
const frame = new VideoFrame(canvas, {
|
|
1726
|
+
timestamp: Math.round(this.video.currentTime * 1000000),
|
|
1727
|
+
});
|
|
1728
|
+
controller.enqueue(frame);
|
|
1729
|
+
}
|
|
1730
|
+
catch (err) {
|
|
1731
|
+
running = false;
|
|
1732
|
+
controller.error(err);
|
|
1733
|
+
this.close();
|
|
1734
|
+
}
|
|
1735
|
+
},
|
|
1736
|
+
cancel: () => {
|
|
1737
|
+
running = false;
|
|
1738
|
+
this.close();
|
|
1739
|
+
},
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
close = () => {
|
|
1743
|
+
this.video.pause();
|
|
1744
|
+
this.video.srcObject = null;
|
|
1745
|
+
this.video.src = '';
|
|
1746
|
+
this.workerTimer.destroy();
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
const TrackProcessor = typeof MediaStreamTrackProcessor !== 'undefined'
|
|
1750
|
+
? MediaStreamTrackProcessor
|
|
1751
|
+
: FallbackProcessor;
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Base class for real-time video filters.
|
|
1755
|
+
*
|
|
1756
|
+
* It sets up the full pipeline that reads frames from the input track,
|
|
1757
|
+
* processes them, and outputs a new track with your effect applied. Subclasses
|
|
1758
|
+
* only need to implement `initialize` (run once before processing starts) and
|
|
1759
|
+
* `transform` (called for every frame).
|
|
1760
|
+
*
|
|
1761
|
+
* Everything else—canvas setup, performance tracking, error handling, and
|
|
1762
|
+
* clean shutdown is handled for you. Calling `start()` returns a processed
|
|
1763
|
+
* `MediaStreamTrack` ready to use.
|
|
1764
|
+
*/
|
|
1765
|
+
class BaseVideoProcessor {
|
|
1766
|
+
track;
|
|
1767
|
+
processor;
|
|
1768
|
+
generator;
|
|
1769
|
+
hooks;
|
|
1770
|
+
abortController = new AbortController();
|
|
1771
|
+
canvas;
|
|
1772
|
+
frames = 0;
|
|
1773
|
+
delayTotal = 0;
|
|
1774
|
+
lastStatsTime = 0;
|
|
1775
|
+
/**
|
|
1776
|
+
* Constructs a new instance.
|
|
1777
|
+
*/
|
|
1778
|
+
constructor(track, hooks = {}) {
|
|
1779
|
+
this.track = track;
|
|
1780
|
+
this.processor = new TrackProcessor({ track });
|
|
1781
|
+
this.generator = new TrackGenerator({
|
|
1782
|
+
kind: 'video',
|
|
1783
|
+
signalTarget: track,
|
|
1784
|
+
});
|
|
1785
|
+
this.hooks = hooks;
|
|
1786
|
+
}
|
|
1787
|
+
async start() {
|
|
1788
|
+
const { readable } = this.processor;
|
|
1789
|
+
const { writable } = this.generator;
|
|
1790
|
+
const { width = 1280, height = 720 } = this.track.getSettings();
|
|
1791
|
+
this.canvas = new OffscreenCanvas(width, height);
|
|
1792
|
+
await this.initialize();
|
|
1793
|
+
const transformStream = new TransformStream({
|
|
1794
|
+
transform: async (frame, controller) => {
|
|
1795
|
+
try {
|
|
1796
|
+
if (this.abortController.signal.aborted)
|
|
1797
|
+
return frame.close();
|
|
1798
|
+
if (this.canvas.width !== frame.displayWidth ||
|
|
1799
|
+
this.canvas.height !== frame.displayHeight) {
|
|
1800
|
+
this.canvas.width = frame.displayWidth;
|
|
1801
|
+
this.canvas.height = frame.displayHeight;
|
|
1802
|
+
}
|
|
1803
|
+
const processed = await this.transform(frame);
|
|
1804
|
+
controller.enqueue(processed);
|
|
1805
|
+
}
|
|
1806
|
+
catch (e) {
|
|
1807
|
+
this.hooks.onError?.(e);
|
|
1808
|
+
}
|
|
1809
|
+
finally {
|
|
1810
|
+
frame.close();
|
|
1811
|
+
}
|
|
1812
|
+
},
|
|
1813
|
+
flush: () => this.onFlush(),
|
|
1814
|
+
});
|
|
1815
|
+
readable
|
|
1816
|
+
.pipeThrough(transformStream, { signal: this.abortController.signal })
|
|
1817
|
+
.pipeTo(writable, { signal: this.abortController.signal })
|
|
1818
|
+
.catch((e) => {
|
|
1819
|
+
if (e.name !== 'AbortError' && e.name !== 'InvalidStateError') {
|
|
1820
|
+
console.error(`[${this.processorName}] Error processing track:`, e);
|
|
1821
|
+
this.hooks.onError?.(e);
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
return this.generator;
|
|
1825
|
+
}
|
|
1826
|
+
stop() {
|
|
1827
|
+
this.abortController.abort();
|
|
1828
|
+
this.generator.stop();
|
|
1829
|
+
this.onStop();
|
|
1830
|
+
}
|
|
1831
|
+
updateStats(delay) {
|
|
1832
|
+
this.frames++;
|
|
1833
|
+
this.delayTotal += delay;
|
|
1834
|
+
const now = performance.now();
|
|
1835
|
+
if (this.lastStatsTime === 0) {
|
|
1836
|
+
this.lastStatsTime = now;
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (now - this.lastStatsTime >= 1000) {
|
|
1840
|
+
const avgDelay = Math.round((this.delayTotal / this.frames) * 100) / 100;
|
|
1841
|
+
const fps = Math.round((1000 * this.frames) / (now - this.lastStatsTime));
|
|
1842
|
+
this.hooks.onStats?.({ delay: avgDelay, fps, timestamp: now });
|
|
1843
|
+
this.frames = 0;
|
|
1844
|
+
this.delayTotal = 0;
|
|
1845
|
+
this.lastStatsTime = now;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
onFlush() { }
|
|
1849
|
+
onStop() { }
|
|
1850
|
+
get processorName() {
|
|
1851
|
+
return 'base-processor';
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1612
1855
|
class WebGLRenderer {
|
|
1613
1856
|
canvas;
|
|
1614
1857
|
gl;
|
|
@@ -2330,249 +2573,6 @@ class WebGLRenderer {
|
|
|
2330
2573
|
}
|
|
2331
2574
|
}
|
|
2332
2575
|
|
|
2333
|
-
/**
|
|
2334
|
-
* Fallback video processor for browsers that do not support MediaStreamTrackGenerator.
|
|
2335
|
-
*
|
|
2336
|
-
* Produces a video MediaStreamTrack sourced from a canvas and exposes
|
|
2337
|
-
* a WritableStream<VideoFrame> on track.writable for writing frames.
|
|
2338
|
-
*/
|
|
2339
|
-
class FallbackGenerator {
|
|
2340
|
-
constructor({ kind, signalTarget }) {
|
|
2341
|
-
if (kind !== 'video') {
|
|
2342
|
-
throw new Error('Only video tracks are supported');
|
|
2343
|
-
}
|
|
2344
|
-
const canvas = document.createElement('canvas');
|
|
2345
|
-
const ctx = canvas.getContext('2d', { desynchronized: true });
|
|
2346
|
-
if (!ctx) {
|
|
2347
|
-
throw new Error('Failed to get 2D context from canvas');
|
|
2348
|
-
}
|
|
2349
|
-
const mediaStream = canvas.captureStream();
|
|
2350
|
-
const track = mediaStream.getVideoTracks()[0];
|
|
2351
|
-
const height = signalTarget?.getSettings().height;
|
|
2352
|
-
const width = signalTarget?.getSettings().width;
|
|
2353
|
-
if (height && width) {
|
|
2354
|
-
canvas.height = height;
|
|
2355
|
-
canvas.width = width;
|
|
2356
|
-
}
|
|
2357
|
-
if (!track) {
|
|
2358
|
-
throw new Error('Failed to create canvas track');
|
|
2359
|
-
}
|
|
2360
|
-
if (signalTarget) {
|
|
2361
|
-
signalTarget.addEventListener('ended', () => {
|
|
2362
|
-
track.stop();
|
|
2363
|
-
});
|
|
2364
|
-
}
|
|
2365
|
-
track.writable = new WritableStream({
|
|
2366
|
-
write: (frame) => {
|
|
2367
|
-
if (canvas.width !== frame.displayWidth ||
|
|
2368
|
-
canvas.height !== frame.displayHeight) {
|
|
2369
|
-
canvas.width = frame.displayWidth;
|
|
2370
|
-
canvas.height = frame.displayHeight;
|
|
2371
|
-
}
|
|
2372
|
-
ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
|
|
2373
|
-
frame.close();
|
|
2374
|
-
},
|
|
2375
|
-
abort: () => {
|
|
2376
|
-
track.stop();
|
|
2377
|
-
},
|
|
2378
|
-
close: () => {
|
|
2379
|
-
track.stop();
|
|
2380
|
-
},
|
|
2381
|
-
});
|
|
2382
|
-
return track;
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
const TrackGenerator = typeof MediaStreamTrackGenerator !== 'undefined'
|
|
2386
|
-
? MediaStreamTrackGenerator
|
|
2387
|
-
: FallbackGenerator;
|
|
2388
|
-
|
|
2389
|
-
/**
|
|
2390
|
-
* Fallback implementation for browsers without MediaStreamTrackGenerator.
|
|
2391
|
-
*
|
|
2392
|
-
* Produces a video MediaStreamTrack sourced from a canvas and exposes a
|
|
2393
|
-
* WritableStream<VideoFrame> on track.writable. Written frames are drawn
|
|
2394
|
-
* into the canvas and update the underlying track automatically.
|
|
2395
|
-
*/
|
|
2396
|
-
class FallbackProcessor {
|
|
2397
|
-
readable;
|
|
2398
|
-
workerTimer;
|
|
2399
|
-
video;
|
|
2400
|
-
constructor({ track }) {
|
|
2401
|
-
if (!track)
|
|
2402
|
-
throw new Error('MediaStreamTrack is required');
|
|
2403
|
-
if (track.kind !== 'video') {
|
|
2404
|
-
throw new Error('MediaStreamTrack must be video');
|
|
2405
|
-
}
|
|
2406
|
-
let running = true;
|
|
2407
|
-
this.video = document.createElement('video');
|
|
2408
|
-
this.video.muted = true;
|
|
2409
|
-
this.video.playsInline = true;
|
|
2410
|
-
this.video.srcObject = new MediaStream([track]);
|
|
2411
|
-
const canvas = new OffscreenCanvas(1, 1);
|
|
2412
|
-
const ctx = canvas.getContext('2d');
|
|
2413
|
-
if (!ctx)
|
|
2414
|
-
throw new Error('Failed to get 2D context from OffscreenCanvas');
|
|
2415
|
-
let timestamp = 0;
|
|
2416
|
-
const frameRate = track.getSettings().frameRate || 30;
|
|
2417
|
-
let frameDuration = 1000 / frameRate;
|
|
2418
|
-
this.workerTimer = new WorkerTimer({ useWorker: true });
|
|
2419
|
-
this.readable = new ReadableStream({
|
|
2420
|
-
start: async () => {
|
|
2421
|
-
await Promise.all([
|
|
2422
|
-
this.video.play(),
|
|
2423
|
-
new Promise((r) => this.video.addEventListener('loadeddata', r, { once: true })),
|
|
2424
|
-
]);
|
|
2425
|
-
frameDuration = 1000 / (track.getSettings().frameRate || 30);
|
|
2426
|
-
timestamp = performance.now();
|
|
2427
|
-
},
|
|
2428
|
-
pull: async (controller) => {
|
|
2429
|
-
if (!running) {
|
|
2430
|
-
controller.close();
|
|
2431
|
-
this.close();
|
|
2432
|
-
return;
|
|
2433
|
-
}
|
|
2434
|
-
const delta = performance.now() - timestamp;
|
|
2435
|
-
if (delta <= frameDuration) {
|
|
2436
|
-
await new Promise((r) => this.workerTimer.setTimeout(r, frameDuration - delta));
|
|
2437
|
-
}
|
|
2438
|
-
timestamp = performance.now();
|
|
2439
|
-
if (canvas.width !== this.video.videoWidth ||
|
|
2440
|
-
canvas.height !== this.video.videoHeight) {
|
|
2441
|
-
canvas.width = this.video.videoWidth;
|
|
2442
|
-
canvas.height = this.video.videoHeight;
|
|
2443
|
-
}
|
|
2444
|
-
ctx.drawImage(this.video, 0, 0);
|
|
2445
|
-
try {
|
|
2446
|
-
const frame = new VideoFrame(canvas, {
|
|
2447
|
-
timestamp: Math.round(this.video.currentTime * 1000000),
|
|
2448
|
-
});
|
|
2449
|
-
controller.enqueue(frame);
|
|
2450
|
-
}
|
|
2451
|
-
catch (err) {
|
|
2452
|
-
running = false;
|
|
2453
|
-
controller.error(err);
|
|
2454
|
-
this.close();
|
|
2455
|
-
}
|
|
2456
|
-
},
|
|
2457
|
-
cancel: () => {
|
|
2458
|
-
running = false;
|
|
2459
|
-
this.close();
|
|
2460
|
-
},
|
|
2461
|
-
});
|
|
2462
|
-
}
|
|
2463
|
-
close = () => {
|
|
2464
|
-
this.video.pause();
|
|
2465
|
-
this.video.srcObject = null;
|
|
2466
|
-
this.video.src = '';
|
|
2467
|
-
this.workerTimer.destroy();
|
|
2468
|
-
};
|
|
2469
|
-
}
|
|
2470
|
-
const TrackProcessor = typeof MediaStreamTrackProcessor !== 'undefined'
|
|
2471
|
-
? MediaStreamTrackProcessor
|
|
2472
|
-
: FallbackProcessor;
|
|
2473
|
-
|
|
2474
|
-
/**
|
|
2475
|
-
* Base class for real-time video filters.
|
|
2476
|
-
*
|
|
2477
|
-
* It sets up the full pipeline that reads frames from the input track,
|
|
2478
|
-
* processes them, and outputs a new track with your effect applied. Subclasses
|
|
2479
|
-
* only need to implement `initialize` (run once before processing starts) and
|
|
2480
|
-
* `transform` (called for every frame).
|
|
2481
|
-
*
|
|
2482
|
-
* Everything else—canvas setup, performance tracking, error handling, and
|
|
2483
|
-
* clean shutdown is handled for you. Calling `start()` returns a processed
|
|
2484
|
-
* `MediaStreamTrack` ready to use.
|
|
2485
|
-
*/
|
|
2486
|
-
class BaseVideoProcessor {
|
|
2487
|
-
track;
|
|
2488
|
-
processor;
|
|
2489
|
-
generator;
|
|
2490
|
-
hooks;
|
|
2491
|
-
abortController = new AbortController();
|
|
2492
|
-
canvas;
|
|
2493
|
-
frames = 0;
|
|
2494
|
-
delayTotal = 0;
|
|
2495
|
-
lastStatsTime = 0;
|
|
2496
|
-
/**
|
|
2497
|
-
* Constructs a new instance.
|
|
2498
|
-
*/
|
|
2499
|
-
constructor(track, hooks = {}) {
|
|
2500
|
-
this.track = track;
|
|
2501
|
-
this.processor = new TrackProcessor({ track });
|
|
2502
|
-
this.generator = new TrackGenerator({
|
|
2503
|
-
kind: 'video',
|
|
2504
|
-
signalTarget: track,
|
|
2505
|
-
});
|
|
2506
|
-
this.hooks = hooks;
|
|
2507
|
-
}
|
|
2508
|
-
async start() {
|
|
2509
|
-
const { readable } = this.processor;
|
|
2510
|
-
const { writable } = this.generator;
|
|
2511
|
-
const { width = 1280, height = 720 } = this.track.getSettings();
|
|
2512
|
-
this.canvas = new OffscreenCanvas(width, height);
|
|
2513
|
-
await this.initialize();
|
|
2514
|
-
const transformStream = new TransformStream({
|
|
2515
|
-
transform: async (frame, controller) => {
|
|
2516
|
-
try {
|
|
2517
|
-
if (this.abortController.signal.aborted)
|
|
2518
|
-
return frame.close();
|
|
2519
|
-
if (this.canvas.width !== frame.displayWidth ||
|
|
2520
|
-
this.canvas.height !== frame.displayHeight) {
|
|
2521
|
-
this.canvas.width = frame.displayWidth;
|
|
2522
|
-
this.canvas.height = frame.displayHeight;
|
|
2523
|
-
}
|
|
2524
|
-
const processed = await this.transform(frame);
|
|
2525
|
-
controller.enqueue(processed);
|
|
2526
|
-
}
|
|
2527
|
-
catch (e) {
|
|
2528
|
-
this.hooks.onError?.(e);
|
|
2529
|
-
}
|
|
2530
|
-
finally {
|
|
2531
|
-
frame.close();
|
|
2532
|
-
}
|
|
2533
|
-
},
|
|
2534
|
-
flush: () => this.onFlush(),
|
|
2535
|
-
});
|
|
2536
|
-
readable
|
|
2537
|
-
.pipeThrough(transformStream, { signal: this.abortController.signal })
|
|
2538
|
-
.pipeTo(writable, { signal: this.abortController.signal })
|
|
2539
|
-
.catch((e) => {
|
|
2540
|
-
if (e.name !== 'AbortError' && e.name !== 'InvalidStateError') {
|
|
2541
|
-
console.error(`[${this.processorName}] Error processing track:`, e);
|
|
2542
|
-
this.hooks.onError?.(e);
|
|
2543
|
-
}
|
|
2544
|
-
});
|
|
2545
|
-
return this.generator;
|
|
2546
|
-
}
|
|
2547
|
-
stop() {
|
|
2548
|
-
this.abortController.abort();
|
|
2549
|
-
this.generator.stop();
|
|
2550
|
-
this.onStop();
|
|
2551
|
-
}
|
|
2552
|
-
updateStats(delay) {
|
|
2553
|
-
this.frames++;
|
|
2554
|
-
this.delayTotal += delay;
|
|
2555
|
-
const now = performance.now();
|
|
2556
|
-
if (this.lastStatsTime === 0) {
|
|
2557
|
-
this.lastStatsTime = now;
|
|
2558
|
-
return;
|
|
2559
|
-
}
|
|
2560
|
-
if (now - this.lastStatsTime >= 1000) {
|
|
2561
|
-
const avgDelay = Math.round((this.delayTotal / this.frames) * 100) / 100;
|
|
2562
|
-
const fps = Math.round((1000 * this.frames) / (now - this.lastStatsTime));
|
|
2563
|
-
this.hooks.onStats?.({ delay: avgDelay, fps, timestamp: now });
|
|
2564
|
-
this.frames = 0;
|
|
2565
|
-
this.delayTotal = 0;
|
|
2566
|
-
this.lastStatsTime = now;
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
onFlush() { }
|
|
2570
|
-
onStop() { }
|
|
2571
|
-
get processorName() {
|
|
2572
|
-
return 'base-processor';
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
2576
|
/**
|
|
2577
2577
|
* Wraps a video MediaStreamTrack in a real-time processing pipeline.
|
|
2578
2578
|
* Incoming frames are processed through a transformer and re-emitted
|
|
@@ -3018,5 +3018,5 @@ class FullScreenBlur extends BaseVideoProcessor {
|
|
|
3018
3018
|
}
|
|
3019
3019
|
}
|
|
3020
3020
|
|
|
3021
|
-
export { BACKGROUND_BLUR_MAP, FullScreenBlur, SegmentationLevel, VirtualBackground, createRenderer, isMediaPipePlatformSupported, isPlatformSupported, loadMediaPipe, loadTFLite };
|
|
3021
|
+
export { BACKGROUND_BLUR_MAP, BaseVideoProcessor, FullScreenBlur, SegmentationLevel, VirtualBackground, createRenderer, isMediaPipePlatformSupported, isPlatformSupported, loadMediaPipe, loadTFLite };
|
|
3022
3022
|
//# sourceMappingURL=index.es.js.map
|