@stream-io/video-react-sdk 1.33.4 → 1.34.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/{background-filters-Zu84SkRR.cjs.js → background-filters-DdQqSx4T.cjs.js} +7 -3
  3. package/dist/background-filters-DdQqSx4T.cjs.js.map +1 -0
  4. package/dist/{background-filters-RdXfNf6_.es.js → background-filters-g6ozIvlN.es.js} +7 -3
  5. package/dist/background-filters-g6ozIvlN.es.js.map +1 -0
  6. package/dist/css/embedded.css +5 -0
  7. package/dist/css/embedded.css.map +1 -1
  8. package/dist/css/styles.css +5 -0
  9. package/dist/css/styles.css.map +1 -1
  10. package/dist/{embedded-BackgroundFilters-Zu84SkRR.cjs.js → embedded-BackgroundFilters-DdQqSx4T.cjs.js} +7 -3
  11. package/dist/embedded-BackgroundFilters-DdQqSx4T.cjs.js.map +1 -0
  12. package/dist/{embedded-BackgroundFilters-RdXfNf6_.es.js → embedded-BackgroundFilters-g6ozIvlN.es.js} +7 -3
  13. package/dist/embedded-BackgroundFilters-g6ozIvlN.es.js.map +1 -0
  14. package/dist/embedded.cjs.js +64 -35
  15. package/dist/embedded.cjs.js.map +1 -1
  16. package/dist/embedded.es.js +64 -35
  17. package/dist/embedded.es.js.map +1 -1
  18. package/dist/index.cjs.js +70 -39
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.es.js +70 -39
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/src/components/BackgroundFilters/types.d.ts +6 -1
  23. package/dist/src/components/CallControls/RecordCallButton.d.ts +6 -5
  24. package/dist/src/translations/index.d.ts +1 -0
  25. package/package.json +5 -5
  26. package/src/components/BackgroundFilters/BackgroundFilters.tsx +6 -0
  27. package/src/components/BackgroundFilters/types.ts +7 -0
  28. package/src/components/CallControls/CallControls.tsx +4 -9
  29. package/src/components/CallControls/RecordCallButton.tsx +26 -13
  30. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +44 -4
  31. package/src/translations/en.json +1 -0
  32. package/dist/background-filters-RdXfNf6_.es.js.map +0 -1
  33. package/dist/background-filters-Zu84SkRR.cjs.js.map +0 -1
  34. package/dist/embedded-BackgroundFilters-RdXfNf6_.es.js.map +0 -1
  35. package/dist/embedded-BackgroundFilters-Zu84SkRR.cjs.js.map +0 -1
@@ -684,7 +684,7 @@ const AvatarFallback = ({ className, names, style, }) => {
684
684
  return (jsx("div", { className: clsx('str-video__avatar--initials-fallback', className), style: style, children: jsxs("div", { children: [names[0][0], names[1]?.[0]] }) }));
685
685
  };
686
686
 
