@stream-io/video-react-sdk 1.36.0 → 1.37.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/{embedded-BackgroundFilters-g6ozIvlN.es.js → background-filters-D2U5UBbH.es.js} +95 -32
  3. package/dist/background-filters-D2U5UBbH.es.js.map +1 -0
  4. package/dist/{background-filters-DdQqSx4T.cjs.js → background-filters-folwWrgs.cjs.js} +95 -32
  5. package/dist/background-filters-folwWrgs.cjs.js.map +1 -0
  6. package/dist/css/embedded.css +6 -0
  7. package/dist/css/embedded.css.map +1 -1
  8. package/dist/css/styles.css +6 -0
  9. package/dist/css/styles.css.map +1 -1
  10. package/dist/{background-filters-g6ozIvlN.es.js → embedded-BackgroundFilters-D2U5UBbH.es.js} +95 -32
  11. package/dist/embedded-BackgroundFilters-D2U5UBbH.es.js.map +1 -0
  12. package/dist/{embedded-BackgroundFilters-DdQqSx4T.cjs.js → embedded-BackgroundFilters-folwWrgs.cjs.js} +95 -32
  13. package/dist/embedded-BackgroundFilters-folwWrgs.cjs.js.map +1 -0
  14. package/dist/embedded.cjs.js +41 -25
  15. package/dist/embedded.cjs.js.map +1 -1
  16. package/dist/embedded.es.js +42 -26
  17. package/dist/embedded.es.js.map +1 -1
  18. package/dist/index.cjs.js +42 -26
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.es.js +42 -26
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/src/components/BackgroundFilters/types.d.ts +5 -4
  23. package/dist/src/core/hooks/useTrackElementVisibility.d.ts +2 -3
  24. package/dist/src/translations/index.d.ts +2 -0
  25. package/package.json +5 -5
  26. package/src/components/BackgroundFilters/BackgroundFilters.tsx +145 -53
  27. package/src/components/BackgroundFilters/types.ts +5 -4
  28. package/src/components/CallControls/ToggleAudioButton.tsx +32 -5
  29. package/src/components/CallControls/ToggleVideoButton.tsx +36 -5
  30. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +1 -1
  31. package/src/core/components/CallLayout/SpeakerLayout.tsx +1 -1
  32. package/src/core/components/ParticipantView/ParticipantView.tsx +0 -1
  33. package/src/core/hooks/useTrackElementVisibility.ts +4 -14
  34. package/src/translations/en.json +2 -0
  35. package/dist/background-filters-DdQqSx4T.cjs.js.map +0 -1
  36. package/dist/background-filters-g6ozIvlN.es.js.map +0 -1
  37. package/dist/embedded-BackgroundFilters-DdQqSx4T.cjs.js.map +0 -1
  38. package/dist/embedded-BackgroundFilters-g6ozIvlN.es.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import { useState, useEffect, Fragment as Fragment$1, createContext, useContext, useCallback, useMemo, useRef, isValidElement, forwardRef, useLayoutEffect, lazy, Suspense } from 'react';
3
3
  import { useCall, useCallStateHooks, useI18n, Restricted, useToggleCallRecording, useConnectedUser, useIsAudioConnecting, StreamCallProvider, StreamVideoProvider, useEffectEvent } from '@stream-io/video-react-bindings';
4
- import { hasAudio, hasScreenShareAudio, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack, createSoundDetector, isPinned, name, NoiseCancellationSettingsModeEnum, CallingState, SfuModels, combineComparators, screenSharing, speakerLayoutSortPreset, paginatedLayoutSortPreset, CallTypes, defaultSortPreset, humanize, StreamVideoClient } from '@stream-io/video-client';
4
+ import { hasAudio, hasScreenShareAudio, hasVideo, hasScreenShare, OwnCapability, Browsers, VisibilityState, hasPausedTrack, createSoundDetector, SfuModels, isPinned, name, NoiseCancellationSettingsModeEnum, CallingState, combineComparators, screenSharing, speakerLayoutSortPreset, paginatedLayoutSortPreset, CallTypes, defaultSortPreset, humanize, StreamVideoClient } from '@stream-io/video-client';
5
5
  import { useFloating, offset, shift, flip, size, autoUpdate, FloatingOverlay, FloatingPortal, arrow, FloatingArrow, useListItem, useListNavigation, useTypeahead, useClick, useDismiss, useRole, useInteractions, FloatingFocusManager, FloatingList } from '@floating-ui/react';
