@stream-io/video-react-sdk 1.43.4 → 1.44.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.
@@ -819,6 +819,28 @@ const MicCaptureErrorNotification = ({ children, text, placement, }) => {
819
819
  return (jsxRuntime.jsx(Notification, { message: message, isVisible: isVisible, placement: placement, close: () => setIsVisible(false), children: children }));
820
820
  };
821
821
 
822
+ const UNAVAILABLE_ERRORS = ['NotReadableError', 'AbortError'];
823
+ const isCameraUnavailable = (error) => !!error?.name && UNAVAILABLE_ERRORS.includes(error.name);
824
+ /**
825
+ * Shows when the last attempt to start the camera failed (e.g. the camera is
826
+ * in use by another app). Reads the error from the camera state, so it shows
827
+ * failures that happened before it was mounted.
828
+ *
829
+ * For error-specific copy, build your own notification on top of
830
+ * `useCameraState().error`.
831
+ */
832
+ const CameraUnavailableNotification = ({ children, placement, className, }) => {
833
+ const { useCameraState } = videoReactBindings.useCallStateHooks();
834
+ const { error } = useCameraState();
835
+ const { t } = videoReactBindings.useI18n();
836
+ const [isVisible, setIsVisible] = react.useState(() => isCameraUnavailable(error));
837
+ react.useEffect(() => {
838
+ setIsVisible(isCameraUnavailable(error));
839
+ }, [error]);
840
+ const message = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: "str-video__notification__line str-video__notification__title", children: t('Camera unavailable') }), jsxRuntime.jsx("span", { className: "str-video__notification__line", children: t('Close other apps that might be using your camera and try again.') })] }));
841
+ return (jsxRuntime.jsx(Notification, { message: message, isVisible: isVisible, placement: placement, className: clsx('str-video__camera-unavailable-notification', className), close: () => setIsVisible(false), children: children }));
842
+ };
843
+
822
844
  const LoadingIndicator = ({ className, type = 'spinner', text, tooltip, }) => {
823
845
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__loading-indicator', className), title: tooltip, children: [jsxRuntime.jsx("div", { className: clsx('str-video__loading-indicator__icon', type) }), text && jsxRuntime.jsx("p", { className: "str-video__loading-indicator-text", children: text })] }));
824
846
  };
@@ -1210,7 +1232,7 @@ const SpeakerTest = (props) => {
1210
1232
  const audioElementRef = react.useRef(null);
1211
1233
  const [isPlaying, setIsPlaying] = react.useState(false);
1212
1234
  const { t } = videoReactBindings.useI18n();
1213
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.43.4"}/assets/piano.mp3`, } = props;
1235
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.44.0"}/assets/piano.mp3`, } = props;
1214
1236
  // Update audio output device when selection changes
1215
1237
  react.useEffect(() => {
1216
1238
  const audio = audioElementRef.current;
@@ -1377,16 +1399,16 @@ const ToggleVideoPreviewButton = (props) => {
1377
1399
  const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(videoClient.SfuModels.TrackType.VIDEO);
1378
1400
  const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1379
1401
  const handleClick = createCallControlHandler(props, () => camera.toggle());
1380
- return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1381
- ? t('Check your browser video permissions')
1382
- : isSystemMuted
1383
- ? t('Camera is paused by your system')
1384
- : (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
1385
- ? 'preview-video-unmute-button'
1386
- : 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1387
- setTooltipDisabled(shown);
1388
- onMenuToggle?.(shown);
1389
- }, 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: "?" })), isSystemMuted && hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }));
1402
+ return (jsxRuntime.jsx(CameraUnavailableNotification, { children: jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1403
+ ? t('Check your browser video permissions')
1404
+ : isSystemMuted
1405
+ ? t('Camera is paused by your system')
1406
+ : (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
1407
+ ? 'preview-video-unmute-button'
1408
+ : 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1409
+ setTooltipDisabled(shown);
1410
+ onMenuToggle?.(shown);
1411
+ }, 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: "?" })), isSystemMuted && hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }) }));
1390
1412
  };
1391
1413
  const ToggleVideoPublishingButton = (props) => {
1392
1414
  const { t } = videoReactBindings.useI18n();
@@ -1407,26 +1429,26 @@ const ToggleVideoPublishingButton = (props) => {
1407
1429
  await camera.toggle();
1408
1430
  }
1409
1431
  });
1410
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1411
- ? t('You have no permission to share your video')
1412
- : !hasBrowserPermission
1413
- ? t('Check your browser video permissions')
1414
- : !isPublishingVideoAllowed
1415
- ? t('Video publishing is disabled by the system')
1416
- : isSystemMuted
1417
- ? t('Camera is paused by your system')
1418
- : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1419
- !hasPermission ||
1420
- !isPublishingVideoAllowed ||
1421
- (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1422
- setTooltipDisabled(shown);
1423
- onMenuToggle?.(shown);
1424
- }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1432
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsx(CameraUnavailableNotification, { children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1433
+ ? t('You have no permission to share your video')
1434
+ : !hasBrowserPermission
1435
+ ? t('Check your browser video permissions')
1436
+ : !isPublishingVideoAllowed
1437
+ ? t('Video publishing is disabled by the system')
1438
+ : isSystemMuted
1439
+ ? t('Camera is paused by your system')
1440
+ : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1425
1441
  !hasPermission ||
1426
- !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: "?" })), isSystemMuted &&
1427
- hasBrowserPermission &&
1428
- hasPermission &&
1429
- isPublishingVideoAllowed && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }) }) }));
1442
+ !isPublishingVideoAllowed ||
1443
+ (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1444
+ setTooltipDisabled(shown);
1445
+ onMenuToggle?.(shown);
1446
+ }, children: [jsxRuntime.jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1447
+ !hasPermission ||
1448
+ !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: "?" })), isSystemMuted &&
1449
+ hasBrowserPermission &&
1450
+ hasPermission &&
1451
+ isPublishingVideoAllowed && (jsxRuntime.jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }) }) }) }));
1430
1452
  };
1431
1453
 
1432
1454
  const EndCallMenu = (props) => {
@@ -2088,6 +2110,8 @@ var en = {
2088
2110
  "Camera on": "Camera on",
2089
2111
  "Camera off": "Camera off",
2090
2112
  "Camera is paused by your system": "Camera is paused by your system",
2113
+ "Camera unavailable": "Camera unavailable",
2114
+ "Close other apps that might be using your camera and try again.": "Close other apps that might be using your camera and try again.",
2091
2115
  "No camera found": "No camera found",
2092
2116
  "Video is disabled": "Video is disabled",
2093
2117
  Pinned: Pinned,