687
- const BackgroundFiltersProviderImpl = lazy(() => import('./embedded-BackgroundFilters-RdXfNf6_.es.js').then((m) => ({
687
+ const BackgroundFiltersProviderImpl = lazy(() => import('./embedded-BackgroundFilters-g6ozIvlN.es.js').then((m) => ({
688
688
  default: m.BackgroundFiltersProvider,
689
689
  })));
690
690
  /**
@@ -862,23 +862,53 @@ const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, tooltipDisable
862
862
  return (jsxs(Fragment, { children: [jsx(Tooltip, { referenceElement: tooltipAnchor, visible: tooltipActuallyVisible, tooltipClassName: tooltipClassName, tooltipPlacement: tooltipPlacement, children: title || '' }), jsx("div", { ref: setTooltipAnchor, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props })] }));
863
863
  };
864
864
 
865
- const RecordEndConfirmation = () => {
865
+ /**
866
+ * Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
867
+ * DOMException, which is a normal situation handled by the SDK)
868
+ *
869
+ * @param props component props, including the onError callback
870
+ * @param handler event handler to wrap
871
+ */
872
+ const createCallControlHandler = (props, handler) => {
873
+ return async () => {
874
+ try {
875
+ await handler();
876
+ }
877
+ catch (error) {
878
+ if (props.onError) {
879
+ props.onError(error);
880
+ return;
881
+ }
882
+ if (!isNotAllowedError(error)) {
883
+ console.error('Call control handler failed', error);
884
+ }
885
+ }
886
+ };
887
+ };
888
+ function isNotAllowedError(error) {
889
+ return error instanceof DOMException && error.name === 'NotAllowedError';
890
+ }
891
+
892
+ const RecordEndConfirmation = (props) => {
866
893
  const { t } = useI18n();
867
894
  const { toggleCallRecording, isAwaitingResponse } = useToggleCallRecording();
868
895
  const { close } = useMenuContext();
869
- return (jsxs("div", { className: "str-video__end-recording__confirmation", children: [jsxs("div", { className: "str-video__end-recording__header", children: [jsx(Icon, { icon: "recording-on" }), jsx("h2", { className: "str-video__end-recording__heading", children: t('End recording') })] }), jsx("p", { className: "str-video__end-recording__description", children: t('Are you sure you want end the recording?') }), jsxs("div", { className: "str-video__end-recording__actions", children: [jsx(CompositeButton, { variant: "secondary", onClick: close, children: t('Cancel') }), jsx(CompositeButton, { variant: "primary", onClick: toggleCallRecording, children: isAwaitingResponse ? jsx(LoadingIndicator, {}) : t('End recording') })] })] }));
896
+ const handleClick = createCallControlHandler(props, toggleCallRecording);
897
+ return (jsxs("div", { className: "str-video__end-recording__confirmation", children: [jsxs("div", { className: "str-video__end-recording__header", children: [jsx(Icon, { icon: "recording-on" }), jsx("h2", { className: "str-video__end-recording__heading", children: t('End recording') })] }), jsx("p", { className: "str-video__end-recording__description", children: t('Are you sure you want end the recording?') }), jsxs("div", { className: "str-video__end-recording__actions", children: [jsx(CompositeButton, { variant: "secondary", onClick: close, children: t('Cancel') }), jsx(CompositeButton, { variant: "primary", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? jsx(LoadingIndicator, {}) : t('End recording') })] })] }));
870
898
  };
871
899
  const ToggleEndRecordingMenuButton = forwardRef(function ToggleEndRecordingMenuButton(props, ref) {
872
900
  return (jsx(CompositeButton, { ref: ref, active: true, variant: "secondary", "data-testid": "recording-stop-button", children: jsx(Icon, { icon: "recording-off" }) }));
873
901
  });
874
- const RecordCallConfirmationButton = ({ caption, }) => {
902
+ const RecordCallConfirmationButton = (props) => {
903
+ const { caption } = props;
875
904
  const { t } = useI18n();
876
905
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording();
906
+ const handleClick = createCallControlHandler(props, toggleCallRecording);
877
907
  if (isCallRecordingInProgress) {
878
908
  return (jsx(Restricted, { requiredGrants: [
879
909
  OwnCapability.START_RECORD_CALL,
880
910
  OwnCapability.STOP_RECORD_CALL,
881
- ], children: jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: MenuVisualType.PORTAL, children: jsx(RecordEndConfirmation, {}) }) }));
911
+ ], children: jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: MenuVisualType.PORTAL, children: jsx(RecordEndConfirmation, { onError: props.onError }) }) }));
882
912
  }
883
913
  const title = isAwaitingResponse
884
914
  ? t('Waiting for recording to start...')
@@ -886,7 +916,7 @@ const RecordCallConfirmationButton = ({ caption, }) => {
886
916
  return (jsx(Restricted, { requiredGrants: [
887
917
  OwnCapability.START_RECORD_CALL,
888
918
  OwnCapability.STOP_RECORD_CALL,
889
- ], children: jsx(WithTooltip, { title: title, children: jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : toggleCallRecording, children: isAwaitingResponse ? (jsx(LoadingIndicator, {})) : (jsx(Icon, { icon: "recording-off" })) }) }) }));
919
+ ], children: jsx(WithTooltip, { title: title, children: jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsx(LoadingIndicator, {})) : (jsx(Icon, { icon: "recording-off" })) }) }) }));
890
920
  };
891
921
 
892
922
  const defaultEmojiReactionMap = {
@@ -961,33 +991,6 @@ const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
961
991
  }, children: reaction.emoji_code && defaultEmojiReactionMap[reaction.emoji_code] }, reaction.emoji_code))) }));
962
992
  };
963
993
 
