@stream-io/video-react-sdk 1.2.12 → 1.2.13

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/dist/index.es.js CHANGED
@@ -1,10 +1,11 @@
1
- import { hasAudio, hasScreenShareAudio, disposeOfMediaStream, CallingState, OwnCapability, getLogger, hasVideo, isPinned, name, NoiseCancellationSettingsModeEnum, hasScreenShare, VisibilityState, Browsers, SfuModels, paginatedLayoutSortPreset, combineComparators, screenSharing, speakerLayoutSortPreset, CallTypes, defaultSortPreset, setSdkInfo } from '@stream-io/video-client';
1
+ import { hasAudio, hasScreenShareAudio, getLogger, disposeOfMediaStream, CallingState, OwnCapability, hasVideo, isPinned, name, NoiseCancellationSettingsModeEnum, hasScreenShare, VisibilityState, Browsers, SfuModels, paginatedLayoutSortPreset, combineComparators, screenSharing, speakerLayoutSortPreset, CallTypes, defaultSortPreset, setSdkInfo } from '@stream-io/video-client';
2
2
  export * from '@stream-io/video-client';
3
3
  import { useCall, useCallStateHooks, useI18n, Restricted, useConnectedUser, StreamCallProvider, StreamVideoProvider, useStreamVideoClient } from '@stream-io/video-react-bindings';
4
4
  export * from '@stream-io/video-react-bindings';
5
5
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
6
6
  import { useState, useEffect, Fragment as Fragment$1, createContext, useContext, useCallback, useRef, useMemo, forwardRef, isValidElement, useLayoutEffect } from 'react';
7
7
  import clsx from 'clsx';
8
+ import { flushSync } from 'react-dom';
8
9
  import { isPlatformSupported, loadTFLite, createRenderer } from '@stream-io/video-filters-web';
9
10
  import { useFloating, offset, shift, flip, size, autoUpdate, FloatingOverlay, FloatingPortal, arrow, FloatingArrow, useListItem, useListNavigation, useTypeahead, useClick, useDismiss, useRole, useInteractions, FloatingFocusManager, FloatingList, useHover } from '@floating-ui/react';
10
11
  import { Chart, CategoryScale, LinearScale, LineElement, PointElement } from 'chart.js';
@@ -74,7 +75,7 @@ const useBackgroundFilters = () => {
74
75
  * in your project before using this component.
75
76
  */
76
77
  const BackgroundFiltersProvider = (props) => {
77
- const { children, backgroundImages = [], backgroundFilter: bgFilterFromProps = undefined, backgroundImage: bgImageFromProps = undefined, backgroundBlurLevel: bgBlurLevelFromProps = 'high', tfFilePath, modelFilePath, basePath, } = props;
78
+ const { children, backgroundImages = [], backgroundFilter: bgFilterFromProps = undefined, backgroundImage: bgImageFromProps = undefined, backgroundBlurLevel: bgBlurLevelFromProps = 'high', tfFilePath, modelFilePath, basePath, onError, } = props;
78
79
  const [backgroundFilter, setBackgroundFilter] = useState(bgFilterFromProps);
79
80
  const [backgroundImage, setBackgroundImage] = useState(bgImageFromProps);
80
81
  const [backgroundBlurLevel, setBackgroundBlurLevel] = useState(bgBlurLevelFromProps);
@@ -104,6 +105,11 @@ const BackgroundFiltersProvider = (props) => {
104
105
  .then(setTfLite)
105
106
  .catch((err) => console.error('Failed to load TFLite', err));
106
107
  }, [basePath, isSupported, modelFilePath, tfFilePath]);
108
+ const handleError = useCallback((error) => {
109
+ getLogger(['filters'])('warn', 'Filter encountered an error and will be disabled');
110
+ disableBackgroundFilter();
111
+ onError?.(error);
112
+ }, [disableBackgroundFilter, onError]);
107
113
  return (jsxs(BackgroundFiltersContext.Provider, { value: {
108
114
  isSupported,
109
115
  isReady: !!tfLite,
@@ -117,104 +123,88 @@ const BackgroundFiltersProvider = (props) => {
117
123
  tfFilePath,
118
124
  modelFilePath,
119
125
  basePath,
120
- }, children: [children, tfLite && backgroundFilter && jsx(BackgroundFilters, { tfLite: tfLite })] }));
126
+ onError: handleError,
127
+ }, children: [children, tfLite && jsx(BackgroundFilters, { tfLite: tfLite })] }));
121
128
  };
