@stream-io/video-react-sdk 1.34.2 → 1.35.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.
@@ -1036,6 +1036,41 @@ const AudioVolumeIndicator = () => {
1036
1036
  return (jsxRuntime.jsxs("div", { className: "str-video__audio-volume-indicator", children: [jsxRuntime.jsx(Icon, { icon: isEnabled ? 'mic' : 'mic-off' }), jsxRuntime.jsx("div", { className: "str-video__audio-volume-indicator__bar", children: jsxRuntime.jsx("div", { className: "str-video__audio-volume-indicator__bar-value", style: { transform: `scaleX(${audioLevel / 100})` } }) })] }));
1037
1037
  };
1038
1038
 
1039
+ const LEVEL_BARS = 5;
1040
+ const DeviceLevelIndicator = ({ deviceId }) => {
1041
+ const [audioLevel, setAudioLevel] = react.useState(0);
1042
+ react.useEffect(() => {
1043
+ let cancelled = false;
1044
+ let dispose;
1045
+ navigator.mediaDevices
1046
+ .getUserMedia({
1047
+ audio: { deviceId: { exact: deviceId } },
1048
+ video: false,
1049
+ })
1050
+ .then((mediaStream) => {
1051
+ if (cancelled) {
1052
+ mediaStream.getTracks().forEach((t) => t.stop());
1053
+ return;
1054
+ }
1055
+ dispose = videoClient.createSoundDetector(mediaStream, ({ audioLevel: al }) => setAudioLevel(al), { detectionFrequencyInMs: 80 });
1056
+ })
1057
+ .catch(console.error);
1058
+ return () => {
1059
+ cancelled = true;
1060
+ dispose?.().catch(console.error);
1061
+ };
1062
+ }, [deviceId]);
1063
+ const activeBars = Math.round((audioLevel / 100) * LEVEL_BARS);
1064
+ return (jsxRuntime.jsx("div", { className: "str-video__device-level-indicator", "aria-label": "Audio level", children: Array.from({ length: LEVEL_BARS }, (_, i) => (jsxRuntime.jsx("div", { className: clsx('str-video__device-level-indicator__bar', {
1065
+ 'str-video__device-level-indicator__bar--active': i < activeBars,
1066
+ }) }, i))) }));
1067
+ };
1068
+ const DeviceAudioPreviewItem = ({ device, onSelect, }) => {
1069
+ if (device.deviceId === 'default')
1070
+ return null;
1071
+ return (jsxRuntime.jsxs("label", { className: `str-video__device-settings__option${device.isSelected ? ' str-video__device-settings__option--selected' : ''}`, htmlFor: `audioinput--${device.deviceId}`, children: [jsxRuntime.jsx("input", { type: "radio", name: "audioinput", value: device.deviceId, id: `audioinput--${device.deviceId}`, checked: device.isSelected, onChange: (e) => onSelect(e.target.value) }), device.label, jsxRuntime.jsx(DeviceLevelIndicator, { deviceId: device.deviceId })] }));
1072
+ };
1073
+
1039
1074
  const SelectContext = react.createContext({});
1040
1075
  const Select = (props) => {
1041
1076
  const { children, icon, defaultSelectedLabel, defaultSelectedIndex, handleSelect: handleSelectProp, } = props;
@@ -1127,6 +1162,18 @@ const DeviceSelectorList = (props) => {
1127
1162
  }, name: type, selected: device.isSelected }, device.deviceId));
1128
1163
  }), children] }));
1129
1164
  };
