@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
package/dist/index.es.js CHANGED
@@ -828,7 +828,7 @@ const AvatarFallback = ({ className, names, style, }) => {
828
828
  return (jsx("div", { className: clsx('str-video__avatar--initials-fallback', className), style: style, children: jsxs("div", { children: [names[0][0], names[1]?.[0]] }) }));
829
829
  };
830
830
 
831
- const BackgroundFiltersProviderImpl = lazy(() => import('./background-filters-RdXfNf6_.es.js').then((m) => ({
831
+ const BackgroundFiltersProviderImpl = lazy(() => import('./background-filters-g6ozIvlN.es.js').then((m) => ({
832
832
  default: m.BackgroundFiltersProvider,
833
833
  })));
834
834
  /**
@@ -1042,23 +1042,53 @@ const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, tooltipDisable
1042
1042
  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 })] }));
1043
1043
  };
1044
1044
 
1045
- const RecordEndConfirmation = () => {
1045
+ /**
1046
+ * Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
1047
+ * DOMException, which is a normal situation handled by the SDK)
1048
+ *
1049
+ * @param props component props, including the onError callback
1050
+ * @param handler event handler to wrap
1051
+ */
1052
+ const createCallControlHandler = (props, handler) => {
1053
+ return async () => {
1054
+ try {
1055
+ await handler();
1056
+ }
1057
+ catch (error) {
1058
+ if (props.onError) {
1059
+ props.onError(error);
1060
+ return;
1061
+ }
1062
+ if (!isNotAllowedError(error)) {
1063
+ console.error('Call control handler failed', error);
1064
+ }
1065
+ }
1066
+ };
1067
+ };
1068
+ function isNotAllowedError(error) {
1069
+ return error instanceof DOMException && error.name === 'NotAllowedError';
1070
+ }
1071
+
1072
+ const RecordEndConfirmation = (props) => {
1046
1073
  const { t } = useI18n();
1047
1074
  const { toggleCallRecording, isAwaitingResponse } = useToggleCallRecording();
1048
1075
  const { close } = useMenuContext();
1049
- 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') })] })] }));
1076
+ const handleClick = createCallControlHandler(props, toggleCallRecording);
1077
+ 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') })] })] }));
1050
1078
  };
1051
1079
  const ToggleEndRecordingMenuButton = forwardRef(function ToggleEndRecordingMenuButton(props, ref) {
1052
1080
  return (jsx(CompositeButton, { ref: ref, active: true, variant: "secondary", "data-testid": "recording-stop-button", children: jsx(Icon, { icon: "recording-off" }) }));
1053
1081
  });
1054
- const RecordCallConfirmationButton = ({ caption, }) => {
1082
+ const RecordCallConfirmationButton = (props) => {
1083
+ const { caption } = props;
1055
1084
  const { t } = useI18n();
1056
1085
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording();
1086
+ const handleClick = createCallControlHandler(props, toggleCallRecording);
1057
1087
  if (isCallRecordingInProgress) {
1058
1088
  return (jsx(Restricted, { requiredGrants: [
1059
1089
  OwnCapability.START_RECORD_CALL,
1060
1090
  OwnCapability.STOP_RECORD_CALL,
1061
- ], children: jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: MenuVisualType.PORTAL, children: jsx(RecordEndConfirmation, {}) }) }));
1091
+ ], children: jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: MenuVisualType.PORTAL, children: jsx(RecordEndConfirmation, { onError: props.onError }) }) }));
1062
1092
  }
1063
1093
  const title = isAwaitingResponse
1064
1094
  ? t('Waiting for recording to start...')