122
129
  const BackgroundFilters = (props) => {
123
- const { tfLite } = props;
124
130
  const call = useCall();
125
- const { backgroundImage, backgroundFilter } = useBackgroundFilters();
126
- const [videoRef, setVideoRef] = useState(null);
127
- const [bgImageRef, setBgImageRef] = useState(null);
128
- const [canvasRef, setCanvasRef] = useState(null);
129
- const [width, setWidth] = useState(1920);
130
- const [height, setHeight] = useState(1080);
131
- // Holds a ref to the `resolve` function of the returned Promise as part
132
- // of the `camera.registerFilter()` API. Once the filter is initialized,
133
- // it should be called with the filtered MediaStream as an argument.
134
- const signalFilterReadyRef = useRef();
135
- const [mediaStream, setMediaStream] = useState();
136
- const unregister = useRef();
131
+ const { children, start } = useRenderer(props.tfLite);
132
+ const { backgroundFilter, onError } = useBackgroundFilters();
133
+ const handleErrorRef = useRef(undefined);
134
+ handleErrorRef.current = onError;
137
135
  useEffect(() => {
138
136
  if (!call || !backgroundFilter)
139
137
  return;
140
- const register = (unregister.current || Promise.resolve()).then(() => call.camera.registerFilter(async (ms) => {
141
- return new Promise((resolve) => {
142
- signalFilterReadyRef.current = resolve;
143
- setMediaStream(ms);
144
- });
145
- }));
138
+ const { unregister } = call.camera.registerFilter((ms) => start(ms, (error) => handleErrorRef.current?.(error)));
146
139
  return () => {
147
- unregister.current = register
148
- .then((unregisterFilter) => unregisterFilter())
149
- .then(() => (signalFilterReadyRef.current = undefined))
150
- .then(() => setMediaStream(undefined))
151
- .catch((err) => console.error('Failed to unregister filter', err));
140
+ unregister();
152
141
  };
153
- }, [backgroundFilter, call]);
154
- const [isPlaying, setIsPlaying] = useState(false);
155
- useEffect(() => {
156
- if (!mediaStream || !videoRef)
157
- return;
158
- const handleOnPlay = () => {
159
- const [track] = mediaStream.getVideoTracks();
160
- if (!track)
142
+ }, [backgroundFilter, call, start]);
143
+ return children;
144
+ };
145
+ const useRenderer = (tfLite) => {
146
+ const { backgroundFilter, backgroundBlurLevel, backgroundImage } = useBackgroundFilters();
147
+ const videoRef = useRef(null);
148
+ const canvasRef = useRef(null);
149
+ const bgImageRef = useRef(null);
150
+ const [videoSize, setVideoSize] = useState({
151
+ width: 1920,
152
+ height: 1080,
153
+ });
154
+ const start = useCallback((ms, onError) => {
155
+ let outputStream;
156
+ let renderer;
157
+ const output = new Promise((resolve, reject) => {
158
+ if (!backgroundFilter) {
159
+ reject(new Error('No filter specified'));
161
160
  return;
162
- const { width: w = 0, height: h = 0 } = track.getSettings();
163
- setWidth(w);
164
- setHeight(h);
165
- setIsPlaying(true);
166
- };
167
- videoRef.addEventListener('play', handleOnPlay);
168
- videoRef.srcObject = mediaStream;
169
- videoRef.play().catch((err) => {
170
- console.error('Failed to play video', err);
171
- });
172
- return () => {
173
- videoRef.removeEventListener('play', handleOnPlay);
174
- videoRef.srcObject = null;
175
- setIsPlaying(false);
176
- };
177
- }, [mediaStream, videoRef]);
178
- useEffect(() => {
179
- const resolveFilter = signalFilterReadyRef.current;
180
- if (!canvasRef || !resolveFilter)
181
- return;
182
- const filter = canvasRef.captureStream();
183
- resolveFilter(filter);
184
- return () => {
185
- disposeOfMediaStream(filter);
186
- };
187
- }, [canvasRef]);
188
- return (jsxs("div", { className: "str-video__background-filters", style: {
189
- width: `${width}px`,
190
- height: `${height}px`,
191
- }, children: [mediaStream && isPlaying && (jsx(RenderPipeline, { tfLite: tfLite, videoRef: videoRef, canvasRef: canvasRef, backgroundImageRef: bgImageRef })), jsx("video", { className: clsx('str-video__background-filters__video', height > width && 'str-video__background-filters__video--tall'), ref: setVideoRef, autoPlay: true, playsInline: true, controls: false, width: width, height: height, muted: true, loop: true }), backgroundImage && (jsx("img", { className: "str-video__background-filters__background-image", alt: "Background", ref: setBgImageRef, src: backgroundImage, width: width, height: height }, backgroundImage)), isPlaying && (jsx("canvas", { className: "str-video__background-filters__target-canvas", width: width, height: height, ref: setCanvasRef }))] }));
192
- };
193
- const RenderPipeline = (props) => {
194
- const { tfLite, videoRef, canvasRef, backgroundImageRef } = props;
195
- const { backgroundFilter, backgroundBlurLevel } = useBackgroundFilters();
196
- useEffect(() => {
197
- if (!videoRef || !canvasRef || !backgroundFilter)
198
- return;
199
- if (backgroundFilter === 'image' && !backgroundImageRef)
200
- return;
201
- const renderer = createRenderer(tfLite, videoRef, canvasRef, {
202
- backgroundFilter,
203
- backgroundImage: backgroundImageRef ?? undefined,
204
- backgroundBlurLevel,
161
+ }
162
+ const videoEl = videoRef.current;
163
+ const canvasEl = canvasRef.current;
164
+ const bgImageEl = bgImageRef.current;
165
+ if (!videoEl || !canvasEl || (backgroundImage && !bgImageEl)) {
166
+ // You should start renderer in effect or event handlers
167
+ reject(new Error('Renderer started before elements are ready'));
168
+ return;
169
+ }
170
+ videoEl.srcObject = ms;
171
+ videoEl.play().then(() => {
172
+ const [track] = ms.getVideoTracks();
173
+ if (!track) {
174
+ reject(new Error('No video tracks in input media stream'));
175
+ return;
176
+ }
177
+ const trackSettings = track.getSettings();
178
+ flushSync(() => setVideoSize({
179
+ width: trackSettings.width ?? 0,
180
+ height: trackSettings.height ?? 0,
181
+ }));
182
+ renderer = createRenderer(tfLite, videoEl, canvasEl, {
183
+ backgroundFilter,
184
+ backgroundBlurLevel,
185
+ backgroundImage: bgImageEl ?? undefined,
186
+ }, onError);
187
+ outputStream = canvasEl.captureStream();
188
+ resolve(outputStream);
189
+ }, () => {
190
+ reject(new Error('Could not play the source video stream'));
191
+ });
205
192
  });
206
- return () => {
207
- renderer.dispose();
193
+ return {
194
+ output,
195
+ stop: () => {
196
+ renderer?.dispose();
197
+ videoRef.current && (videoRef.current.srcObject = null);
198
+ outputStream && disposeOfMediaStream(outputStream);
199
+ },
208
200
  };
209
- }, [
210
- backgroundBlurLevel,
211
- backgroundFilter,
212
- backgroundImageRef,
213
- canvasRef,
214
- tfLite,
215
- videoRef,
216
- ]);
217
- return null;
201
+ }, [backgroundBlurLevel, backgroundFilter, backgroundImage, tfLite]);
202
+ const children = (jsxs("div", { className: "str-video__background-filters", children: [jsx("video", { className: clsx('str-video__background-filters__video', videoSize.height > videoSize.width &&
203
+ 'str-video__background-filters__video--tall'), ref: videoRef, playsInline: true, muted: true, controls: false, ...videoSize }), backgroundImage && (jsx("img", { className: "str-video__background-filters__background-image", alt: "Background", ref: bgImageRef, src: backgroundImage, ...videoSize })), jsx("canvas", { className: "str-video__background-filters__target-canvas", ...videoSize, ref: canvasRef })] }));
204
+ return {
205
+ start,
206
+ children,
207
+ };
218
208
  };
219
209
 
220
210
  const useFloatingUIPreset = ({ middleware = [], placement, strategy, offset: offsetInPx = 10, }) => {
@@ -2622,7 +2612,7 @@ const LivestreamPlayer = (props) => {
2622
2612
  return (jsx(StreamCall, { call: call, children: jsx(LivestreamLayout, { ...layoutProps }) }));
2623
2613
  };
2624
2614
 
2625
- const [major, minor, patch] = ("1.2.12" ).split('.');
2615
+ const [major, minor, patch] = ("1.2.13" ).split('.');
2626
2616
  setSdkInfo({
2627
2617
  type: SfuModels.SdkType.REACT,
2628
2618
  major,