@stream-io/video-react-sdk 1.28.1 → 1.29.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.
@@ -1,8 +1,7 @@
1
1
  import {
2
- createContext,
2
+ Context,
3
3
  PropsWithChildren,
4
4
  useCallback,
5
- useContext,
6
5
  useEffect,
7
6
  useMemo,
8
7
  useRef,
@@ -13,19 +12,23 @@ import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
13
12
  import { Call, disposeOfMediaStream } from '@stream-io/video-client';
14
13
  import {
15
14
  BackgroundBlurLevel,
16
- BackgroundFilter,
17
15
  createRenderer,
18
- isPlatformSupported,
19
16
  isMediaPipePlatformSupported,
20
- loadTFLite,
17
+ isPlatformSupported,
21
18
  loadMediaPipe,
22
- PlatformSupportFlags,
23
- VirtualBackground,
19
+ loadTFLite,
20
+ PerformanceStats,
24
21
  Renderer,
25
22
  TFLite,
26
- PerformanceStats,
23
+ VirtualBackground,
27
24
  } from '@stream-io/video-filters-web';
28
25
  import clsx from 'clsx';
26
+ import type {
27
+ BackgroundFiltersPerformance,
28
+ BackgroundFiltersProps,
29
+ BackgroundFiltersContextValue,
30
+ PerformanceDegradationReason,
31
+ } from './types';
29
32
 
30
33
  /**
31
34
  * Constants for FPS warning calculation.
@@ -39,32 +42,6 @@ const DEFAULT_FPS = 30;
39
42
  const DEVIATION_LIMIT = 0.5;
40
43
  const OUTLIER_PERSISTENCE = 5;
41
44
 
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
-
68
45
  /**
69
46
  * Represents the available background filter processing engines.
70
47
  */
@@ -74,160 +51,6 @@ enum FilterEngine {
74
51
  NONE,
75
52
  }
76
53
 
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
-
231
54
  /**
232
55
  * Determines which filter engine is available.
233
56
  * MEDIA_PIPE is the default unless legacy filters are requested or MediaPipe is unsupported.
@@ -239,12 +62,11 @@ const determineEngine = async (
239
62
  forceSafariSupport: boolean | undefined,
240
63
  forceMobileSupport: boolean | undefined,
241
64
  ): Promise<FilterEngine> => {
242
- const isTfPlatformSupported = await isPlatformSupported({
243
- forceSafariSupport,
244
- forceMobileSupport,
245
- });
246
-
247
65
  if (useLegacyFilter) {
66
+ const isTfPlatformSupported = await isPlatformSupported({
67
+ forceSafariSupport,
68
+ forceMobileSupport,
69
+ });
248
70
  return isTfPlatformSupported ? FilterEngine.TF : FilterEngine.NONE;
249
71
  }
250
72
 
@@ -263,9 +85,15 @@ const determineEngine = async (
263
85
  * in your project before using this component.
264
86
  */
265
87
  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
+ },
267
94
  ) => {
268
95
  const {
96
+ ContextProvider,
269
97
  children,
270
98
  backgroundImages = [],
271
99
  backgroundFilter: bgFilterFromProps = undefined,
@@ -340,7 +168,7 @@ export const BackgroundFiltersProvider = (
340
168
  const reasons: Array<PerformanceDegradationReason> = [];
341
169
 
342
170
  if (showLowFpsWarning) {
343
- reasons.push(PerformanceDegradationReason.FRAME_DROP);
171
+ reasons.push('frame-drop');
344
172
  }
345
173
 
346
174
  const qualityLimitationReasons =
@@ -351,7 +179,7 @@ export const BackgroundFiltersProvider = (
351
179
  qualityLimitationReasons &&
352
180
  qualityLimitationReasons?.includes('cpu')
353
181
  ) {
354
- reasons.push(PerformanceDegradationReason.CPU_THROTTLING);
182
+ reasons.push('cpu-throttling');
355
183
  }
356
184
 
357
185
  return {
@@ -458,52 +286,54 @@ export const BackgroundFiltersProvider = (
458
286
  );
459
287
 
460
288
  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
+ };
461
305
  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}>
480
307
  {children}
481
308
  {isReady && (
482
309
  <BackgroundFilters
310
+ api={contextValue}
483
311
  tfLite={tfLite}
484
312
  engine={engine}
485
313
  onStats={handleStats}
486
314
  />
487
315
  )}
488
- </BackgroundFiltersContext.Provider>
316
+ </ContextProvider.Provider>
489
317
  );
490
318
  };
