@stream-io/video-react-sdk 1.2.11 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [1.2.13](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.2.12...@stream-io/video-react-sdk-1.2.13) (2024-07-02)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `@stream-io/video-client` updated to version `1.4.4`
10
+ * `@stream-io/video-filters-web` updated to version `0.1.3`
11
+ * `@stream-io/video-react-bindings` updated to version `0.4.48`
12
+ * `@stream-io/audio-filters-web` updated to version `0.2.1`
13
+
14
+ ### Bug Fixes
15
+
16
+ * refactor background filters ([#1415](https://github.com/GetStream/stream-video-js/issues/1415)) ([deb6da2](https://github.com/GetStream/stream-video-js/commit/deb6da238f541c733451e84b198434671da8dceb))
17
+
18
+ ### [1.2.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.2.11...@stream-io/video-react-sdk-1.2.12) (2024-07-02)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * **CallStats:** stat card labels translations ([#1429](https://github.com/GetStream/stream-video-js/issues/1429)) ([51132cd](https://github.com/GetStream/stream-video-js/commit/51132cd5f939ef6cebe1f7ad26a1576a6f92f71f))
24
+
5
25
  ### [1.2.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.2.10...@stream-io/video-react-sdk-1.2.11) (2024-06-28)
6
26
 
7
27
  ### Dependency Updates
package/dist/index.cjs.js CHANGED
@@ -5,6 +5,7 @@ var videoReactBindings = require('@stream-io/video-react-bindings');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var react = require('react');
7
7
  var clsx = require('clsx');
8
+ var reactDom = require('react-dom');
8
9
  var videoFiltersWeb = require('@stream-io/video-filters-web');
9
10
  var react$1 = require('@floating-ui/react');
10
11
  var chart_js = require('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] = react.useState(bgFilterFromProps);
79
80
  const [backgroundImage, setBackgroundImage] = react.useState(bgImageFromProps);
80
81
  const [backgroundBlurLevel, setBackgroundBlurLevel] = react.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 = react.useCallback((error) => {
109
+ videoClient.getLogger(['filters'])('warn', 'Filter encountered an error and will be disabled');
110
+ disableBackgroundFilter();
111
+ onError?.(error);
112
+ }, [disableBackgroundFilter, onError]);
107
113
  return (jsxRuntime.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 && jsxRuntime.jsx(BackgroundFilters, { tfLite: tfLite })] }));
126
+ onError: handleError,
127
+ }, children: [children, tfLite && jsxRuntime.jsx(BackgroundFilters, { tfLite: tfLite })] }));
121
128
  };
122
129
  const BackgroundFilters = (props) => {
123
- const { tfLite } = props;
124
130
  const call = videoReactBindings.useCall();
125
- const { backgroundImage, backgroundFilter } = useBackgroundFilters();
126
- const [videoRef, setVideoRef] = react.useState(null);
127
- const [bgImageRef, setBgImageRef] = react.useState(null);
128
- const [canvasRef, setCanvasRef] = react.useState(null);
129
- const [width, setWidth] = react.useState(1920);
130
- const [height, setHeight] = react.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 = react.useRef();
135
- const [mediaStream, setMediaStream] = react.useState();
136
- const unregister = react.useRef();
131
+ const { children, start } = useRenderer(props.tfLite);
132
+ const { backgroundFilter, onError } = useBackgroundFilters();
133
+ const handleErrorRef = react.useRef(undefined);
134
+ handleErrorRef.current = onError;
137
135
  react.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] = react.useState(false);
