@stream-io/video-react-sdk 1.20.2 → 1.21.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.
- package/CHANGELOG.md +16 -0
- package/dist/index.cjs.js +47 -31
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +47 -31
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/CallControls/ScreenShareButton.d.ts +2 -1
- package/dist/src/components/CallControls/ToggleAudioButton.d.ts +3 -2
- package/dist/src/components/CallControls/ToggleVideoButton.d.ts +3 -2
- package/package.json +3 -3
- package/src/components/CallControls/ScreenShareButton.tsx +15 -8
- package/src/components/CallControls/ToggleAudioButton.tsx +29 -14
- package/src/components/CallControls/ToggleVideoButton.tsx +24 -14
- package/src/components/Notification/PermissionNotification.tsx +4 -2
- package/src/core/components/CallLayout/LivestreamLayout.tsx +14 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.21.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.20.2...@stream-io/video-react-sdk-1.21.0) (2025-09-09)
|
|
6
|
+
|
|
7
|
+
### Dependency Updates
|
|
8
|
+
|
|
9
|
+
- `@stream-io/video-client` updated to version `1.29.0`
|
|
10
|
+
- `@stream-io/video-react-bindings` updated to version `1.8.0`
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- opt-out from optimistic updates ([#1904](https://github.com/GetStream/stream-video-js/issues/1904)) ([45dba34](https://github.com/GetStream/stream-video-js/commit/45dba34d38dc64f456e37b593e38e420426529f5))
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- capabilities and call grants ([#1899](https://github.com/GetStream/stream-video-js/issues/1899)) ([5725dfa](https://github.com/GetStream/stream-video-js/commit/5725dfa29b1e5fdb6fe4e26825ce7b268664d2fa))
|
|
19
|
+
- **LivestreamLayout:** handle enter/exit fullscreen gracefully ([#1916](https://github.com/GetStream/stream-video-js/issues/1916)) ([7dd2a0b](https://github.com/GetStream/stream-video-js/commit/7dd2a0b74d9767aae8463fb665a14b944e6cb204)), closes [#1915](https://github.com/GetStream/stream-video-js/issues/1915)
|
|
20
|
+
|
|
5
21
|
## [1.20.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.20.1...@stream-io/video-react-sdk-1.20.2) (2025-09-02)
|
|
6
22
|
|
|
7
23
|
### Dependency Updates
|
package/dist/index.cjs.js
CHANGED
|
@@ -1072,11 +1072,13 @@ const PermissionNotification = (props) => {
|
|
|
1072
1072
|
const prevHasPermission = react.useRef(hasPermission);
|
|
1073
1073
|
const [showNotification, setShowNotification] = react.useState();
|
|
1074
1074
|
react.useEffect(() => {
|
|
1075
|
-
if (
|
|
1075
|
+
if (prevHasPermission.current === hasPermission)
|
|
1076
|
+
return;
|
|
1077
|
+
if (hasPermission) {
|
|
1076
1078
|
setShowNotification('granted');
|
|
1077
1079
|
prevHasPermission.current = true;
|
|
1078
1080
|
}
|
|
1079
|
-
else
|
|
1081
|
+
else {
|
|
1080
1082
|
setShowNotification('revoked');
|
|
1081
1083
|
prevHasPermission.current = false;
|
|
1082
1084
|
}
|
|
@@ -1301,16 +1303,19 @@ function isNotAllowedError(error) {
|
|
|
1301
1303
|
|
|
1302
1304
|
const ScreenShareButton = (props) => {
|
|
1303
1305
|
const { t } = videoReactBindings.useI18n();
|
|
1304
|
-
const { caption } = props;
|
|
1306
|
+
const { caption, optimisticUpdates } = props;
|
|
1305
1307
|
const { useHasOngoingScreenShare, useScreenShareState, useCallSettings } = videoReactBindings.useCallStateHooks();
|
|
1306
1308
|
const isSomeoneScreenSharing = useHasOngoingScreenShare();
|
|
1307
1309
|
const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.OwnCapability.SCREENSHARE);
|
|
1308
1310
|
const callSettings = useCallSettings();
|
|
1309
1311
|
const isScreenSharingAllowed = callSettings?.screensharing.enabled;
|
|
1310
|
-
const { screenShare,
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1312
|
+
const { screenShare, optionsAwareIsMute, isTogglePending } = useScreenShareState({
|
|
1313
|
+
optimisticUpdates,
|
|
1314
|
+
});
|
|
1315
|
+
const amIScreenSharing = !optionsAwareIsMute;
|
|
1316
|
+
const disableScreenShareButton = (!amIScreenSharing &&
|
|
1317
|
+
(isSomeoneScreenSharing || isScreenSharingAllowed === false)) ||
|
|
1318
|
+
(!optimisticUpdates && isTogglePending);
|
|
1314
1319
|
const handleClick = createCallControlHandler(props, async () => {
|
|
1315
1320
|
if (!hasPermission) {
|
|
1316
1321
|
await requestPermission();
|
|
@@ -1474,27 +1479,27 @@ const ToggleDeviceSettingsMenuButton = react.forwardRef(function ToggleDeviceSet
|
|
|
1474
1479
|
});
|
|
1475
1480
|
|
|
1476
1481
|
const ToggleAudioPreviewButton = (props) => {
|
|
1477
|
-
const { caption, Menu = DeviceSelectorAudioInput, menuPlacement = 'top', onMenuToggle, ...restCompositeButtonProps } = props;
|
|
1482
|
+
const { caption, Menu = DeviceSelectorAudioInput, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
|
|
1478
1483
|
const { t } = videoReactBindings.useI18n();
|
|
1479
1484
|
const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
|
|
1480
|
-
const { microphone,
|
|
1485
|
+
const { microphone, hasBrowserPermission, isPromptingPermission, optionsAwareIsMute, isTogglePending, } = useMicrophoneState({ optimisticUpdates });
|
|
1481
1486
|
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1482
1487
|
const handleClick = createCallControlHandler(props, () => microphone.toggle());
|
|
1483
1488
|
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1484
1489
|
? t('Check your browser audio permissions')
|
|
1485
|
-
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active:
|
|
1490
|
+
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute
|
|
1486
1491
|
? 'preview-audio-unmute-button'
|
|
1487
1492
|
: 'preview-audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1488
1493
|
setTooltipDisabled(shown);
|
|
1489
1494
|
onMenuToggle?.(shown);
|
|
1490
|
-
}, children: [jsxRuntime.jsx(Icon, { icon: !
|
|
1495
|
+
}, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }));
|
|
1491
1496
|
};
|
|
1492
1497
|
const ToggleAudioPublishingButton = (props) => {
|
|
1493
1498
|
const { t } = videoReactBindings.useI18n();
|
|
1494
|
-
const { caption, Menu = jsxRuntime.jsx(DeviceSelectorAudioInput, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, ...restCompositeButtonProps } = props;
|
|
1499
|
+
const { caption, Menu = jsxRuntime.jsx(DeviceSelectorAudioInput, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
|
|
1495
1500
|
const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.OwnCapability.SEND_AUDIO);
|
|
1496
1501
|
const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
|
|
1497
|
-
const { microphone,
|
|
1502
|
+
const { microphone, hasBrowserPermission, isPromptingPermission, isTogglePending, optionsAwareIsMute, } = useMicrophoneState({ optimisticUpdates });
|
|
1498
1503
|
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1499
1504
|
const handleClick = createCallControlHandler(props, async () => {
|
|
1500
1505
|
if (!hasPermission) {
|
|
@@ -1508,34 +1513,37 @@ const ToggleAudioPublishingButton = (props) => {
|
|
|
1508
1513
|
? t('You have no permission to share your audio')
|
|
1509
1514
|
: !hasBrowserPermission
|
|
1510
1515
|
? t('Check your browser mic permissions')
|
|
1511
|
-
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active:
|
|
1516
|
+
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
|
|
1517
|
+
!hasPermission ||
|
|
1518
|
+
// disable button while the toggle action is pending when not using optimistic updates
|
|
1519
|
+
(!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1512
1520
|
setTooltipDisabled(shown);
|
|
1513
1521
|
onMenuToggle?.(shown);
|
|
1514
|
-
}, children: [jsxRuntime.jsx(Icon, { icon:
|
|
1522
|
+
}, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }) }) }));
|
|
1515
1523
|
};
|
|
1516
1524
|
|
|
1517
1525
|
const ToggleVideoPreviewButton = (props) => {
|
|
1518
|
-
const { caption, Menu = DeviceSelectorVideo, menuPlacement = 'top', onMenuToggle, ...restCompositeButtonProps } = props;
|
|
1526
|
+
const { caption, Menu = DeviceSelectorVideo, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
|
|
1519
1527
|
const { t } = videoReactBindings.useI18n();
|
|
1520
1528
|
const { useCameraState } = videoReactBindings.useCallStateHooks();
|
|
1521
|
-
const { camera,
|
|
1529
|
+
const { camera, hasBrowserPermission, isPromptingPermission, isTogglePending, optionsAwareIsMute, } = useCameraState({ optimisticUpdates });
|
|
1522
1530
|
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
1523
1531
|
const handleClick = createCallControlHandler(props, () => camera.toggle());
|
|
1524
1532
|
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1525
1533
|
? t('Check your browser video permissions')
|
|
1526
|
-
: (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active:
|
|
1534
|
+
: (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optionsAwareIsMute
|
|
1527
1535
|
? 'preview-video-unmute-button'
|
|
1528
|
-
: 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1536
|
+
: 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1529
1537
|
setTooltipDisabled(shown);
|
|
1530
1538
|
onMenuToggle?.(shown);
|
|
1531
|
-
}, children: [jsxRuntime.jsx(Icon, { icon: !
|
|
1539
|
+
}, children: [jsxRuntime.jsx(Icon, { icon: !optionsAwareIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }));
|
|
1532
1540
|
};
|
|
1533
1541
|
const ToggleVideoPublishingButton = (props) => {
|
|
1534
1542
|
const { t } = videoReactBindings.useI18n();
|
|
1535
|
-
const { caption, Menu = jsxRuntime.jsx(DeviceSelectorVideo, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, ...restCompositeButtonProps } = props;
|
|
1543
|
+
const { caption, Menu = jsxRuntime.jsx(DeviceSelectorVideo, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
|
|
1536
1544
|
const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.OwnCapability.SEND_VIDEO);
|
|
1537
1545
|
const { useCameraState, useCallSettings } = videoReactBindings.useCallStateHooks();
|
|
1538
|
-
const { camera,
|
|
1546
|
+
const { camera, optionsAwareIsMute, hasBrowserPermission, isPromptingPermission, isTogglePending, } = useCameraState({ optimisticUpdates });
|
|
1539
1547
|
const callSettings = useCallSettings();
|
|
1540
1548
|
const isPublishingVideoAllowed = callSettings?.video.enabled;
|
|
1541
1549
|
const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
|
|
@@ -1553,12 +1561,13 @@ const ToggleVideoPublishingButton = (props) => {
|
|
|
1553
1561
|
? t('Check your browser video permissions')
|
|
1554
1562
|
: !isPublishingVideoAllowed
|
|
1555
1563
|
? t('Video publishing is disabled by the system')
|
|
1556
|
-
: caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active:
|
|
1564
|
+
: caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
|
|
1557
1565
|
!hasPermission ||
|
|
1558
|
-
!isPublishingVideoAllowed
|
|
1566
|
+
!isPublishingVideoAllowed ||
|
|
1567
|
+
(!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1559
1568
|
setTooltipDisabled(shown);
|
|
1560
1569
|
onMenuToggle?.(shown);
|
|
1561
|
-
}, children: [jsxRuntime.jsx(Icon, { icon:
|
|
1570
|
+
}, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
|
|
1562
1571
|
!hasPermission ||
|
|
1563
1572
|
!isPublishingVideoAllowed) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsxRuntime.jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }) }) }));
|
|
1564
1573
|
};
|
|
@@ -2648,16 +2657,23 @@ const useUpdateCallDuration = () => {
|
|
|
2648
2657
|
};
|
|
2649
2658
|
const useToggleFullScreen = () => {
|
|
2650
2659
|
const { participantViewElement } = useParticipantViewContext();
|
|
2651
|
-
const [isFullscreen, setIsFullscreen] = react.useState(
|
|
2660
|
+
const [isFullscreen, setIsFullscreen] = react.useState(!!document.fullscreenElement);
|
|
2661
|
+
react.useEffect(() => {
|
|
2662
|
+
const handler = () => setIsFullscreen(!!document.fullscreenElement);
|
|
2663
|
+
document.addEventListener('fullscreenchange', handler);
|
|
2664
|
+
return () => {
|
|
2665
|
+
document.removeEventListener('fullscreenchange', handler);
|
|
2666
|
+
};
|
|
2667
|
+
}, []);
|
|
2652
2668
|
return react.useCallback(() => {
|
|
2653
2669
|
if (isFullscreen) {
|
|
2654
|
-
document.exitFullscreen().
|
|
2655
|
-
|
|
2670
|
+
document.exitFullscreen().catch((err) => {
|
|
2671
|
+
console.error('Failed to exit fullscreen', err);
|
|
2656
2672
|
});
|
|
2657
2673
|
}
|
|
2658
2674
|
else {
|
|
2659
|
-
participantViewElement?.requestFullscreen().
|
|
2660
|
-
|
|
2675
|
+
participantViewElement?.requestFullscreen().catch((err) => {
|
|
2676
|
+
console.error('Failed to enter fullscreen', err);
|
|
2661
2677
|
});
|
|
2662
2678
|
}
|
|
2663
2679
|
}, [isFullscreen, participantViewElement]);
|
|
@@ -2954,7 +2970,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
|
|
|
2954
2970
|
return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
|
|
2955
2971
|
};
|
|
2956
2972
|
|
|
2957
|
-
const [major, minor, patch] = ("1.
|
|
2973
|
+
const [major, minor, patch] = ("1.21.0").split('.');
|
|
2958
2974
|
videoClient.setSdkInfo({
|
|
2959
2975
|
type: videoClient.SfuModels.SdkType.REACT,
|
|
2960
2976
|
major,
|