6
6
  import clsx from 'clsx';
7
7
 
@@ -663,17 +663,13 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
663
663
  };
664
664
  Video$1.displayName = 'Video';
665
665
 
666
- const useTrackElementVisibility = ({ trackedElement, dynascaleManager: propsDynascaleManager, sessionId, trackType, }) => {
666
+ const useTrackElementVisibility = ({ trackedElement, sessionId, trackType, }) => {
667
667
  const call = useCall();
668
- const manager = propsDynascaleManager ?? call?.dynascaleManager;
669
668
  useEffect(() => {
670
- if (!trackedElement || !manager || !call || trackType === 'none')
669
+ if (!trackedElement || !call || trackType === 'none')
671
670
  return;
672
- const unobserve = manager.trackElementVisibility(trackedElement, sessionId, trackType);
673
- return () => {
674
- unobserve();
675
- };
676
- }, [trackedElement, manager, call, sessionId, trackType]);
671
+ return call.trackElementVisibility(trackedElement, sessionId, trackType);
672
+ }, [trackedElement, call, sessionId, trackType]);
677
673
  };
678
674
 
679
675
  const Avatar = ({ imageSrc, name, style, className, ...rest }) => {
@@ -684,7 +680,7 @@ const AvatarFallback = ({ className, names, style, }) => {
684
680
  return (jsx("div", { className: clsx('str-video__avatar--initials-fallback', className), style: style, children: jsxs("div", { children: [names[0][0], names[1]?.[0]] }) }));
685
681
  };
686
682
 
687
- const BackgroundFiltersProviderImpl = lazy(() => import('./embedded-BackgroundFilters-g6ozIvlN.es.js').then((m) => ({
683
+ const BackgroundFiltersProviderImpl = lazy(() => import('./embedded-BackgroundFilters-D2U5UBbH.es.js').then((m) => ({
688
684
  default: m.BackgroundFiltersProvider,
689
685
  })));
690
686
  /**
@@ -1205,7 +1201,7 @@ const SpeakerTest = (props) => {
1205
1201
  const audioElementRef = useRef(null);
1206
1202
  const [isPlaying, setIsPlaying] = useState(false);
1207
1203
  const { t } = useI18n();
1208
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.36.0"}/assets/piano.mp3`, } = props;
1204
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.37.0"}/assets/piano.mp3`, } = props;
1209
1205
  // Update audio output device when selection changes
1210
1206
  useEffect(() => {
1211
1207
  const audio = audioElementRef.current;
@@ -1314,25 +1310,31 @@ forwardRef(function ToggleDeviceSettingsMenuButton({ menuShown }, ref) {
1314
1310
  const ToggleAudioPreviewButton = (props) => {
1315
1311
  const { caption, Menu = DeviceSelectorAudioInput, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1316
1312
  const { t } = useI18n();
1317
- const { useMicrophoneState } = useCallStateHooks();
1313
+ const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
1318
1314
  const { microphone, hasBrowserPermission, isPromptingPermission, optionsAwareIsMute, isTogglePending, } = useMicrophoneState({ optimisticUpdates });
1315
+ const localParticipant = useLocalParticipant();
1316
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(SfuModels.TrackType.AUDIO);
1319
1317
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
1320
1318
  const handleClick = createCallControlHandler(props, () => microphone.toggle());
1321
1319
  return (jsx(WithTooltip, { title: !hasBrowserPermission
1322
1320
  ? t('Check your browser audio permissions')
1323
- : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute
1321
+ : isSystemMuted
1322
+ ? t('Microphone is paused by your system')
1323
+ : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute
1324
1324
  ? 'preview-audio-unmute-button'
1325
1325
  : 'preview-audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1326
1326
  setTooltipDisabled(shown);
1327
1327
  onMenuToggle?.(shown);
1328
- }, children: [jsx(Icon, { icon: !optionsAwareIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }));
1328
+ }, children: [jsx(Icon, { icon: !optionsAwareIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && (jsx("span", { className: "str-video__system-muted", title: t('Microphone is paused by your system'), children: "!" }))] }) }));
1329
1329
  };
1330
1330
  const ToggleAudioPublishingButton = (props) => {
1331
1331
  const { t } = useI18n();
1332
1332
  const { caption, Menu = jsx(DeviceSelectorAudioInput, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1333
1333
  const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(OwnCapability.SEND_AUDIO);
1334
- const { useMicrophoneState } = useCallStateHooks();
1334
+ const { useMicrophoneState, useLocalParticipant } = useCallStateHooks();
1335
1335
  const { microphone, hasBrowserPermission, isPromptingPermission, isTogglePending, optionsAwareIsMute, } = useMicrophoneState({ optimisticUpdates });
1336
+ const localParticipant = useLocalParticipant();
1337
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(SfuModels.TrackType.AUDIO);
1336
1338
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
1337
1339
  const handleClick = createCallControlHandler(props, async () => {
1338
1340
  if (!hasPermission) {
@@ -1346,37 +1348,45 @@ const ToggleAudioPublishingButton = (props) => {
1346
1348
  ? t('You have no permission to share your audio')
1347
1349
  : !hasBrowserPermission
1348
1350
  ? t('Check your browser mic permissions')
1349
- : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1351
+ : isSystemMuted
1352
+ ? t('Microphone is paused by your system')
1353
+ : (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1350
1354
  !hasPermission ||
1351
1355
  // disable button while the toggle action is pending when not using optimistic updates
1352
1356
  (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1353
1357
  setTooltipDisabled(shown);
1354
1358
  onMenuToggle?.(shown);
1355
- }, children: [jsx(Icon, { icon: optionsAwareIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }) }) }));
1359
+ }, children: [jsx(Icon, { icon: optionsAwareIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && hasPermission && (jsx("span", { className: "str-video__system-muted", title: t('Microphone is paused by your system'), children: "!" }))] }) }) }) }));
1356
1360
  };
1357
1361
 
1358
1362
  const ToggleVideoPreviewButton = (props) => {
1359
1363
  const { caption, Menu = DeviceSelectorVideo, menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1360
1364
  const { t } = useI18n();
1361
- const { useCameraState } = useCallStateHooks();
1365
+ const { useCameraState, useLocalParticipant } = useCallStateHooks();
1362
1366
  const { camera, hasBrowserPermission, isPromptingPermission, isTogglePending, optionsAwareIsMute, } = useCameraState({ optimisticUpdates });
1367
+ const localParticipant = useLocalParticipant();
1368
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(SfuModels.TrackType.VIDEO);
1363
1369
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
1364
1370
  const handleClick = createCallControlHandler(props, () => camera.toggle());
1365
1371
  return (jsx(WithTooltip, { title: !hasBrowserPermission
1366
1372
  ? t('Check your browser video permissions')
1367
- : (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optionsAwareIsMute
1373
+ : isSystemMuted
1374
+ ? t('Camera is paused by your system')
1375
+ : (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optionsAwareIsMute
1368
1376
  ? 'preview-video-unmute-button'
1369
1377
  : 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission || (!optimisticUpdates && isTogglePending), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
1370
1378
  setTooltipDisabled(shown);
1371
1379
  onMenuToggle?.(shown);
1372
- }, children: [jsx(Icon, { icon: !optionsAwareIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }));
1380
+ }, children: [jsx(Icon, { icon: !optionsAwareIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted && hasBrowserPermission && (jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }));
1373
1381
  };
1374
1382
  const ToggleVideoPublishingButton = (props) => {
1375
1383
  const { t } = useI18n();
1376
1384
  const { caption, Menu = jsx(DeviceSelectorVideo, { visualType: "list" }), menuPlacement = 'top', onMenuToggle, optimisticUpdates, ...restCompositeButtonProps } = props;
1377
1385
  const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(OwnCapability.SEND_VIDEO);
1378
- const { useCameraState, useCallSettings } = useCallStateHooks();
1386
+ const { useCameraState, useCallSettings, useLocalParticipant } = useCallStateHooks();
1379
1387
  const { camera, optionsAwareIsMute, hasBrowserPermission, isPromptingPermission, isTogglePending, } = useCameraState({ optimisticUpdates });
1388
+ const localParticipant = useLocalParticipant();
1389
+ const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(SfuModels.TrackType.VIDEO);
1380
1390
  const callSettings = useCallSettings();
1381
1391
  const isPublishingVideoAllowed = callSettings?.video.enabled;
1382
1392
  const [tooltipDisabled, setTooltipDisabled] = useState(false);
@@ -1394,7 +1404,9 @@ const ToggleVideoPublishingButton = (props) => {
1394
1404
  ? t('Check your browser video permissions')
1395
1405
  : !isPublishingVideoAllowed
1396
1406
  ? t('Video publishing is disabled by the system')
1397
- : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1407
+ : isSystemMuted
1408
+ ? t('Camera is paused by your system')
1409
+ : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxs(CompositeButton, { active: optionsAwareIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1398
1410
  !hasPermission ||
1399
1411
  !isPublishingVideoAllowed ||
1400
1412
  (!optimisticUpdates && isTogglePending), "data-testid": optionsAwareIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
@@ -1402,7 +1414,10 @@ const ToggleVideoPublishingButton = (props) => {
1402
1414
  onMenuToggle?.(shown);
1403
1415
  }, children: [jsx(Icon, { icon: optionsAwareIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1404
1416
  !hasPermission ||
1405
- !isPublishingVideoAllowed) && (jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" }))] }) }) }) }));
1417
+ !isPublishingVideoAllowed) && (jsx("span", { className: "str-video__no-media-permission", children: "!" })), isPromptingPermission && (jsx("span", { className: "str-video__pending-permission", title: t('Waiting for permission'), children: "?" })), isSystemMuted &&
1418
+ hasBrowserPermission &&
1419
+ hasPermission &&
1420
+ isPublishingVideoAllowed && (jsx("span", { className: "str-video__system-muted", title: t('Camera is paused by your system'), children: "!" }))] }) }) }) }));
1406
1421
  };
1407
1422
 
1408
1423
  const EndCallMenu = (props) => {
@@ -1900,7 +1915,6 @@ const ParticipantView = forwardRef(function ParticipantView({ participant, track
1900
1915
  const [trackedElement, setTrackedElement] = useState(null);
1901
1916
  const [contextVideoElement, setContextVideoElement] = useState(null);
1902
1917
  const [contextVideoPlaceholderElement, setContextVideoPlaceholderElement] = useState(null);
1903
- // TODO: allow to pass custom ViewportTracker instance from props
1904
1918
  useTrackElementVisibility({
1905
1919
  sessionId,
1906
1920
  trackedElement,
@@ -2031,8 +2045,10 @@ var en = {
2031
2045
  Dismiss: Dismiss,
2032
2046
  "Microphone on": "Microphone on",
2033
2047
  "Microphone off": "Microphone off",
2048
+ "Microphone is paused by your system": "Microphone is paused by your system",
2034
2049
  "Camera on": "Camera on",
2035
2050
  "Camera off": "Camera off",
2051
+ "Camera is paused by your system": "Camera is paused by your system",
2036
2052
  "No camera found": "No camera found",
2037
2053
  "Video is disabled": "Video is disabled",
2038
2054
  Pinned: Pinned,
@@ -2419,7 +2435,7 @@ const PaginatedGridLayout = (props) => {
2419
2435
  if (!paginatedGridLayoutWrapperElement || !call)
2420
2436
  return;
2421
2437
  const cleanup = call.setViewport(paginatedGridLayoutWrapperElement);
2422
- return () => cleanup();
2438
+ return () => cleanup?.();
2423
2439
  }, [paginatedGridLayoutWrapperElement, call]);
2424
2440
  // only used to render video elements
2425
2441
  const participantGroups = useMemo(() => chunk(participants, groupSize), [participants, groupSize]);
@@ -2511,7 +2527,7 @@ const SpeakerLayout = ({ ParticipantViewUIBar = DefaultParticipantViewUIBar, Par
2511
2527
  if (!participantsBarWrapperElement || !call)
2512
2528
  return;
2513
2529
  const cleanup = call.setViewport(participantsBarWrapperElement);
2514
- return () => cleanup();
2530
+ return () => cleanup?.();
2515
2531
  }, [participantsBarWrapperElement, call]);
2516
2532
  const isOneOnOneCall = allParticipants.length === 2;
2517
2533
  useSpeakerLayoutSortPreset(call, isOneOnOneCall);