491
319
 
492
320
  const BackgroundFilters = (props: {
321
+ api: BackgroundFiltersContextValue;
493
322
  tfLite?: TFLite;
494
323
  engine: FilterEngine;
495
324
  onStats: (stats: PerformanceStats) => void;
496
325
  }) => {
497
326
  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;
500
330
  const handleErrorRef = useRef<((error: any) => void) | undefined>(undefined);
501
331
  handleErrorRef.current = onError;
502
332
 
503
333
  const handleStatsRef = useRef<
504
334
  ((stats: PerformanceStats) => void) | undefined
505
335
  >(undefined);
506
- handleStatsRef.current = props.onStats;
336
+ handleStatsRef.current = onStats;
507
337
 
508
338
  useEffect(() => {
509
339
  if (!call || !backgroundFilter) return;
@@ -524,6 +354,7 @@ const BackgroundFilters = (props: {
524
354
  };
525
355
 
526
356
  const useRenderer = (
357
+ api: BackgroundFiltersContextValue,
527
358
  tfLite: TFLite | undefined,
528
359
  call: Call | undefined,
529
360
  engine: FilterEngine,
@@ -534,7 +365,7 @@ const useRenderer = (
534
365
  backgroundImage,
535
366
  modelFilePath,
536
367
  basePath,
537
- } = useBackgroundFilters();
368
+ } = api;
538
369
 
539
370
  const videoRef = useRef<HTMLVideoElement>(null);
540
371
  const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -0,0 +1,60 @@
1
+ import {
2
+ createContext,
3
+ lazy,
4
+ PropsWithChildren,
5
+ ReactNode,
6
+ Suspense,
7
+ useContext,
8
+ } from 'react';
9
+ import type {
10
+ BackgroundFiltersProps,
11
+ BackgroundFiltersContextValue,
12
+ } from './types';
13
+
14
+ const BackgroundFiltersProviderImpl = lazy(() =>
15
+ import('./BackgroundFilters').then((m) => ({
16
+ default: m.BackgroundFiltersProvider,
17
+ })),
18
+ );
19
+
20
+ /**
21
+ * The context for the background filters.
22
+ */
23
+ const BackgroundFiltersContext = createContext<
24
+ BackgroundFiltersContextValue | undefined
25
+ >(undefined);
26
+
27
+ /**
28
+ * A hook to access the background filters context API.
29
+ */
30
+ export const useBackgroundFilters = () => {
31
+ const context = useContext(BackgroundFiltersContext);
32
+ if (!context) {
33
+ throw new Error(
34
+ 'useBackgroundFilters must be used within a BackgroundFiltersProvider',
35
+ );
36
+ }
37
+ return context;
38
+ };
39
+
40
+ /**
41
+ * A provider component that enables the use of background filters in your app.
42
+ *
43
+ * Please make sure you have the `@stream-io/video-filters-web` package installed
44
+ * in your project before using this component.
45
+ */
46
+ export const BackgroundFiltersProvider = (
47
+ props: PropsWithChildren<BackgroundFiltersProps> & {
48
+ SuspenseFallback?: ReactNode;
49
+ },
50
+ ) => {
51
+ const { SuspenseFallback = null, ...filterProps } = props;
52
+ return (
53
+ <Suspense fallback={SuspenseFallback}>
54
+ <BackgroundFiltersProviderImpl
55
+ {...filterProps}
56
+ ContextProvider={BackgroundFiltersContext}
57
+ />
58
+ </Suspense>
59
+ );
60
+ };
@@ -1 +1,4 @@
1
- export * from './BackgroundFilters';
1
+ // don't export BackgroundFilters.tsx as it is lazily loaded through
2
+ // the BackgroundFiltersProvider
3
+ export * from './BackgroundFiltersProvider';
4
+ export * from './types';
@@ -0,0 +1,162 @@
1
+ import type {
2
+ BackgroundBlurLevel,
3
+ BackgroundFilter,
4
+ PlatformSupportFlags,
5
+ } from '@stream-io/video-filters-web';
6
+
7
+ /**
8
+ * Configuration for performance metric thresholds.
9
+ */
10
+ export type BackgroundFiltersPerformanceThresholds = {
11
+ /**
12
+ * The lower FPS threshold for triggering a performance warning.
13
+ * When the EMA FPS falls below this value, a warning is shown.
14
+ * @default 23
15
+ */
16
+ fpsWarningThresholdLower?: number;
17
+
18
+ /**
19
+ * The upper FPS threshold for clearing a performance warning.
20
+ * When the EMA FPS rises above this value, the warning is cleared.
21
+ * @default 25
22
+ */
23
+ fpsWarningThresholdUpper?: number;
24
+
25
+ /**
26
+ * The default FPS value used as the initial value for the EMA (Exponential Moving Average)
27
+ * calculation and when stats are unavailable or when resetting the filter.
28
+ * @default 30
29
+ */
30
+ defaultFps?: number;
31
+ };
32
+
33
+ export type BackgroundFiltersProps = PlatformSupportFlags & {
34
+ /**
35
+ * A list of URLs to use as background images.
36
+ */
37
+ backgroundImages?: string[];
38
+
39
+ /**
40
+ * The background filter to apply to the video (by default).
41
+ * @default undefined no filter applied
42
+ */
43
+ backgroundFilter?: BackgroundFilter;
44
+
45
+ /**
46
+ * The URL of the image to use as the background (by default).
47
+ */
48
+ backgroundImage?: string;
49
+
50
+ /**
51
+ * The level of blur to apply to the background (by default).
52
+ * @default 'high'.
53
+ */
54
+ backgroundBlurLevel?: BackgroundBlurLevel;
55
+
56
+ /**
57
+ * The base path for the TensorFlow Lite files.
58
+ * @default 'https://unpkg.com/@stream-io/video-filters-web/mediapipe'.
59
+ */
60
+ basePath?: string;
61
+
62
+ /**
63
+ * The path to the TensorFlow Lite WebAssembly file.
64
+ *
65
+ * Override this prop to use a custom path to the TensorFlow Lite WebAssembly file
66
+ * (e.g., if you choose to host it yourself).
67
+ */
68
+ tfFilePath?: string;
69
+
70
+ /**
71
+ * The path to the MediaPipe model file.
72
+ * Override this prop to use a custom path to the MediaPipe model file
73
+ * (e.g., if you choose to host it yourself).
74
+ */
75
+ modelFilePath?: string;
76
+
77
+ /**
78
+ * When true, the filter uses the legacy TensorFlow-based segmentation model.
79
+ * When false, it uses the default MediaPipe Tasks Vision model.
80
+ *
81
+ * Only enable this if you need to mimic the behavior of older SDK versions.
82
+ */
83
+ useLegacyFilter?: boolean;
84
+
85
+ /**
86
+ * When a started filter encounters an error, this callback will be executed.
87
+ * The default behavior (not overridable) is unregistering a failed filter.
88
+ * Use this callback to display UI error message, disable the corresponding stream,
89
+ * or to try registering the filter again.
90
+ */
91
+ onError?: (error: any) => void;
92
+
93
+ /**
94
+ * Configuration for performance metric thresholds.
95
+ * Use this to customize when performance warnings are triggered.
96
+ */
97
+ performanceThresholds?: BackgroundFiltersPerformanceThresholds;
98
+ };
99
+
100
+ /**
101
+ * Represents the possible reasons for background filter performance degradation.
102
+ */
103
+ export type PerformanceDegradationReason = 'frame-drop' | 'cpu-throttling';
104
+
105
+ /**
106
+ * Performance degradation information for background filters.
107
+ *
108
+ * Performance is calculated using an Exponential Moving Average (EMA) of FPS values
109
+ * to smooth out quick spikes and provide stable performance warnings.
110
+ */
111
+ export type BackgroundFiltersPerformance = {
112
+ /**
113
+ * Whether performance is currently degraded.
114
+ */
115
+ degraded: boolean;
116
+ /**
117
+ * Reasons for performance degradation.
118
+ */
119
+ reason?: Array<PerformanceDegradationReason>;
120
+ };
121
+
122
+ export type BackgroundFiltersAPI = {
123
+ /**
124
+ * Whether the current platform supports the background filters.
125
+ */
126
+ isSupported: boolean;
127
+
128
+ /**
129
+ * Indicates whether the background filters engine is loaded and ready.
130
+ */
131
+ isReady: boolean;
132
+
133
+ /**
134
+ * Performance information for background filters.
135
+ */
136
+ performance: BackgroundFiltersPerformance;
137
+
138
+ /**
139
+ * Disables all background filters applied to the video.
140
+ */
141
+ disableBackgroundFilter: () => void;
142
+
143
+ /**
144
+ * Applies a background blur filter to the video.
145
+ *
146
+ * @param blurLevel the level of blur to apply to the background.
147
+ */
148
+ applyBackgroundBlurFilter: (blurLevel: BackgroundBlurLevel) => void;
149
+
150
+ /**
151
+ * Applies a background image filter to the video.
152
+ *
153
+ * @param imageUrl the URL of the image to use as the background.
154
+ */
155
+ applyBackgroundImageFilter: (imageUrl: string) => void;
156
+ };
157
+
158
+ /**
159
+ * The context value for the background filters context.
160
+ */
161
+ export type BackgroundFiltersContextValue = BackgroundFiltersProps &
162
+ BackgroundFiltersAPI;
@@ -10,6 +10,7 @@ import {
10
10
  } from '../ParticipantView';
11
11
  import { IconButton } from '../../../components';
12
12
  import {
13
+ useDragToScroll,
13
14
  useHorizontalScrollPosition,
14
15
  useVerticalScrollPosition,
15
16
  } from '../../../hooks';
@@ -88,6 +89,12 @@ export type SpeakerLayoutProps = {
88
89
  * Whether the layout is muted. Defaults to `false`.
89
90
  */
90
91
  muted?: boolean;
92
+
93
+ /**
94
+ * Whether to enable drag-to-scroll functionality on the participants bar.
95
+ * @default false
96
+ */
97
+ enableDragToScroll?: boolean;
91
98
  } & Pick<
92
99
  ParticipantViewProps,
93
100
  'VideoPlaceholder' | 'PictureInPicturePlaceholder'
@@ -109,6 +116,7 @@ export const SpeakerLayout = ({
109
116
  filterParticipants,
110
117
  pageArrowsVisible = true,
111
118
  muted,
119
+ enableDragToScroll = false,
112
120
  }: SpeakerLayoutProps) => {
113
121
  const call = useCall();
114
122
  const { useParticipants } = useCallStateHooks();
@@ -146,6 +154,9 @@ export const SpeakerLayout = ({
146
154
 
147
155
  const isOneOnOneCall = allParticipants.length === 2;
148
156
  useSpeakerLayoutSortPreset(call, isOneOnOneCall);
157
+ useDragToScroll(participantsBarWrapperElement, {
158
+ enabled: enableDragToScroll,
159
+ });
149
160
 
150
161
  let participantsWithAppliedLimit = otherParticipants;
151
162
 
@@ -4,3 +4,4 @@ export * from './useScrollPosition';
4
4
  export * from './useRequestPermission';
5
5
  export * from './useDeviceList';
6
6
  export * from './useModeration';
7
+ export * from './useDragToScroll';