@@ -1066,11 +1096,13 @@ const RecordCallConfirmationButton = ({ caption, }) => {
1066
1096
  return (jsx(Restricted, { requiredGrants: [
1067
1097
  OwnCapability.START_RECORD_CALL,
1068
1098
  OwnCapability.STOP_RECORD_CALL,
1069
- ], 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" })) }) }) }));
1099
+ ], 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" })) }) }) }));
1070
1100
  };
1071
- const RecordCallButton = ({ caption }) => {
1101
+ const RecordCallButton = (props) => {
1102
+ const { caption } = props;
1072
1103
  const { t } = useI18n();
1073
1104
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording();
1105
+ const handleClick = createCallControlHandler(props, toggleCallRecording);
1074
1106
  let title = caption ?? t('Record call');
1075
1107
  if (isAwaitingResponse) {
1076
1108
  title = isCallRecordingInProgress
@@ -1082,7 +1114,7 @@ const RecordCallButton = ({ caption }) => {
1082
1114
  OwnCapability.STOP_RECORD_CALL,
1083
1115
  ], children: jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": isCallRecordingInProgress
1084
1116
  ? 'recording-stop-button'
1085
- : 'recording-start-button', title: title, onClick: isAwaitingResponse ? undefined : toggleCallRecording, children: isAwaitingResponse ? (jsx(LoadingIndicator, {})) : (jsx(Icon, { icon: isCallRecordingInProgress ? 'recording-on' : 'recording-off' })) }) }));
1117
+ : 'recording-start-button', title: title, onClick: isAwaitingResponse ? undefined : handleClick, children: isAwaitingResponse ? (jsx(LoadingIndicator, {})) : (jsx(Icon, { icon: isCallRecordingInProgress ? 'recording-on' : 'recording-off' })) }) }));
1086
1118
  };
1087
1119
 
1088
1120
  const defaultEmojiReactionMap = {
@@ -1157,33 +1189,6 @@ const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
1157
1189
  }, children: reaction.emoji_code && defaultEmojiReactionMap[reaction.emoji_code] }, reaction.emoji_code))) }));
1158
1190
  };
1159
1191
 
