11import {
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';
1312import { Call , disposeOfMediaStream } from '@stream-io/video-client' ;
1413import {
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' ;
2825import 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;
3942const DEVIATION_LIMIT = 0.5 ;
4043const 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 */
26587export 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
492320const 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
526356const 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