964
- /**
965
- * Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
966
- * DOMException, which is a normal situation handled by the SDK)
967
- *
968
- * @param props component props, including the onError callback
969
- * @param handler event handler to wrap
970
- */
971
- const createCallControlHandler = (props, handler) => {
972
- return async () => {
973
- try {
974
- await handler();
975
- }
976
- catch (error) {
977
- if (props.onError) {
978
- props.onError(error);
979
- return;
980
- }
981
- if (!isNotAllowedError(error)) {
982
- console.error('Call control handler failed', error);
983
- }
984
- }
985
- };
986
- };
987
- function isNotAllowedError(error) {
988
- return error instanceof DOMException && error.name === 'NotAllowedError';
989
- }
990
-
991
994
  const ScreenShareButton = (props) => {
992
995
  const { t } = useI18n();
993
996
  const { caption, optimisticUpdates } = props;
@@ -1151,7 +1154,7 @@ const SpeakerTest = (props) => {
1151
1154
  const audioElementRef = useRef(null);
1152
1155
  const [isPlaying, setIsPlaying] = useState(false);
1153
1156
  const { t } = useI18n();
1154
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.33.4"}/assets/piano.mp3`, } = props;
1157
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.34.1"}/assets/piano.mp3`, } = props;
1155
1158
  // Update audio output device when selection changes
1156
1159
  useEffect(() => {
1157
1160
  const audio = audioElementRef.current;
@@ -1792,7 +1795,9 @@ const ParticipantDetails = ({ indicatorsVisible = true, }) => {
1792
1795
  const hasVideoTrack = hasVideo(participant);
1793
1796
  const canUnpin = !!pin && pin.isLocalPin;
1794
1797
  const isTrackPaused = trackType !== 'none' ? hasPausedTrack(participant, trackType) : false;
1795
- return (jsxs(Fragment, { children: [jsx("div", { className: "str-video__participant-details", children: jsxs("span", { className: "str-video__participant-details__name", children: [name || userId, indicatorsVisible && !hasAudioTrack && (jsx("span", { className: "str-video__participant-details__name--audio-muted" })), indicatorsVisible && !hasVideoTrack && (jsx("span", { className: "str-video__participant-details__name--video-muted" })), indicatorsVisible && isTrackPaused && (jsx("span", { title: t('Video paused due to insufficient bandwidth'), className: "str-video__participant-details__name--track-paused" })), indicatorsVisible && canUnpin && (
1798
+ const isAudioTrackUnmuted = useIsTrackUnmuted(participant);
1799
+ const isAudioConnecting = hasAudioTrack && !isAudioTrackUnmuted;
1800
+ return (jsxs(Fragment, { children: [jsx("div", { className: "str-video__participant-details", children: jsxs("div", { className: "str-video__participant-details__name", children: [name || userId, indicatorsVisible && isAudioConnecting && (jsx(LoadingIndicator, { className: "str-video__participant-details__name--audio-connecting", tooltip: t('Audio is connecting...') })), indicatorsVisible && !hasAudioTrack && (jsx("span", { className: "str-video__participant-details__name--audio-muted" })), indicatorsVisible && !hasVideoTrack && (jsx("span", { className: "str-video__participant-details__name--video-muted" })), indicatorsVisible && isTrackPaused && (jsx("span", { title: t('Video paused due to insufficient bandwidth'), className: "str-video__participant-details__name--track-paused" })), indicatorsVisible && canUnpin && (
1796
1801
  // TODO: remove this monstrosity once we have a proper design
1797
1802
  jsx("span", { title: t('Unpin'), onClick: () => call?.unpin(sessionId), className: "str-video__participant-details__name--pinned" })), indicatorsVisible && jsx(SpeechIndicator, {})] }) }), indicatorsVisible && (jsx(Notification, { isVisible: isLocalParticipant &&
1798
1803
  connectionQuality === SfuModels.ConnectionQuality.POOR, message: t('Poor connection quality'), children: connectionQualityAsString && (jsx("span", { className: clsx('str-video__participant-details__connection-quality', `str-video__participant-details__connection-quality--${connectionQualityAsString}`), title: connectionQualityAsString })) }))] }));
@@ -1802,6 +1807,29 @@ const SpeechIndicator = () => {
1802
1807
  const { isSpeaking, isDominantSpeaker } = participant;
1803
1808
  return (jsxs("span", { className: clsx('str-video__speech-indicator', isSpeaking && 'str-video__speech-indicator--speaking', isDominantSpeaker && 'str-video__speech-indicator--dominant'), children: [jsx("span", { className: "str-video__speech-indicator__bar" }), jsx("span", { className: "str-video__speech-indicator__bar" }), jsx("span", { className: "str-video__speech-indicator__bar" })] }));
1804
1809
  };
1810
+ const useIsTrackUnmuted = (participant) => {
1811
+ const audioStream = participant.audioStream;
1812
+ const [unmuted, setUnmuted] = useState(() => {
1813
+ const track = audioStream?.getAudioTracks()[0];
1814
+ return !!track && !track.muted;
1815
+ });
1816
+ useEffect(() => {
1817
+ const track = audioStream?.getAudioTracks()[0];
1818
+ if (!track)
1819
+ return;
1820
+ setUnmuted(!track.muted);
1821
+ const handler = () => {
1822
+ setUnmuted(!track.muted);
1823
+ };
1824
+ track.addEventListener('mute', handler);
1825
+ track.addEventListener('unmute', handler);
1826
+ return () => {
1827
+ track.removeEventListener('mute', handler);
1828
+ track.removeEventListener('unmute', handler);
1829
+ };
1830
+ }, [audioStream]);
1831
+ return unmuted;
1832
+ };
1805
1833
 
1806
1834
  const ParticipantView = forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
1807
1835
  const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
@@ -1972,6 +2000,7 @@ var en = {
1972
2000
  "Dominant Speaker": "Dominant Speaker",
1973
2001
  "Poor connection quality": "Poor connection quality. Please check your internet connection.",
1974
2002
  "Video paused due to insufficient bandwidth": "Video paused due to insufficient bandwidth",
2003
+ "Audio is connecting...": "Audio is connecting...",
1975
2004
  Participants: Participants,
1976
2005
  Anonymous: Anonymous,
1977
2006
  "No participants found": "No participants found",