1160
- /**
1161
- * Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
1162
- * DOMException, which is a normal situation handled by the SDK)
1163
- *
1164
- * @param props component props, including the onError callback
1165
- * @param handler event handler to wrap
1166
- */
1167
- const createCallControlHandler = (props, handler) => {
1168
- return async () => {
1169
- try {
1170
- await handler();
1171
- }
1172
- catch (error) {
1173
- if (props.onError) {
1174
- props.onError(error);
1175
- return;
1176
- }
1177
- if (!isNotAllowedError(error)) {
1178
- console.error('Call control handler failed', error);
1179
- }
1180
- }
1181
- };
1182
- };
1183
- function isNotAllowedError(error) {
1184
- return error instanceof DOMException && error.name === 'NotAllowedError';
1185
- }
1186
-
1187
1192
  const ScreenShareButton = (props) => {
1188
1193
  const { t } = useI18n();
1189
1194
  const { caption, optimisticUpdates } = props;
@@ -1347,7 +1352,7 @@ const SpeakerTest = (props) => {
1347
1352
  const audioElementRef = useRef(null);
1348
1353
  const [isPlaying, setIsPlaying] = useState(false);
1349
1354
  const { t } = useI18n();
1350
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.33.4"}/assets/piano.mp3`, } = props;
1355
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.34.1"}/assets/piano.mp3`, } = props;
1351
1356
  // Update audio output device when selection changes
1352
1357
  useEffect(() => {
1353
1358
  const audio = audioElementRef.current;
@@ -1582,7 +1587,7 @@ const CancelCallButton = ({ disabled, caption, onClick, onLeave, }) => {
1582
1587
  return (jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: caption ?? t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
1583
1588
  };
1584
1589
 
1585
- const CallControls = ({ onLeave }) => (jsxs("div", { className: "str-video__call-controls", children: [jsx(Restricted, { requiredGrants: [OwnCapability.SEND_AUDIO], children: jsx(MicCaptureErrorNotification, { children: jsx(SpeakingWhileMutedNotification, { children: jsx(ToggleAudioPublishingButton, {}) }) }) }), jsx(Restricted, { requiredGrants: [OwnCapability.SEND_VIDEO], children: jsx(ToggleVideoPublishingButton, {}) }), jsx(Restricted, { requiredGrants: [OwnCapability.CREATE_REACTION], children: jsx(ReactionsButton, {}) }), jsx(Restricted, { requiredGrants: [OwnCapability.SCREENSHARE], children: jsx(ScreenShareButton, {}) }), jsx(Restricted, { requiredGrants: [
1590
+ const CallControls = ({ onLeave }) => (jsxs("div", { className: "str-video__call-controls", children: [jsx(Restricted, { requiredGrants: [OwnCapability.SEND_AUDIO], children: jsx(SpeakingWhileMutedNotification, { children: jsx(ToggleAudioPublishingButton, {}) }) }), jsx(Restricted, { requiredGrants: [OwnCapability.SEND_VIDEO], children: jsx(ToggleVideoPublishingButton, {}) }), jsx(Restricted, { requiredGrants: [OwnCapability.CREATE_REACTION], children: jsx(ReactionsButton, {}) }), jsx(Restricted, { requiredGrants: [OwnCapability.SCREENSHARE], children: jsx(ScreenShareButton, {}) }), jsx(Restricted, { requiredGrants: [
1586
1591
  OwnCapability.START_RECORD_CALL,
1587
1592
  OwnCapability.STOP_RECORD_CALL,
1588
1593
  ], children: jsx(RecordCallButton, {}) }), jsx(CancelCallButton, { onLeave: onLeave })] }));
@@ -2288,7 +2293,9 @@ const ParticipantDetails = ({ indicatorsVisible = true, }) => {
2288
2293
  const hasVideoTrack = hasVideo(participant);
2289
2294
  const canUnpin = !!pin && pin.isLocalPin;
2290
2295
  const isTrackPaused = trackType !== 'none' ? hasPausedTrack(participant, trackType) : false;
2291
- 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 && (
2296
+ const isAudioTrackUnmuted = useIsTrackUnmuted(participant);
2297
+ const isAudioConnecting = hasAudioTrack && !isAudioTrackUnmuted;
2298
+ 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 && (
2292
2299
  // TODO: remove this monstrosity once we have a proper design
2293
2300
  jsx("span", { title: t('Unpin'), onClick: () => call?.unpin(sessionId), className: "str-video__participant-details__name--pinned" })), indicatorsVisible && jsx(SpeechIndicator, {})] }) }), indicatorsVisible && (jsx(Notification, { isVisible: isLocalParticipant &&
2294
2301
  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 })) }))] }));
@@ -2298,6 +2305,29 @@ const SpeechIndicator = () => {
2298
2305
  const { isSpeaking, isDominantSpeaker } = participant;
2299
2306
  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" })] }));
2300
2307
  };
2308
+ const useIsTrackUnmuted = (participant) => {
2309
+ const audioStream = participant.audioStream;
2310
+ const [unmuted, setUnmuted] = useState(() => {
2311
+ const track = audioStream?.getAudioTracks()[0];
2312
+ return !!track && !track.muted;
2313
+ });
2314
+ useEffect(() => {
2315
+ const track = audioStream?.getAudioTracks()[0];
2316
+ if (!track)
2317
+ return;
2318
+ setUnmuted(!track.muted);
2319
+ const handler = () => {
2320
+ setUnmuted(!track.muted);
2321
+ };
2322
+ track.addEventListener('mute', handler);
2323
+ track.addEventListener('unmute', handler);
2324
+ return () => {
2325
+ track.removeEventListener('mute', handler);
2326
+ track.removeEventListener('unmute', handler);
2327
+ };
2328
+ }, [audioStream]);
2329
+ return unmuted;
2330
+ };
2301
2331
 
2302
2332
  const ParticipantView = forwardRef(function ParticipantView({ participant, trackType = 'videoTrack', mirror, muteAudio, refs: { setVideoElement, setVideoPlaceholderElement } = {}, className, VideoPlaceholder, PictureInPicturePlaceholder, ParticipantViewUI = DefaultParticipantViewUI, }, ref) {
2303
2333
  const { isLocalParticipant, isSpeaking, isDominantSpeaker, sessionId } = participant;
@@ -2468,6 +2498,7 @@ var en = {
2468
2498
  "Dominant Speaker": "Dominant Speaker",
2469
2499
  "Poor connection quality": "Poor connection quality. Please check your internet connection.",
2470
2500
  "Video paused due to insufficient bandwidth": "Video paused due to insufficient bandwidth",
2501
+ "Audio is connecting...": "Audio is connecting...",
2471
2502
  Participants: Participants,
2472
2503
  Anonymous: Anonymous,
2473
2504
  "No participants found": "No participants found",
@@ -3144,7 +3175,7 @@ const checkCanJoinEarly = (startsAt, joinAheadTimeSeconds) => {
3144
3175
  return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
3145
3176
  };
3146
3177
 
3147
- const [major, minor, patch] = ("1.33.4").split('.');
3178
+ const [major, minor, patch] = ("1.34.1").split('.');
3148
3179
  setSdkInfo({
3149
3180
  type: SfuModels.SdkType.REACT,
3150
3181
  major,