Skip to content

Commit 4f060c8

Browse files
committed
fix(perf): Chunk and lazy load BackgroundFiltersProvider
1 parent c4adc2a commit 4f060c8

File tree

5 files changed

+279
-220
lines changed

5 files changed

+279
-220
lines changed

packages/react-sdk/rollup.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ const chunkFileNames = (chunkInfo) => {
1111
if (chunkInfo.name.includes('CallStatsLatencyChart')) {
1212
return 'latency-chart-[hash].[format].js';
1313
}
14+
if (chunkInfo.name.includes('BackgroundFilters')) {
15+
return 'background-filters-[hash].[format].js';
16+
}
1417
return '[name]-[hash].[format].js';
1518
};
1619

packages/react-sdk/src/components/BackgroundFilters/BackgroundFilters.tsx

Lines changed: 50 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {
2-
createContext,
2+
Context,
33
PropsWithChildren,
44
useCallback,
5-
useContext,
65
useEffect,
76
useMemo,
87
useRef,
@@ -13,19 +12,23 @@ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
1312
import { Call, disposeOfMediaStream } from '@stream-io/video-client';
1413
import {
1514
BackgroundBlurLevel,
16-
BackgroundFilter,
1715
createRenderer,
18-
isPlatformSupported,
1916
isMediaPipePlatformSupported,
20-
loadTFLite,
17+
isPlatformSupported,
2118
loadMediaPipe,
22-
PlatformSupportFlags,
23-
VirtualBackground,
19+
loadTFLite,
20+
PerformanceStats,
2421
Renderer,
2522
TFLite,
26-
PerformanceStats,
23+
VirtualBackground,
2724
} from '@stream-io/video-filters-web';
2825
import clsx from 'clsx';
26+
import type {
27+
BackgroundFiltersPerformance,
28+
BackgroundFiltersProps,
29+
BackgroundFiltersContextValue,
30+
PerformanceDegradationReason,
31+
} from './types';
2932

3033
/**
3134
* Constants for FPS warning calculation.
@@ -39,32 +42,6 @@ const DEFAULT_FPS = 30;
3942
const DEVIATION_LIMIT = 0.5;
4043
const OUTLIER_PERSISTENCE = 5;
4144

42-
/**
43-
* Configuration for performance metric thresholds.
44-
*/
45-
export type BackgroundFiltersPerformanceThresholds = {
46-
/**
47-
* The lower FPS threshold for triggering a performance warning.
48-
* When the EMA FPS falls below this value, a warning is shown.
49-
* @default 23
50-
*/
51-
fpsWarningThresholdLower?: number;
52-
53-
/**
54-
* The upper FPS threshold for clearing a performance warning.
55-
* When the EMA FPS rises above this value, the warning is cleared.
56-
* @default 25
57-
*/
58-
fpsWarningThresholdUpper?: number;
59-
60-
/**
61-
* The default FPS value used as the initial value for the EMA (Exponential Moving Average)
62-
* calculation and when stats are unavailable or when resetting the filter.
63-
* @default 30
64-
*/
65-
defaultFps?: number;
66-
};
67-
6845
/**
6946
* Represents the available background filter processing engines.
7047
*/
@@ -74,160 +51,6 @@ enum FilterEngine {
7451
NONE,
7552
}
7653

77-
/**
78-
* Represents the possible reasons for background filter performance degradation.
79-
*/
80-
export enum PerformanceDegradationReason {
81-
FRAME_DROP = 'frame-drop',
82-
CPU_THROTTLING = 'cpu-throttling',
83-
}
84-
85-
export type BackgroundFiltersProps = PlatformSupportFlags & {
86-
/**
87-
* A list of URLs to use as background images.
88-
*/
89-
backgroundImages?: string[];
90-
91-
/**
92-
* The background filter to apply to the video (by default).
93-
* @default undefined no filter applied
94-
*/
95-
backgroundFilter?: BackgroundFilter;
96-
97-
/**
98-
* The URL of the image to use as the background (by default).
99-
*/
100-
backgroundImage?: string;
101-
102-
/**
103-
* The level of blur to apply to the background (by default).
104-
* @default 'high'.
105-
*/
106-
backgroundBlurLevel?: BackgroundBlurLevel;
107-
108-
/**
109-
* The base path for the TensorFlow Lite files.
110-
* @default 'https://unpkg.com/@stream-io/video-filters-web/mediapipe'.
111-
*/
112-
basePath?: string;
113-
114-
/**
115-
* The path to the TensorFlow Lite WebAssembly file.
116-
*
117-
* Override this prop to use a custom path to the TensorFlow Lite WebAssembly file
118-
* (e.g., if you choose to host it yourself).
119-
*/
120-
tfFilePath?: string;
121-
122-
/**
123-
* The path to the MediaPipe model file.
124-
* Override this prop to use a custom path to the MediaPipe model file
125-
* (e.g., if you choose to host it yourself).
126-
*/
127-
modelFilePath?: string;
128-
129-
/**
130-
* When true, the filter uses the legacy TensorFlow-based segmentation model.
131-
* When false, it uses the default MediaPipe Tasks Vision model.
132-
*
133-
* Only enable this if you need to mimic the behavior of older SDK versions.
134-
*/
135-
useLegacyFilter?: boolean;
136-
137-
/**
138-
* When a started filter encounters an error, this callback will be executed.
139-
* The default behavior (not overridable) is unregistering a failed filter.
140-
* Use this callback to display UI error message, disable the corresponding stream,
141-
* or to try registering the filter again.
142-
*/
143-
onError?: (error: any) => void;
144-
145-
/**
146-
* Configuration for performance metric thresholds.
147-
* Use this to customize when performance warnings are triggered.
148-
*/
149-
performanceThresholds?: BackgroundFiltersPerformanceThresholds;
150-
};
151-
152-
/**
153-
* Performance degradation information for background filters.
154-
*
155-
* Performance is calculated using an Exponential Moving Average (EMA) of FPS values
156-
* to smooth out quick spikes and provide stable performance warnings.
157-
*/
158-
export type BackgroundFiltersPerformance = {
159-
/**
160-
* Whether performance is currently degraded.
161-
*/
162-
degraded: boolean;
163-
/**
164-
* Reasons for performance degradation.
165-
*/
166-
reason?: Array<PerformanceDegradationReason>;
167-
};
168-
169-
export type BackgroundFiltersAPI = {
170-
/**
171-
* Whether the current platform supports the background filters.
172-
*/
173-
isSupported: boolean;
174-
175-
/**
176-
* Indicates whether the background filters engine is loaded and ready.
177-
*/
178-
isReady: boolean;
179-
180-
/**
181-
* Performance information for background filters.
182-
*/
183-
performance: BackgroundFiltersPerformance;
184-
185-
/**
186-
* Disables all background filters applied to the video.
187-
*/
188-
disableBackgroundFilter: () => void;
189-
190-
/**
191-
* Applies a background blur filter to the video.
192-
*
193-
* @param blurLevel the level of blur to apply to the background.
194-
*/
195-
applyBackgroundBlurFilter: (blurLevel: BackgroundBlurLevel) => void;
196-
197-
/**
198-
* Applies a background image filter to the video.
199-
*
200-
* @param imageUrl the URL of the image to use as the background.
201-
*/
202-
applyBackgroundImageFilter: (imageUrl: string) => void;
203-
};
204-
205-
/**
206-
* The context value for the background filters context.
207-
*/
208-
export type BackgroundFiltersContextValue = BackgroundFiltersProps &
209-
BackgroundFiltersAPI;
210-
211-
/**
212-
* The context for the background filters.
213-
*/
214-
const BackgroundFiltersContext = createContext<
215-
BackgroundFiltersContextValue | undefined
216-
>(undefined);
217-
218-
/**
219-
* A hook to access the background filters context API.
220-
*/
221-
export const useBackgroundFilters = () => {
222-
const context = useContext(BackgroundFiltersContext);
223-
if (!context) {
224-
throw new Error(
225-
'useBackgroundFilters must be used within a BackgroundFiltersProvider',
226-
);
227-
}
228-
return context;
229-
};
230-
23154
/**
23255
* Determines which filter engine is available.
23356
* MEDIA_PIPE is the default unless legacy filters are requested or MediaPipe is unsupported.
@@ -239,12 +62,11 @@ const determineEngine = async (
23962
forceSafariSupport: boolean | undefined,
24063
forceMobileSupport: boolean | undefined,
24164
): Promise<FilterEngine> => {
242-
const isTfPlatformSupported = await isPlatformSupported({
243-
forceSafariSupport,
244-
forceMobileSupport,
245-
});
246-
24765
if (useLegacyFilter) {
66+
const isTfPlatformSupported = await isPlatformSupported({
67+
forceSafariSupport,
68+
forceMobileSupport,
69+
});
24870
return isTfPlatformSupported ? FilterEngine.TF : FilterEngine.NONE;
24971
}
25072

@@ -263,9 +85,15 @@ const determineEngine = async (
26385
* in your project before using this component.
26486
*/
26587
export const BackgroundFiltersProvider = (
266-
props: PropsWithChildren<BackgroundFiltersProps>,
88+
props: PropsWithChildren<BackgroundFiltersProps> & {
89+
// for code splitting. Prevents circular dependency issues where
90+
// this Context needs to be present in the main chunk, but also
91+
// imported by the background filters chunk.
92+
ContextProvider: Context<BackgroundFiltersContextValue | undefined>;
93+
},
26794
) => {
26895
const {
96+
ContextProvider,
26997
children,
27098
backgroundImages = [],
27199
backgroundFilter: bgFilterFromProps = undefined,
@@ -340,7 +168,7 @@ export const BackgroundFiltersProvider = (
340168
const reasons: Array<PerformanceDegradationReason> = [];
341169

342170
if (showLowFpsWarning) {
343-
reasons.push(PerformanceDegradationReason.FRAME_DROP);
171+
reasons.push('frame-drop');
344172
}
345173

346174
const qualityLimitationReasons =
@@ -351,7 +179,7 @@ export const BackgroundFiltersProvider = (
351179
qualityLimitationReasons &&
352180
qualityLimitationReasons?.includes('cpu')
353181
) {
354-
reasons.push(PerformanceDegradationReason.CPU_THROTTLING);
182+
reasons.push('cpu-throttling');
355183
}
356184

357185
return {
@@ -458,52 +286,54 @@ export const BackgroundFiltersProvider = (
458286
);
459287

460288
const isReady = useLegacyFilter ? !!tfLite : !!mediaPipe;
289+
const contextValue: BackgroundFiltersContextValue = {
290+
isSupported,
291+
performance,
292+
isReady,
293+
backgroundImage,
294+
backgroundBlurLevel,
295+
backgroundFilter,
296+
disableBackgroundFilter,
297+
applyBackgroundBlurFilter,
298+
applyBackgroundImageFilter,
299+
backgroundImages,
300+
tfFilePath,
301+
modelFilePath,
302+
basePath,
303+
onError: handleError,
304+
};
461305
return (
462-
<BackgroundFiltersContext.Provider
463-
value={{
464-
isSupported,
465-
performance,
466-
isReady,
467-
backgroundImage,
468-
backgroundBlurLevel,
469-
backgroundFilter,
470-
disableBackgroundFilter,
471-
applyBackgroundBlurFilter,
472-
applyBackgroundImageFilter,
473-
backgroundImages,
474-
tfFilePath,
475-
modelFilePath,
476-
basePath,
477-
onError: handleError,
478-
}}
479-
>
306+
<ContextProvider.Provider value={contextValue}>
480307
{children}
481308
{isReady && (
482309
<BackgroundFilters
310+
api={contextValue}
483311
tfLite={tfLite}
484312
engine={engine}
485313
onStats={handleStats}
486314
/>
487315
)}
488-
</BackgroundFiltersContext.Provider>
316+
</ContextProvider.Provider>
489317
);
490318
};
491319

492320
const BackgroundFilters = (props: {
321+
api: BackgroundFiltersContextValue;
493322
tfLite?: TFLite;
494323
engine: FilterEngine;
495324
onStats: (stats: PerformanceStats) => void;
496325
}) => {
497326
const call = useCall();
498-
const { children, start } = useRenderer(props.tfLite, call, props.engine);
499-
const { onError, backgroundFilter } = useBackgroundFilters();
327+
const { engine, api, tfLite, onStats } = props;
328+
const { children, start } = useRenderer(api, tfLite, call, engine);
329+
const { onError, backgroundFilter } = api;
500330
const handleErrorRef = useRef<((error: any) => void) | undefined>(undefined);
501331
handleErrorRef.current = onError;
502332

503333
const handleStatsRef = useRef<
504334
((stats: PerformanceStats) => void) | undefined
505335
>(undefined);
506-
handleStatsRef.current = props.onStats;
336+
handleStatsRef.current = onStats;
507337

508338
useEffect(() => {
509339
if (!call || !backgroundFilter) return;
@@ -524,6 +354,7 @@ const BackgroundFilters = (props: {
524354
};
525355

526356
const useRenderer = (
357+
api: BackgroundFiltersContextValue,
527358
tfLite: TFLite | undefined,
528359
call: Call | undefined,
529360
engine: FilterEngine,
@@ -534,7 +365,7 @@ const useRenderer = (
534365
backgroundImage,
535366
modelFilePath,
536367
basePath,
537-
} = useBackgroundFilters();
368+
} = api;
538369

539370
const videoRef = useRef<HTMLVideoElement>(null);
540371
const canvasRef = useRef<HTMLCanvasElement>(null);

0 commit comments

Comments
 (0)