1165
+ const DeviceSelectorPreview = (props) => {
1166
+ const { devices = [], selectedDeviceId, title, onChange, children, PreviewItem, } = props;
1167
+ const { close } = useMenuContext();
1168
+ const { deviceList } = useDeviceList(devices, selectedDeviceId);
1169
+ const onSelect = react.useCallback((deviceId) => {
1170
+ if (deviceId === 'default')
1171
+ return;
1172
+ onChange?.(deviceId);
1173
+ close?.();
1174
+ }, [onChange, close]);
1175
+ return (jsxRuntime.jsxs("div", { className: "str-video__device-settings__device-kind", children: [title && (jsxRuntime.jsx("div", { className: "str-video__device-settings__device-selector-title", children: title })), deviceList.map((device) => (jsxRuntime.jsx(PreviewItem, { device: device, onSelect: onSelect }, device.deviceId))), children] }));
1176
+ };
1130
1177
  const DeviceSelectorDropdown = (props) => {
1131
1178
  const { devices = [], selectedDeviceId, title, onChange, icon } = props;
1132
1179
  const { deviceList, selectedDeviceInfo, selectedIndex } = useDeviceList(devices, selectedDeviceId);
@@ -1139,6 +1186,10 @@ const DeviceSelectorDropdown = (props) => {
1139
1186
  return (jsxRuntime.jsxs("div", { className: "str-video__device-settings__device-kind", children: [jsxRuntime.jsx("div", { className: "str-video__device-settings__device-selector-title", children: title }), jsxRuntime.jsx(DropDownSelect, { icon: icon, defaultSelectedIndex: selectedIndex, defaultSelectedLabel: selectedDeviceInfo.label, handleSelect: handleSelect, children: deviceList.map((device) => (jsxRuntime.jsx(DropDownSelectOption, { icon: icon, label: device.label, selected: device.isSelected }, device.deviceId))) })] }));
1140
1187
  };
1141
1188
  const DeviceSelector = (props) => {
1189
+ if (props.visualType === 'preview') {
1190
+ const { PreviewItem, ...rest } = props;
1191
+ return jsxRuntime.jsx(DeviceSelectorPreview, { ...rest, PreviewItem: PreviewItem });
1192
+ }
1142
1193
  const { visualType = 'list', icon, ...rest } = props;
1143
1194
  if (visualType === 'list') {
1144
1195
  return jsxRuntime.jsx(DeviceSelectorList, { ...rest });
@@ -1156,7 +1207,7 @@ const SpeakerTest = (props) => {
1156
1207
  const audioElementRef = react.useRef(null);
1157
1208
  const [isPlaying, setIsPlaying] = react.useState(false);
1158
1209
  const { t } = videoReactBindings.useI18n();
1159
- const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.34.2"}/assets/piano.mp3`, } = props;
1210
+ const { audioUrl = `https://unpkg.com/${"@stream-io/video-react-sdk"}@${"1.35.0"}/assets/piano.mp3`, } = props;
1160
1211
  // Update audio output device when selection changes
1161
1212
  react.useEffect(() => {
1162
1213
  const audio = audioElementRef.current;
@@ -1199,7 +1250,7 @@ const DeviceSelectorAudioInput = ({ title, visualType, volumeIndicatorVisible =
1199
1250
  const { microphone, selectedDevice, devices } = useMicrophoneState();
1200
1251
  return (jsxRuntime.jsx(DeviceSelector, { devices: devices || [], selectedDeviceId: selectedDevice, type: "audioinput", onChange: async (deviceId) => {
1201
1252
  await microphone.select(deviceId);
1202
- }, title: title, visualType: visualType, icon: "mic", children: volumeIndicatorVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" }), jsxRuntime.jsx(AudioVolumeIndicator, {})] })) }));
1253
+ }, title: title, visualType: visualType, icon: "mic", PreviewItem: DeviceAudioPreviewItem, children: volumeIndicatorVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" }), jsxRuntime.jsx(AudioVolumeIndicator, {})] })) }));
1203
1254
  };
1204
1255
  const DeviceSelectorAudioOutput = ({ title, visualType, speakerTestVisible = true, speakerTestAudioUrl, }) => {
1205
1256
  const { useSpeakerState } = videoReactBindings.useCallStateHooks();
@@ -1211,12 +1262,48 @@ const DeviceSelectorAudioOutput = ({ title, visualType, speakerTestVisible = tru
1211
1262
  }, title: title, visualType: visualType, icon: "speaker", children: speakerTestVisible && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("hr", { className: "str-video__device-settings__separator" }), jsxRuntime.jsx(SpeakerTest, { audioUrl: speakerTestAudioUrl })] })) }));
1212
1263
  };
1213
1264
 
1265
+ const DeviceVideoPreview = ({ deviceId }) => {
1266
+ const videoRef = react.useRef(null);
1267
+ react.useEffect(() => {
1268
+ let cancelled = false;
1269
+ let stream;
1270
+ navigator.mediaDevices
1271
+ .getUserMedia({
1272
+ video: { deviceId: { exact: deviceId } },
1273
+ audio: false,
1274
+ })
1275
+ .then((mediaStream) => {
1276
+ if (cancelled) {
1277
+ mediaStream.getTracks().forEach((t) => t.stop());
1278
+ return;
1279
+ }
1280
+ stream = mediaStream;
1281
+ if (videoRef.current) {
1282
+ videoRef.current.srcObject = mediaStream;
1283
+ }
1284
+ })
1285
+ .catch(console.error);
1286
+ return () => {
1287
+ cancelled = true;
1288
+ stream?.getTracks().forEach((t) => t.stop());
1289
+ };
1290
+ }, [deviceId]);
1291
+ return (jsxRuntime.jsx("div", { className: "str-video__device-video-preview", children: jsxRuntime.jsx("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "str-video__device-video-preview__video" }) }));
1292
+ };
1293
+ const DeviceVideoPreviewItem = ({ device, onSelect, }) => {
1294
+ if (device.deviceId === 'default')
1295
+ return null;
1296
+ return (jsxRuntime.jsxs("button", { type: "button", className: clsx('str-video__device-preview', {
1297
+ 'str-video__device-preview--selected': device.isSelected,
1298
+ }), onClick: () => onSelect(device.deviceId), "aria-pressed": device.isSelected, children: [jsxRuntime.jsx(DeviceVideoPreview, { deviceId: device.deviceId }), jsxRuntime.jsx("span", { className: "str-video__device-preview__label", children: device.label })] }));
1299
+ };
1300
+
1214
1301
  const DeviceSelectorVideo = ({ title, visualType, }) => {
1215
1302
  const { useCameraState } = videoReactBindings.useCallStateHooks();
1216
1303
  const { camera, devices, selectedDevice } = useCameraState();
1217
1304
  return (jsxRuntime.jsx(DeviceSelector, { devices: devices || [], type: "videoinput", selectedDeviceId: selectedDevice, onChange: async (deviceId) => {
1218
1305
  await camera.select(deviceId);
1219
- }, title: title, visualType: visualType, icon: "camera" }));
1306
+ }, title: title, visualType: visualType, icon: "camera", PreviewItem: DeviceVideoPreviewItem }));
1220
1307
  };
1221
1308
 
1222
1309
  react.forwardRef(function ToggleDeviceSettingsMenuButton({ menuShown }, ref) {