@stream-io/video-react-sdk 1.36.1 → 1.37.1

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,7 +1,6 @@
1
- import { DynascaleManager, VideoTrackType } from '@stream-io/video-client';
2
- export declare const useTrackElementVisibility: <T extends HTMLElement>({ trackedElement, dynascaleManager: propsDynascaleManager, sessionId, trackType, }: {
1
+ import { VideoTrackType } from '@stream-io/video-client';
2
+ export declare const useTrackElementVisibility: <T extends HTMLElement>({ trackedElement, sessionId, trackType, }: {
3
3
  trackedElement: T | null;
4
4
  sessionId: string;
5
- dynascaleManager?: DynascaleManager;
6
5
  trackType: VideoTrackType | "none";
7
6
  }) => void;
@@ -56,8 +56,10 @@ export declare const translations: {
56
56
  Dismiss: string;
57
57
  "Microphone on": string;
58
58
  "Microphone off": string;
59
+ "Microphone is paused by your system": string;
59
60
  "Camera on": string;
60
61
  "Camera off": string;
62
+ "Camera is paused by your system": string;
61
63
  "No camera found": string;
62
64
  "Video is disabled": string;
63
65
  Pinned: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-sdk",
3
- "version": "1.36.1",
3
+ "version": "1.37.1",
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.49.0",
48
+ "@stream-io/video-client": "1.50.0",
49
49
  "@stream-io/video-filters-web": "0.7.4",
50
- "@stream-io/video-react-bindings": "1.15.1",
50
+ "@stream-io/video-react-bindings": "1.16.0",
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.3",
64
- "@stream-io/video-styling": "^1.13.0",
63
+ "@stream-io/audio-filters-web": "^0.8.0",
64
+ "@stream-io/video-styling": "^1.14.0",
65
65
  "@types/react": "~19.1.17",
66
66
  "@types/react-dom": "~19.1.11",
67
67
  "react": "19.1.0",
@@ -1,4 +1,4 @@
1
- import { OwnCapability } from '@stream-io/video-client';
1
+ import { OwnCapability, SfuModels } from '@stream-io/video-client';
2
2
  import {
3
3
  Restricted,
4
4
  useCallStateHooks,
@@ -38,7 +38,7 @@ export const ToggleAudioPreviewButton = (
38
38
  ...restCompositeButtonProps
39
39
  } = props;
40
40
  const { t } = useI18n();
41
- const { useMicrophoneState } = useCallStateHooks();
41
+ const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
42
42
  const {
43
43
  microphone,
44
44
  hasBrowserPermission,
@@ -46,6 +46,10 @@ export const ToggleAudioPreviewButton = (
46
46
  optionsAwareIsMute,
47
47
  isTogglePending,
48
48
  } = useMicrophoneState({ optimisticUpdates });
49
+ const localParticipant = useLocalParticipant();
50
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
51
+ SfuModels.TrackType.AUDIO,
52
+ );
49
53
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
50
54
  const handleClick = createCallControlHandler(props, () =>
51
55
  microphone.toggle(),
@@ -56,7 +60,9 @@ export const ToggleAudioPreviewButton = (
56
60
  title={
57
61
  !hasBrowserPermission
58
62
  ? t('Check your browser audio permissions')
59
- : (caption ?? t('Mic'))
63
+ : isSystemMuted
64
+ ? t('Microphone is paused by your system')
65
+ : (caption ?? t('Mic'))
60
66
  }
61
67
  tooltipDisabled={tooltipDisabled}
62
68
  >
@@ -99,6 +105,13 @@ export const ToggleAudioPreviewButton = (
99
105
  children="?"
100
106
  />
101
107
  )}
108
+ {isSystemMuted && hasBrowserPermission && (
109
+ <span
110
+ className="str-video__system-muted"
111
+ title={t('Microphone is paused by your system')}
112
+ children="!"
113
+ />
114
+ )}
102
115
  </CompositeButton>
103
116
  </WithTooltip>
104
117
  );
@@ -128,7 +141,7 @@ export const ToggleAudioPublishingButton = (
128
141
  const { hasPermission, requestPermission, isAwaitingPermission } =
129
142
  useRequestPermission(OwnCapability.SEND_AUDIO);
130
143
 
131
- const { useMicrophoneState } = useCallStateHooks();
144
+ const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
132
145
  const {
133
146
  microphone,
134
147
  hasBrowserPermission,
@@ -136,6 +149,10 @@ export const ToggleAudioPublishingButton = (
136
149
  isTogglePending,
137
150
  optionsAwareIsMute,
138
151
  } = useMicrophoneState({ optimisticUpdates });
152
+ const localParticipant = useLocalParticipant();
153
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
154
+ SfuModels.TrackType.AUDIO,
155
+ );
139
156
 
140
157
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
141
158
  const handleClick = createCallControlHandler(props, async () => {
@@ -161,7 +178,9 @@ export const ToggleAudioPublishingButton = (
161
178
  ? t('You have no permission to share your audio')
162
179
  : !hasBrowserPermission
163
180
  ? t('Check your browser mic permissions')
164
- : (caption ?? t('Mic'))
181
+ : isSystemMuted
182
+ ? t('Microphone is paused by your system')
183
+ : (caption ?? t('Mic'))
165
184
  }
166
185
  tooltipDisabled={tooltipDisabled}
167
186
  >
@@ -200,6 +219,14 @@ export const ToggleAudioPublishingButton = (
200
219
  ?
201
220
  </span>
202
221
  )}
222
+ {isSystemMuted && hasBrowserPermission && hasPermission && (
223
+ <span
224
+ className="str-video__system-muted"
225
+ title={t('Microphone is paused by your system')}
226
+ >
227
+ !
228
+ </span>
229
+ )}
203
230
  </CompositeButton>
204
231
  </WithTooltip>
205
232
  </PermissionNotification>
@@ -5,7 +5,7 @@ import {
5
5
  UseInputMediaDeviceOptions,
6
6
  } from '@stream-io/video-react-bindings';
7
7
  import clsx from 'clsx';
8
- import { OwnCapability } from '@stream-io/video-client';
8
+ import { OwnCapability, SfuModels } from '@stream-io/video-client';
9
9
  import { CompositeButton, IconButtonWithMenuProps } from '../Button/';
10
10
  import { DeviceSelectorVideo } from '../DeviceSettings';
11
11
  import { PermissionNotification } from '../Notification';
@@ -38,7 +38,7 @@ export const ToggleVideoPreviewButton = (
38
38
  ...restCompositeButtonProps
39
39
  } = props;
40
40
  const { t } = useI18n();
41
- const { useCameraState } = useCallStateHooks();
41
+ const { useCameraState, useLocalParticipant } = useCallStateHooks();
42
42
  const {
43
43
  camera,
44
44
  hasBrowserPermission,
@@ -46,6 +46,10 @@ export const ToggleVideoPreviewButton = (
46
46
  isTogglePending,
47
47
  optionsAwareIsMute,
48
48
  } = useCameraState({ optimisticUpdates });
49
+ const localParticipant = useLocalParticipant();
50
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
51
+ SfuModels.TrackType.VIDEO,
52
+ );
49
53
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
50
54
  const handleClick = createCallControlHandler(props, () => camera.toggle());
51
55
 
@@ -54,7 +58,9 @@ export const ToggleVideoPreviewButton = (
54
58
  title={
55
59
  !hasBrowserPermission
56
60
  ? t('Check your browser video permissions')
57
- : (caption ?? t('Video'))
61
+ : isSystemMuted
62
+ ? t('Camera is paused by your system')
63
+ : (caption ?? t('Video'))
58
64
  }
59
65
  tooltipDisabled={tooltipDisabled}
60
66
  >
@@ -97,6 +103,13 @@ export const ToggleVideoPreviewButton = (
97
103
  children="?"
98
104
  />
99
105
  )}
106
+ {isSystemMuted && hasBrowserPermission && (
107
+ <span
108
+ className="str-video__system-muted"
109
+ title={t('Camera is paused by your system')}
110
+ children="!"
111
+ />
112
+ )}
100
113
  </CompositeButton>
101
114
  </WithTooltip>
102
115
  );
@@ -126,7 +139,8 @@ export const ToggleVideoPublishingButton = (
126
139
  const { hasPermission, requestPermission, isAwaitingPermission } =
127
140
  useRequestPermission(OwnCapability.SEND_VIDEO);
128
141
 
129
- const { useCameraState, useCallSettings } = useCallStateHooks();
142
+ const { useCameraState, useCallSettings, useLocalParticipant } =
143
+ useCallStateHooks();
130
144
  const {
131
145
  camera,
132
146
  optionsAwareIsMute,
@@ -134,6 +148,10 @@ export const ToggleVideoPublishingButton = (
134
148
  isPromptingPermission,
135
149
  isTogglePending,
136
150
  } = useCameraState({ optimisticUpdates });
151
+ const localParticipant = useLocalParticipant();
152
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
153
+ SfuModels.TrackType.VIDEO,
154
+ );
137
155
  const callSettings = useCallSettings();
138
156
  const isPublishingVideoAllowed = callSettings?.video.enabled;
139
157
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
@@ -164,7 +182,9 @@ export const ToggleVideoPublishingButton = (
164
182
  ? t('Check your browser video permissions')
165
183
  : !isPublishingVideoAllowed
166
184
  ? t('Video publishing is disabled by the system')
167
- : caption || t('Video')
185
+ : isSystemMuted
186
+ ? t('Camera is paused by your system')
187
+ : caption || t('Video')
168
188
  }
169
189
  tooltipDisabled={tooltipDisabled}
170
190
  >
@@ -205,6 +225,17 @@ export const ToggleVideoPublishingButton = (
205
225
  ?
206
226
  </span>
207
227
  )}
228
+ {isSystemMuted &&
229
+ hasBrowserPermission &&
230
+ hasPermission &&
231
+ isPublishingVideoAllowed && (
232
+ <span
233
+ className="str-video__system-muted"
234
+ title={t('Camera is paused by your system')}
235
+ >
236
+ !
237
+ </span>
238
+ )}
208
239
  </CompositeButton>
209
240
  </WithTooltip>
210
241
  </PermissionNotification>
@@ -195,13 +195,29 @@ export const BackstageLayout = (props: BackstageLayoutProps) => {
195
195
  const participantCount = useParticipantCount();
196
196
  const startsAt = useCallStartsAt();
197
197
  const { t } = useI18n();
198
+ const [startsAtPassed, setStartsAtPassed] = useState(
199
+ () => !!startsAt && startsAt.getTime() < Date.now(),
200
+ );
201
+
202
+ useEffect(() => {
203
+ if (!startsAt || startsAtPassed) return;
204
+
205
+ const check = () => {
206
+ if (startsAt.getTime() < Date.now()) {
207
+ setStartsAtPassed(true);
208
+ }
209
+ };
210
+
211
+ const interval = setInterval(check, 1000);
212
+ return () => clearInterval(interval);
213
+ }, [startsAt, startsAtPassed]);
198
214
 
199
215
  return (
200
216
  <div className="str-video__livestream-layout__wrapper">
201
217
  <div className="str-video__livestream-layout__backstage">
202
218
  {startsAt && (
203
219
  <span className="str-video__livestream-layout__starts-at">
204
- {startsAt.getTime() < Date.now()
220
+ {startsAtPassed
205
221
  ? t('Livestream starts soon')
206
222
  : t('Livestream starts at {{ time }}', {
207
223
  time: startsAt.toLocaleTimeString([], {
@@ -152,7 +152,7 @@ export const PaginatedGridLayout = (props: PaginatedGridLayoutProps) => {
152
152
 
153
153
  const cleanup = call.setViewport(paginatedGridLayoutWrapperElement);
154
154
 
155
- return () => cleanup();
155
+ return () => cleanup?.();
156
156
  }, [paginatedGridLayoutWrapperElement, call]);
157
157
 
158
158
  // only used to render video elements
@@ -149,7 +149,7 @@ export const SpeakerLayout = ({
149
149
 
150
150
  const cleanup = call.setViewport(participantsBarWrapperElement);
151
151
 
152
- return () => cleanup();
152
+ return () => cleanup?.();
153
153
  }, [participantsBarWrapperElement, call]);
154
154
 
155
155
  const isOneOnOneCall = allParticipants.length === 2;
@@ -102,7 +102,6 @@ export const ParticipantView = forwardRef<HTMLDivElement, ParticipantViewProps>(
102
102
  const [contextVideoPlaceholderElement, setContextVideoPlaceholderElement] =
103
103
  useState<HTMLDivElement | null>(null);
104
104
 
105
- // TODO: allow to pass custom ViewportTracker instance from props
106
105
  useTrackElementVisibility({
107
106
  sessionId,
108
107
  trackedElement,
@@ -1,29 +1,19 @@
1
1
  import { useEffect } from 'react';
2
- import { DynascaleManager, VideoTrackType } from '@stream-io/video-client';
2
+ import { VideoTrackType } from '@stream-io/video-client';
3
3
  import { useCall } from '@stream-io/video-react-bindings';
4
4
 
5
5
  export const useTrackElementVisibility = <T extends HTMLElement>({
6
6
  trackedElement,
7
- dynascaleManager: propsDynascaleManager,
8
7
  sessionId,
9
8
  trackType,
10
9
  }: {
11
10
  trackedElement: T | null;
12
11
  sessionId: string;
13
- dynascaleManager?: DynascaleManager;
14
12
  trackType: VideoTrackType | 'none';
15
13
  }) => {
16
14
  const call = useCall();
17
- const manager = propsDynascaleManager ?? call?.dynascaleManager;
18
15
  useEffect(() => {
19
- if (!trackedElement || !manager || !call || trackType === 'none') return;
20
- const unobserve = manager.trackElementVisibility(
21
- trackedElement,
22
- sessionId,
23
- trackType,
24
- );
25
- return () => {
26
- unobserve();
27
- };
28
- }, [trackedElement, manager, call, sessionId, trackType]);
16
+ if (!trackedElement || !call || trackType === 'none') return;
17
+ return call.trackElementVisibility(trackedElement, sessionId, trackType);
18
+ }, [trackedElement, call, sessionId, trackType]);
29
19
  };
@@ -60,8 +60,10 @@
60
60
 
61
61
  "Microphone on": "Microphone on",
62
62
  "Microphone off": "Microphone off",
63
+ "Microphone is paused by your system": "Microphone is paused by your system",
63
64
  "Camera on": "Camera on",
64
65
  "Camera off": "Camera off",
66
+ "Camera is paused by your system": "Camera is paused by your system",
65
67
  "No camera found": "No camera found",
66
68
  "Video is disabled": "Video is disabled",
67
69
  "Pinned": "Pinned",