@stream-io/video-react-sdk 1.33.3 → 1.34.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.
@@ -15,6 +15,10 @@ export type NoiseCancellationAPI = {
15
15
  * are supported on this platform and for the current user.
16
16
  */
17
17
  isSupported: boolean | undefined;
18
+ /**
19
+ * Provides information whether Noise Cancellation is initialized and ready.
20
+ */
21
+ isReady: boolean;
18
22
  /**
19
23
  * Provides information whether Noise Cancellation is active or not.
20
24
  */
@@ -86,6 +86,7 @@ export declare const translations: {
86
86
  "Dominant Speaker": string;
87
87
  "Poor connection quality": string;
88
88
  "Video paused due to insufficient bandwidth": string;
89
+ "Audio is connecting...": string;
89
90
  Participants: string;
90
91
  Anonymous: string;
91
92
  "No participants found": string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-sdk",
3
- "version": "1.33.3",
3
+ "version": "1.34.0",
4
4
  "main": "./dist/index.cjs.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "types": "./dist/index.d.ts",
@@ -45,9 +45,9 @@
45
45
  ],
46
46
  "dependencies": {
47
47
  "@floating-ui/react": "^0.27.6",
48
- "@stream-io/video-client": "1.44.3",
48
+ "@stream-io/video-client": "1.44.4",
49
49
  "@stream-io/video-filters-web": "0.7.2",
50
- "@stream-io/video-react-bindings": "1.13.12",
50
+ "@stream-io/video-react-bindings": "1.13.13",
51
51
  "chart.js": "^4.4.4",
52
52
  "clsx": "^2.0.0",
53
53
  "react-chartjs-2": "^5.3.0"
@@ -60,8 +60,8 @@
60
60
  "@rollup/plugin-json": "^6.1.0",
61
61
  "@rollup/plugin-replace": "^6.0.2",
62
62
  "@rollup/plugin-typescript": "^12.1.4",
63
- "@stream-io/audio-filters-web": "^0.7.2",
64
- "@stream-io/video-styling": "^1.11.0",
63
+ "@stream-io/audio-filters-web": "^0.7.3",
64
+ "@stream-io/video-styling": "^1.12.0",
65
65
  "@types/react": "~19.1.17",
66
66
  "@types/react-dom": "~19.1.11",
67
67
  "react": "19.1.0",
@@ -1,9 +1,6 @@
1
1
  import { OwnCapability } from '@stream-io/video-client';
2
2
  import { Restricted } from '@stream-io/video-react-bindings';
3
- import {
4
- SpeakingWhileMutedNotification,
5
- MicCaptureErrorNotification,
6
- } from '../Notification';
3
+ import { SpeakingWhileMutedNotification } from '../Notification';
7
4
  import { RecordCallButton } from './RecordCallButton';
8
5
  import { ReactionsButton } from './ReactionsButton';
9
6
  import { ScreenShareButton } from './ScreenShareButton';
@@ -18,11 +15,9 @@ export type CallControlsProps = {
18
15
  export const CallControls = ({ onLeave }: CallControlsProps) => (
19
16
  <div className="str-video__call-controls">
20
17
  <Restricted requiredGrants={[OwnCapability.SEND_AUDIO]}>
21
- <MicCaptureErrorNotification>
22
- <SpeakingWhileMutedNotification>
23
- <ToggleAudioPublishingButton />
24
- </SpeakingWhileMutedNotification>
25
- </MicCaptureErrorNotification>
18
+ <SpeakingWhileMutedNotification>
19
+ <ToggleAudioPublishingButton />
20
+ </SpeakingWhileMutedNotification>
26
21
  </Restricted>
27
22
  <Restricted requiredGrants={[OwnCapability.SEND_VIDEO]}>
28
23
  <ToggleVideoPublishingButton />
@@ -30,6 +30,10 @@ export type NoiseCancellationAPI = {
30
30
  * are supported on this platform and for the current user.
31
31
  */
32
32
  isSupported: boolean | undefined;
33
+ /**
34
+ * Provides information whether Noise Cancellation is initialized and ready.
35
+ */
36
+ isReady: boolean;
33
37
  /**
34
38
  * Provides information whether Noise Cancellation is active or not.
35
39
  */
@@ -105,6 +109,8 @@ export const NoiseCancellationProvider = (
105
109
  isSupportedByBrowser && hasCapability && noiseCancellationAllowed;
106
110
 
107
111
  const [isEnabled, setIsEnabled] = useState(false);
112
+ const [isReady, setIsReady] = useState(false);
113
+
108
114
  const deinit = useRef<Promise<void>>(undefined);
109
115
  useEffect(() => {
110
116
  if (!call || !isSupported) return;
@@ -112,20 +118,26 @@ export const NoiseCancellationProvider = (
112
118
  const unsubscribe = noiseCancellation.on('change', (v) => setIsEnabled(v));
113
119
  const init = (deinit.current || Promise.resolve())
114
120
  .then(() => noiseCancellation.init({ tracer: call.tracer }))
115
- .then(() => call.microphone.enableNoiseCancellation(noiseCancellation))
121
+ .then(() => {
122
+ setIsReady(true);
123
+ return call.microphone.enableNoiseCancellation(noiseCancellation);
124
+ })
116
125
  .catch((e) => console.error(`Can't initialize noise cancellation`, e));
117
126
 
118
127
  return () => {
119
128
  deinit.current = init
120
129
  .then(() => call.microphone.disableNoiseCancellation())
121
130
  .then(() => noiseCancellation.dispose())
122
- .then(() => unsubscribe());
131
+ .then(() => unsubscribe())
132
+ .catch((e) => console.error("Can't clean up noise cancellation", e))
133
+ .finally(() => setIsReady(false));
123
134
  };
124
135
  }, [call, isSupported, noiseCancellation]);
125
136
 
126
137
  const contextValue = useMemo<NoiseCancellationAPI>(
127
138
  () => ({
128
139
  isSupported,
140
+ isReady,
129
141
  isEnabled,
130
142
  setSuppressionLevel: (level) => {
131
143
  if (!noiseCancellation) return;
@@ -148,7 +160,7 @@ export const NoiseCancellationProvider = (
148
160
  }
149
161
  },
150
162
  }),
151
- [isEnabled, isSupported, noiseCancellation],
163
+ [isEnabled, isReady, isSupported, noiseCancellation],
152
164
  );
153
165
  return (
154
166
  <NoiseCancellationContext.Provider value={contextValue}>
@@ -1,4 +1,4 @@
1
- import { ComponentType, forwardRef } from 'react';
1
+ import { ComponentType, forwardRef, useEffect, useState } from 'react';
2
2
  import { Placement } from '@floating-ui/react';
3
3
  import {
4
4
  hasAudio,
@@ -6,6 +6,7 @@ import {
6
6
  hasScreenShare,
7
7
  hasVideo,
8
8
  SfuModels,
9
+ StreamVideoParticipant,
9
10
  } from '@stream-io/video-client';
10
11
  import { useCall, useI18n } from '@stream-io/video-react-bindings';
11
12
  import clsx from 'clsx';
@@ -13,6 +14,7 @@ import clsx from 'clsx';
13
14
  import {
14
15
  Icon,
15
16
  IconButton,
17
+ LoadingIndicator,
16
18
  MenuToggle,
17
19
  Notification,
18
20
  ToggleMenuButtonProps,
@@ -137,12 +139,20 @@ export const ParticipantDetails = ({
137
139
  const isTrackPaused =
138
140
  trackType !== 'none' ? hasPausedTrack(participant, trackType) : false;
139
141
 
142
+ const isAudioTrackUnmuted = useIsTrackUnmuted(participant);
143
+ const isAudioConnecting = hasAudioTrack && !isAudioTrackUnmuted;
144
+
140
145
  return (
141
146
  <>
142
147
  <div className="str-video__participant-details">
143
- <span className="str-video__participant-details__name">
148
+ <div className="str-video__participant-details__name">
144
149
  {name || userId}
145
-
150
+ {indicatorsVisible && isAudioConnecting && (
151
+ <LoadingIndicator
152
+ className="str-video__participant-details__name--audio-connecting"
153
+ tooltip={t('Audio is connecting...')}
154
+ />
155
+ )}
146
156
  {indicatorsVisible && !hasAudioTrack && (
147
157
  <span className="str-video__participant-details__name--audio-muted" />
148
158
  )}
@@ -164,7 +174,7 @@ export const ParticipantDetails = ({
164
174
  />
165
175
  )}
166
176
  {indicatorsVisible && <SpeechIndicator />}
167
- </span>
177
+ </div>
168
178
  </div>
169
179
  {indicatorsVisible && (
170
180
  <Notification
@@ -206,3 +216,33 @@ export const SpeechIndicator = () => {
206
216
  </span>
207
217
  );
208
218
  };
219
+
220
+ const useIsTrackUnmuted = (participant: StreamVideoParticipant) => {
221
+ const audioStream = participant.audioStream;
222
+
223
+ const [unmuted, setUnmuted] = useState(() => {
224
+ const track = audioStream?.getAudioTracks()[0];
225
+ return !!track && !track.muted;
226
+ });
227
+
228
+ useEffect(() => {
229
+ const track = audioStream?.getAudioTracks()[0];
230
+ if (!track) return;
231
+
232
+ setUnmuted(!track.muted);
233
+
234
+ const handler = () => {
235
+ setUnmuted(!track.muted);
236
+ };
237
+
238
+ track.addEventListener('mute', handler);
239
+ track.addEventListener('unmute', handler);
240
+
241
+ return () => {
242
+ track.removeEventListener('mute', handler);
243
+ track.removeEventListener('unmute', handler);
244
+ };
245
+ }, [audioStream]);
246
+
247
+ return unmuted;
248
+ };
@@ -91,6 +91,7 @@
91
91
  "Dominant Speaker": "Dominant Speaker",
92
92
  "Poor connection quality": "Poor connection quality. Please check your internet connection.",
93
93
  "Video paused due to insufficient bandwidth": "Video paused due to insufficient bandwidth",
94
+ "Audio is connecting...": "Audio is connecting...",
94
95
 
95
96
  "Participants": "Participants",
96
97
  "Anonymous": ", and ({{ count }}) anonymous",