@stream-io/video-react-sdk 1.20.2 → 1.21.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,5 +1,6 @@
1
+ import { UseInputMediaDeviceOptions } from '@stream-io/video-react-bindings';
1
2
  import { PropsWithErrorHandler } from '../../utilities/callControlHandler';
2
3
  export type ScreenShareButtonProps = PropsWithErrorHandler<{
3
4
  caption?: string;
4
- }>;
5
+ } & UseInputMediaDeviceOptions>;
5
6
  export declare const ScreenShareButton: (props: ScreenShareButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
+ import { type UseInputMediaDeviceOptions } from '@stream-io/video-react-bindings';
1
2
  import { IconButtonWithMenuProps } from '../Button';
2
3
  import { PropsWithErrorHandler } from '../../utilities/callControlHandler';
3
- export type ToggleAudioPreviewButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'>>;
4
+ export type ToggleAudioPreviewButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'> & UseInputMediaDeviceOptions>;
4
5
  export declare const ToggleAudioPreviewButton: (props: ToggleAudioPreviewButtonProps) => import("react/jsx-runtime").JSX.Element;
5
- export type ToggleAudioPublishingButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'>>;
6
+ export type ToggleAudioPublishingButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'> & UseInputMediaDeviceOptions>;
6
7
  export declare const ToggleAudioPublishingButton: (props: ToggleAudioPublishingButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,8 @@
1
+ import { UseInputMediaDeviceOptions } from '@stream-io/video-react-bindings';
1
2
  import { IconButtonWithMenuProps } from '../Button/';
2
3
  import { PropsWithErrorHandler } from '../../utilities/callControlHandler';
3
- export type ToggleVideoPreviewButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'>>;
4
+ export type ToggleVideoPreviewButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'> & UseInputMediaDeviceOptions>;
4
5
  export declare const ToggleVideoPreviewButton: (props: ToggleVideoPreviewButtonProps) => import("react/jsx-runtime").JSX.Element;
5
- type ToggleVideoPublishingButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'>>;
6
+ type ToggleVideoPublishingButtonProps = PropsWithErrorHandler<Pick<IconButtonWithMenuProps, 'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'> & UseInputMediaDeviceOptions>;
6
7
  export declare const ToggleVideoPublishingButton: (props: ToggleVideoPublishingButtonProps) => import("react/jsx-runtime").JSX.Element;
7
8
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-sdk",
3
- "version": "1.20.2",
3
+ "version": "1.21.1",
4
4
  "main": "./dist/index.cjs.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "types": "./dist/index.d.ts",
@@ -30,9 +30,9 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@floating-ui/react": "^0.27.6",
33
- "@stream-io/video-client": "1.28.1",
33
+ "@stream-io/video-client": "1.30.0",
34
34
  "@stream-io/video-filters-web": "0.2.1",
35
- "@stream-io/video-react-bindings": "1.7.16",
35
+ "@stream-io/video-react-bindings": "1.8.1",
36
36
  "chart.js": "^4.4.4",
37
37
  "clsx": "^2.0.0",
38
38
  "react-chartjs-2": "^5.3.0"
@@ -3,6 +3,7 @@ import {
3
3
  Restricted,
4
4
  useCallStateHooks,
5
5
  useI18n,
6
+ UseInputMediaDeviceOptions,
6
7
  } from '@stream-io/video-react-bindings';
7
8
  import { CompositeButton } from '../Button/';
8
9
  import { PermissionNotification } from '../Notification';
@@ -14,13 +15,15 @@ import {
14
15
  createCallControlHandler,
15
16
  } from '../../utilities/callControlHandler';
16
17
 
17
- export type ScreenShareButtonProps = PropsWithErrorHandler<{
18
- caption?: string;
19
- }>;
18
+ export type ScreenShareButtonProps = PropsWithErrorHandler<
19
+ {
20
+ caption?: string;
21
+ } & UseInputMediaDeviceOptions
22
+ >;
20
23
 
21
24
  export const ScreenShareButton = (props: ScreenShareButtonProps) => {
22
25
  const { t } = useI18n();
23
- const { caption } = props;
26
+ const { caption, optimisticUpdates } = props;
24
27
 
25
28
  const { useHasOngoingScreenShare, useScreenShareState, useCallSettings } =
26
29
  useCallStateHooks();
@@ -31,11 +34,15 @@ export const ScreenShareButton = (props: ScreenShareButtonProps) => {
31
34
  const callSettings = useCallSettings();
32
35
  const isScreenSharingAllowed = callSettings?.screensharing.enabled;
33
36
 
34
- const { screenShare, optimisticIsMute } = useScreenShareState();
35
- const amIScreenSharing = !optimisticIsMute;
37
+ const { screenShare, optionsAwareIsMute, isTogglePending } =
38
+ useScreenShareState({
39
+ optimisticUpdates,
40
+ });
41
+ const amIScreenSharing = !optionsAwareIsMute;
36
42
  const disableScreenShareButton =
37
- !amIScreenSharing &&
38
- (isSomeoneScreenSharing || isScreenSharingAllowed === false);
43
+ (!amIScreenSharing &&
44
+ (isSomeoneScreenSharing || isScreenSharingAllowed === false)) ||
45
+ (!optimisticUpdates && isTogglePending);
39
46
  const handleClick = createCallControlHandler(props, async () => {
40
47
  if (!hasPermission) {
41
48
  await requestPermission();
@@ -3,6 +3,7 @@ import {
3
3
  Restricted,
4
4
  useCallStateHooks,
5
5
  useI18n,
6
+ type UseInputMediaDeviceOptions,
6
7
  } from '@stream-io/video-react-bindings';
7
8
  import clsx from 'clsx';
8
9
  import { CompositeButton, IconButtonWithMenuProps } from '../Button';
@@ -21,7 +22,8 @@ export type ToggleAudioPreviewButtonProps = PropsWithErrorHandler<
21
22
  Pick<
22
23
  IconButtonWithMenuProps,
23
24
  'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'
24
- >
25
+ > &
26
+ UseInputMediaDeviceOptions
25
27
  >;
26
28
 
27
29
  export const ToggleAudioPreviewButton = (
@@ -32,16 +34,18 @@ export const ToggleAudioPreviewButton = (
32
34
  Menu = DeviceSelectorAudioInput,
33
35
  menuPlacement = 'top',
34
36
  onMenuToggle,
37
+ optimisticUpdates,
35
38
  ...restCompositeButtonProps
36
39
  } = props;
37
40
  const { t } = useI18n();
38
41
  const { useMicrophoneState } = useCallStateHooks();
39
42
  const {
40
43
  microphone,
41
- optimisticIsMute,
42
44
  hasBrowserPermission,
43
45
  isPromptingPermission,
44
- } = useMicrophoneState();
46
+ optionsAwareIsMute,
47
+ isTogglePending,
48
+ } = useMicrophoneState({ optimisticUpdates });
45
49
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
46
50
  const handleClick = createCallControlHandler(props, () =>
47
51
  microphone.toggle(),
@@ -57,15 +61,17 @@ export const ToggleAudioPreviewButton = (
57
61
  tooltipDisabled={tooltipDisabled}
58
62
  >
59
63
  <CompositeButton
60
- active={optimisticIsMute}
64
+ active={optionsAwareIsMute}
61
65
  caption={caption}
62
66
  className={clsx(
63
67
  !hasBrowserPermission && 'str-video__device-unavailable',
64
68
  )}
65
69
  variant="secondary"
66
- disabled={!hasBrowserPermission}
70
+ disabled={
71
+ !hasBrowserPermission || (!optimisticUpdates && isTogglePending)
72
+ }
67
73
  data-testid={
68
- optimisticIsMute
74
+ optionsAwareIsMute
69
75
  ? 'preview-audio-unmute-button'
70
76
  : 'preview-audio-mute-button'
71
77
  }
@@ -78,7 +84,7 @@ export const ToggleAudioPreviewButton = (
78
84
  onMenuToggle?.(shown);
79
85
  }}
80
86
  >
81
- <Icon icon={!optimisticIsMute ? 'mic' : 'mic-off'} />
87
+ <Icon icon={!optionsAwareIsMute ? 'mic' : 'mic-off'} />
82
88
  {!hasBrowserPermission && (
83
89
  <span
84
90
  className="str-video__no-media-permission"
@@ -102,7 +108,8 @@ export type ToggleAudioPublishingButtonProps = PropsWithErrorHandler<
102
108
  Pick<
103
109
  IconButtonWithMenuProps,
104
110
  'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'
105
- >
111
+ > &
112
+ UseInputMediaDeviceOptions
106
113
  >;
107
114
 
108
115
  export const ToggleAudioPublishingButton = (
@@ -114,6 +121,7 @@ export const ToggleAudioPublishingButton = (
114
121
  Menu = <DeviceSelectorAudioInput visualType="list" />,
115
122
  menuPlacement = 'top',
116
123
  onMenuToggle,
124
+ optimisticUpdates,
117
125
  ...restCompositeButtonProps
118
126
  } = props;
119
127
 
@@ -123,10 +131,12 @@ export const ToggleAudioPublishingButton = (
123
131
  const { useMicrophoneState } = useCallStateHooks();
124
132
  const {
125
133
  microphone,
126
- optimisticIsMute,
127
134
  hasBrowserPermission,
128
135
  isPromptingPermission,
129
- } = useMicrophoneState();
136
+ isTogglePending,
137
+ optionsAwareIsMute,
138
+ } = useMicrophoneState({ optimisticUpdates });
139
+
130
140
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
131
141
  const handleClick = createCallControlHandler(props, async () => {
132
142
  if (!hasPermission) {
@@ -156,12 +166,17 @@ export const ToggleAudioPublishingButton = (
156
166
  tooltipDisabled={tooltipDisabled}
157
167
  >
158
168
  <CompositeButton
159
- active={optimisticIsMute}
169
+ active={optionsAwareIsMute}
160
170
  caption={caption}
161
171
  variant="secondary"
162
- disabled={!hasBrowserPermission || !hasPermission}
172
+ disabled={
173
+ !hasBrowserPermission ||
174
+ !hasPermission ||
175
+ // disable button while the toggle action is pending when not using optimistic updates
176
+ (!optimisticUpdates && isTogglePending)
177
+ }
163
178
  data-testid={
164
- optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button'
179
+ optionsAwareIsMute ? 'audio-unmute-button' : 'audio-mute-button'
165
180
  }
166
181
  onClick={handleClick}
167
182
  Menu={Menu}
@@ -173,7 +188,7 @@ export const ToggleAudioPublishingButton = (
173
188
  onMenuToggle?.(shown);
174
189
  }}
175
190
  >
176
- <Icon icon={optimisticIsMute ? 'mic-off' : 'mic'} />
191
+ <Icon icon={optionsAwareIsMute ? 'mic-off' : 'mic'} />
177
192
  {(!hasBrowserPermission || !hasPermission) && (
178
193
  <span className="str-video__no-media-permission">!</span>
179
194
  )}
@@ -2,6 +2,7 @@ import {
2
2
  Restricted,
3
3
  useCallStateHooks,
4
4
  useI18n,
5
+ UseInputMediaDeviceOptions,
5
6
  } from '@stream-io/video-react-bindings';
6
7
  import clsx from 'clsx';
7
8
  import { OwnCapability } from '@stream-io/video-client';
@@ -21,7 +22,8 @@ export type ToggleVideoPreviewButtonProps = PropsWithErrorHandler<
21
22
  Pick<
22
23
  IconButtonWithMenuProps,
23
24
  'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'
24
- >
25
+ > &
26
+ UseInputMediaDeviceOptions
25
27
  >;
26
28
 
27
29
  export const ToggleVideoPreviewButton = (
@@ -32,16 +34,18 @@ export const ToggleVideoPreviewButton = (
32
34
  Menu = DeviceSelectorVideo,
33
35
  menuPlacement = 'top',
34
36
  onMenuToggle,
37
+ optimisticUpdates,
35
38
  ...restCompositeButtonProps
36
39
  } = props;
37
40
  const { t } = useI18n();
38
41
  const { useCameraState } = useCallStateHooks();
39
42
  const {
40
43
  camera,
41
- optimisticIsMute,
42
44
  hasBrowserPermission,
43
45
  isPromptingPermission,
44
- } = useCameraState();
46
+ isTogglePending,
47
+ optionsAwareIsMute,
48
+ } = useCameraState({ optimisticUpdates });
45
49
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
46
50
  const handleClick = createCallControlHandler(props, () => camera.toggle());
47
51
 
@@ -55,19 +59,21 @@ export const ToggleVideoPreviewButton = (
55
59
  tooltipDisabled={tooltipDisabled}
56
60
  >
57
61
  <CompositeButton
58
- active={optimisticIsMute}
62
+ active={optionsAwareIsMute}
59
63
  caption={caption}
60
64
  className={clsx(
61
65
  !hasBrowserPermission && 'str-video__device-unavailable',
62
66
  )}
63
67
  variant="secondary"
64
68
  data-testid={
65
- optimisticIsMute
69
+ optionsAwareIsMute
66
70
  ? 'preview-video-unmute-button'
67
71
  : 'preview-video-mute-button'
68
72
  }
69
73
  onClick={handleClick}
70
- disabled={!hasBrowserPermission}
74
+ disabled={
75
+ !hasBrowserPermission || (!optimisticUpdates && isTogglePending)
76
+ }
71
77
  Menu={Menu}
72
78
  menuPlacement={menuPlacement}
73
79
  {...restCompositeButtonProps}
@@ -76,7 +82,7 @@ export const ToggleVideoPreviewButton = (
76
82
  onMenuToggle?.(shown);
77
83
  }}
78
84
  >
79
- <Icon icon={!optimisticIsMute ? 'camera' : 'camera-off'} />
85
+ <Icon icon={!optionsAwareIsMute ? 'camera' : 'camera-off'} />
80
86
  {!hasBrowserPermission && (
81
87
  <span
82
88
  className="str-video__no-media-permission"
@@ -100,7 +106,8 @@ type ToggleVideoPublishingButtonProps = PropsWithErrorHandler<
100
106
  Pick<
101
107
  IconButtonWithMenuProps,
102
108
  'caption' | 'Menu' | 'menuPlacement' | 'onMenuToggle'
103
- >
109
+ > &
110
+ UseInputMediaDeviceOptions
104
111
  >;
105
112
 
106
113
  export const ToggleVideoPublishingButton = (
@@ -112,6 +119,7 @@ export const ToggleVideoPublishingButton = (
112
119
  Menu = <DeviceSelectorVideo visualType="list" />,
113
120
  menuPlacement = 'top',
114
121
  onMenuToggle,
122
+ optimisticUpdates,
115
123
  ...restCompositeButtonProps
116
124
  } = props;
117
125
 
@@ -121,10 +129,11 @@ export const ToggleVideoPublishingButton = (
121
129
  const { useCameraState, useCallSettings } = useCallStateHooks();
122
130
  const {
123
131
  camera,
124
- optimisticIsMute,
132
+ optionsAwareIsMute,
125
133
  hasBrowserPermission,
126
134
  isPromptingPermission,
127
- } = useCameraState();
135
+ isTogglePending,
136
+ } = useCameraState({ optimisticUpdates });
128
137
  const callSettings = useCallSettings();
129
138
  const isPublishingVideoAllowed = callSettings?.video.enabled;
130
139
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
@@ -160,16 +169,17 @@ export const ToggleVideoPublishingButton = (
160
169
  tooltipDisabled={tooltipDisabled}
161
170
  >
162
171
  <CompositeButton
163
- active={optimisticIsMute}
172
+ active={optionsAwareIsMute}
164
173
  caption={caption}
165
174
  variant="secondary"
166
175
  disabled={
167
176
  !hasBrowserPermission ||
168
177
  !hasPermission ||
169
- !isPublishingVideoAllowed
178
+ !isPublishingVideoAllowed ||
179
+ (!optimisticUpdates && isTogglePending)
170
180
  }
171
181
  data-testid={
172
- optimisticIsMute ? 'video-unmute-button' : 'video-mute-button'
182
+ optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button'
173
183
  }
174
184
  onClick={handleClick}
175
185
  Menu={Menu}
@@ -181,7 +191,7 @@ export const ToggleVideoPublishingButton = (
181
191
  onMenuToggle?.(shown);
182
192
  }}
183
193
  >
184
- <Icon icon={optimisticIsMute ? 'camera-off' : 'camera'} />
194
+ <Icon icon={optionsAwareIsMute ? 'camera-off' : 'camera'} />
185
195
  {(!hasBrowserPermission ||
186
196
  !hasPermission ||
187
197
  !isPublishingVideoAllowed) && (
@@ -62,10 +62,12 @@ export const PermissionNotification = (props: PermissionNotificationProps) => {
62
62
  'granted' | 'revoked'
63
63
  >();
64
64
  useEffect(() => {
65
- if (hasPermission && !prevHasPermission.current) {
65
+ if (prevHasPermission.current === hasPermission) return;
66
+
67
+ if (hasPermission) {
66
68
  setShowNotification('granted');
67
69
  prevHasPermission.current = true;
68
- } else if (!hasPermission && prevHasPermission.current) {
70
+ } else {
69
71
  setShowNotification('revoked');
70
72
  prevHasPermission.current = false;
71
73
  }
@@ -261,15 +261,24 @@ const useUpdateCallDuration = () => {
261
261
 
262
262
  const useToggleFullScreen = () => {
263
263
  const { participantViewElement } = useParticipantViewContext();
264
- const [isFullscreen, setIsFullscreen] = useState(false);
264
+ const [isFullscreen, setIsFullscreen] = useState(
265
+ !!document.fullscreenElement,
266
+ );
267
+ useEffect(() => {
268
+ const handler = () => setIsFullscreen(!!document.fullscreenElement);
269
+ document.addEventListener('fullscreenchange', handler);
270
+ return () => {
271
+ document.removeEventListener('fullscreenchange', handler);
272
+ };
273
+ }, []);
265
274
  return useCallback(() => {
266
275
  if (isFullscreen) {
267
- document.exitFullscreen().then(() => {
268
- setIsFullscreen(false);
276
+ document.exitFullscreen().catch((err) => {
277
+ console.error('Failed to exit fullscreen', err);
269
278
  });
270
279
  } else {
271
- participantViewElement?.requestFullscreen().then(() => {
272
- setIsFullscreen(true);
280
+ participantViewElement?.requestFullscreen().catch((err) => {
281
+ console.error('Failed to enter fullscreen', err);
273
282
  });
274
283
  }
275
284
  }, [isFullscreen, participantViewElement]);