155
- react.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 = react.useRef(null);
148
+ const canvasRef = react.useRef(null);
149
+ const bgImageRef = react.useRef(null);
150
+ const [videoSize, setVideoSize] = react.useState({
151
+ width: 1920,
152
+ height: 1080,
153
+ });
154
+ const start = react.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
- react.useEffect(() => {
179
- const resolveFilter = signalFilterReadyRef.current;
180
- if (!canvasRef || !resolveFilter)
181
- return;
182
- const filter = canvasRef.captureStream();
183
- resolveFilter(filter);
184
- return () => {
185
- videoClient.disposeOfMediaStream(filter);
186
- };
187
- }, [canvasRef]);
188
- return (jsxRuntime.jsxs("div", { className: "str-video__background-filters", style: {
189
- width: `${width}px`,
190
- height: `${height}px`,
191
- }, children: [mediaStream && isPlaying && (jsxRuntime.jsx(RenderPipeline, { tfLite: tfLite, videoRef: videoRef, canvasRef: canvasRef, backgroundImageRef: bgImageRef })), jsxRuntime.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 && (jsxRuntime.jsx("img", { className: "str-video__background-filters__background-image", alt: "Background", ref: setBgImageRef, src: backgroundImage, width: width, height: height }, backgroundImage)), isPlaying && (jsxRuntime.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
- react.useEffect(() => {
197
- if (!videoRef || !canvasRef || !backgroundFilter)
198
- return;
199
- if (backgroundFilter === 'image' && !backgroundImageRef)
200
- return;
201
- const renderer = videoFiltersWeb.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
+ reactDom.flushSync(() => setVideoSize({
179
+ width: trackSettings.width ?? 0,
180
+ height: trackSettings.height ?? 0,
181
+ }));
182
+ renderer = videoFiltersWeb.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 && videoClient.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 = (jsxRuntime.jsxs("div", { className: "str-video__background-filters", children: [jsxRuntime.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 && (jsxRuntime.jsx("img", { className: "str-video__background-filters__background-image", alt: "Background", ref: bgImageRef, src: backgroundImage, ...videoSize })), jsxRuntime.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, }) => {
@@ -1346,13 +1336,13 @@ const CallStats = (props) => {
1346
1336
  highBound: latencyHighBound,
1347
1337
  value: callStatsReport?.publisherStats.averageRoundTripTimeInMs || 0,
1348
1338
  };
1349
- return (jsxRuntime.jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.') })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__latencychart", children: jsxRuntime.jsx(CallStatsLatencyChart, { values: latencyBuffer }) }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Call performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: "Region", value: callStatsReport.datacenter }), jsxRuntime.jsx(StatCard, { label: "Latency", value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: "Receive jitter", value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
1339
+ return (jsxRuntime.jsx("div", { className: "str-video__call-stats", children: callStatsReport && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "call-latency" }), t('Call Latency')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Very high latency values may reduce call quality, cause lag, and make the call less enjoyable.') })] }), jsxRuntime.jsx("div", { className: "str-video__call-stats__latencychart", children: jsxRuntime.jsx(CallStatsLatencyChart, { values: latencyBuffer }) }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__header", children: [jsxRuntime.jsxs("h3", { className: "str-video__call-stats__heading", children: [jsxRuntime.jsx(Icon, { className: "str-video__call-stats__icon", icon: "network-quality" }), t('Call performance')] }), jsxRuntime.jsx("p", { className: "str-video__call-stats__description", children: t('Review the key data points below to assess call performance') })] }), jsxRuntime.jsxs("div", { className: "str-video__call-stats__card-container", children: [jsxRuntime.jsx(StatCard, { label: t('Region'), value: callStatsReport.datacenter }), jsxRuntime.jsx(StatCard, { label: t('Latency'), value: `${callStatsReport.publisherStats.averageRoundTripTimeInMs} ms.`, comparison: latencyComparison }), jsxRuntime.jsx(StatCard, { label: t('Receive jitter'), value: `${callStatsReport.subscriberStats.averageJitterInMs} ms.`, comparison: {
1350
1340
  ...latencyComparison,
1351
1341
  value: callStatsReport.subscriberStats.averageJitterInMs,
1352
- } }), jsxRuntime.jsx(StatCard, { label: "Publish jitter", value: `${callStatsReport.publisherStats.averageJitterInMs} ms.`, comparison: {
1342
+ } }), jsxRuntime.jsx(StatCard, { label: t('Publish jitter'), value: `${callStatsReport.publisherStats.averageJitterInMs} ms.`, comparison: {
1353
1343
  ...latencyComparison,
1354
1344
  value: callStatsReport.publisherStats.averageJitterInMs,
1355
- } }), jsxRuntime.jsx(StatCard, { label: "Publish resolution", value: toFrameSize(callStatsReport.publisherStats) }), jsxRuntime.jsx(StatCard, { label: "Publish quality drop reason", value: callStatsReport.publisherStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: "Receiving resolution", value: toFrameSize(callStatsReport.subscriberStats) }), jsxRuntime.jsx(StatCard, { label: "Receive quality drop reason", value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: "Publish bitrate", value: publishBitrate }), jsxRuntime.jsx(StatCard, { label: "Receiving bitrate", value: subscribeBitrate })] })] })) }));
1345
+ } }), jsxRuntime.jsx(StatCard, { label: t('Publish resolution'), value: toFrameSize(callStatsReport.publisherStats) }), jsxRuntime.jsx(StatCard, { label: t('Publish quality drop reason'), value: callStatsReport.publisherStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Receiving resolution'), value: toFrameSize(callStatsReport.subscriberStats) }), jsxRuntime.jsx(StatCard, { label: t('Receive quality drop reason'), value: callStatsReport.subscriberStats.qualityLimitationReasons }), jsxRuntime.jsx(StatCard, { label: t('Publish bitrate'), value: publishBitrate }), jsxRuntime.jsx(StatCard, { label: t('Receiving bitrate'), value: subscribeBitrate })] })] })) }));
1356
1346
  };
1357
1347
  const StatCardExplanation = (props) => {
1358
1348
  const { description } = props;
@@ -2622,7 +2612,7 @@ const LivestreamPlayer = (props) => {
2622
2612
  return (jsxRuntime.jsx(StreamCall, { call: call, children: jsxRuntime.jsx(LivestreamLayout, { ...layoutProps }) }));
2623
2613
  };
2624
2614
 
2625
- const [major, minor, patch] = ("1.2.11" ).split('.');
2615
+ const [major, minor, patch] = ("1.2.13" ).split('.');
2626
2616
  videoClient.setSdkInfo({
2627
2617
  type: videoClient.SfuModels.SdkType.REACT,
2628
2618
  major,