@stream-io/video-react-sdk 1.12.10 → 1.13.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.
- package/CHANGELOG.md +278 -207
- package/dist/index.cjs.js +113 -125
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +114 -126
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/DeviceSettings/DeviceSelector.d.ts +0 -1
- package/dist/src/components/Search/SearchResults.d.ts +1 -1
- package/dist/src/core/hooks/useCalculateHardLimit.d.ts +9 -1
- package/dist/src/hooks/usePersistedDevicePreferences.d.ts +5 -2
- package/package.json +6 -7
- package/src/components/BackgroundFilters/BackgroundFilters.tsx +2 -2
- package/src/components/CallControls/RecordCallButton.tsx +1 -1
- package/src/components/CallControls/ToggleAudioButton.tsx +3 -3
- package/src/components/CallControls/ToggleVideoButton.tsx +2 -2
- package/src/components/DeviceSettings/DeviceSelector.tsx +2 -3
- package/src/components/Search/SearchResults.tsx +3 -3
- package/src/core/components/Video/Video.tsx +2 -2
- package/src/hooks/usePersistedDevicePreferences.ts +180 -124
- package/src/utilities/filter.ts +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -75,135 +75,121 @@ const useFloatingUIPreset = ({ middleware = [], placement, strategy, offset: off
|
|
|
75
75
|
|
|
76
76
|
const defaultDevice = 'default';
|
|
77
77
|
/**
|
|
78
|
-
* This hook will persist the device
|
|
78
|
+
* This hook will apply and persist the device preferences from local storage.
|
|
79
79
|
*
|
|
80
80
|
* @param key the key to use for local storage.
|
|
81
81
|
*/
|
|
82
|
-
const
|
|
83
|
-
const {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
const usePersistedDevicePreferences = (key = '@stream-io/device-preferences') => {
|
|
83
|
+
const { useCameraState, useMicrophoneState, useSpeakerState } = videoReactBindings.useCallStateHooks();
|
|
84
|
+
usePersistedDevicePreference(key, 'camera', useCameraState());
|
|
85
|
+
usePersistedDevicePreference(key, 'microphone', useMicrophoneState());
|
|
86
|
+
usePersistedDevicePreference(key, 'speaker', useSpeakerState());
|
|
87
|
+
};
|
|
88
|
+
const usePersistedDevicePreference = (key, deviceKey, state) => {
|
|
89
|
+
const { useCallCallingState } = videoReactBindings.useCallStateHooks();
|
|
90
|
+
const callingState = useCallCallingState();
|
|
91
|
+
const [applyingState, setApplyingState] = react.useState('idle');
|
|
92
|
+
const manager = state[deviceKey];
|
|
93
|
+
react.useEffect(function apply() {
|
|
94
|
+
if (callingState === videoClient.CallingState.LEFT ||
|
|
95
|
+
!state.devices?.length ||
|
|
96
|
+
applyingState !== 'idle') {
|
|
92
97
|
return;
|
|
93
|
-
|
|
98
|
+
}
|
|
99
|
+
const preferences = parseLocalDevicePreferences(key);
|
|
100
|
+
const preference = preferences[deviceKey];
|
|
101
|
+
setApplyingState('applying');
|
|
102
|
+
if (preference && !manager.state.selectedDevice) {
|
|
103
|
+
selectDevice(manager, [preference].flat(), state.devices)
|
|
104
|
+
.catch((err) => {
|
|
105
|
+
console.warn(`Failed to save ${deviceKey} device preferences`, err);
|
|
106
|
+
})
|
|
107
|
+
.finally(() => setApplyingState('applied'));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
setApplyingState('applied');
|
|
111
|
+
}
|
|
112
|
+
}, [applyingState, callingState, deviceKey, key, manager, state.devices]);
|
|
113
|
+
react.useEffect(function persist() {
|
|
114
|
+
if (callingState === videoClient.CallingState.LEFT ||
|
|
115
|
+
!state.devices?.length ||
|
|
116
|
+
applyingState !== 'applied') {
|
|
94
117
|
return;
|
|
118
|
+
}
|
|
95
119
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
camera: {
|
|
102
|
-
selectedDeviceId: camera.selectedDevice || defaultDevice,
|
|
103
|
-
muted: camera.isMute,
|
|
104
|
-
},
|
|
105
|
-
speaker: {
|
|
106
|
-
selectedDeviceId: speaker.selectedDevice || defaultDevice,
|
|
107
|
-
muted: false,
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
window.localStorage.setItem(key, JSON.stringify(preferences));
|
|
120
|
+
patchLocalDevicePreference(key, deviceKey, {
|
|
121
|
+
devices: state.devices,
|
|
122
|
+
selectedDevice: state.selectedDevice,
|
|
123
|
+
isMute: state.isMute,
|
|
124
|
+
});
|
|
111
125
|
}
|
|
112
126
|
catch (err) {
|
|
113
|
-
console.warn(
|
|
127
|
+
console.warn(`Failed to save ${deviceKey} device preferences`, err);
|
|
114
128
|
}
|
|
115
129
|
}, [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
applyingState,
|
|
131
|
+
callingState,
|
|
132
|
+
deviceKey,
|
|
119
133
|
key,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
shouldPersistRef,
|
|
134
|
+
state.devices,
|
|
135
|
+
state.isMute,
|
|
136
|
+
state.selectedDevice,
|
|
124
137
|
]);
|
|
125
138
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
react.useEffect(() => {
|
|
136
|
-
if (!call)
|
|
137
|
-
return;
|
|
138
|
-
if (call.state.callingState === videoClient.CallingState.LEFT)
|
|
139
|
-
return;
|
|
140
|
-
let cancel = false;
|
|
141
|
-
const apply = async () => {
|
|
142
|
-
const initMic = async (setting) => {
|
|
143
|
-
if (cancel)
|
|
144
|
-
return;
|
|
145
|
-
await call.microphone.select(parseDeviceId(setting.selectedDeviceId));
|
|
146
|
-
if (cancel)
|
|
147
|
-
return;
|
|
148
|
-
if (setting.muted) {
|
|
149
|
-
await call.microphone.disable();
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
await call.microphone.enable();
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
const initCamera = async (setting) => {
|
|
156
|
-
if (cancel)
|
|
157
|
-
return;
|
|
158
|
-
await call.camera.select(parseDeviceId(setting.selectedDeviceId));
|
|
159
|
-
if (cancel)
|
|
160
|
-
return;
|
|
161
|
-
if (setting.muted) {
|
|
162
|
-
await call.camera.disable();
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
await call.camera.enable();
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
const initSpeaker = (setting) => {
|
|
169
|
-
if (cancel)
|
|
170
|
-
return;
|
|
171
|
-
call.speaker.select(parseDeviceId(setting.selectedDeviceId) ?? '');
|
|
172
|
-
};
|
|
173
|
-
let preferences = null;
|
|
174
|
-
try {
|
|
175
|
-
preferences = JSON.parse(window.localStorage.getItem(key));
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
console.warn('Failed to load device preferences', err);
|
|
179
|
-
}
|
|
180
|
-
if (preferences) {
|
|
181
|
-
await initMic(preferences.mic);
|
|
182
|
-
await initCamera(preferences.camera);
|
|
183
|
-
initSpeaker(preferences.speaker);
|
|
139
|
+
const parseLocalDevicePreferences = (key) => {
|
|
140
|
+
const preferencesStr = window.localStorage.getItem(key);
|
|
141
|
+
let preferences = {};
|
|
142
|
+
if (preferencesStr) {
|
|
143
|
+
try {
|
|
144
|
+
preferences = JSON.parse(preferencesStr);
|
|
145
|
+
if (Object.hasOwn(preferences, 'mic')) {
|
|
146
|
+
// for backwards compatibility
|
|
147
|
+
preferences.microphone = preferences.mic;
|
|
184
148
|
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* assume preferences are empty */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return preferences;
|
|
155
|
+
};
|
|
156
|
+
const patchLocalDevicePreference = (key, deviceKey, state) => {
|
|
157
|
+
const preferences = parseLocalDevicePreferences(key);
|
|
158
|
+
const nextPreference = getSelectedDevicePreference(state.devices, state.selectedDevice);
|
|
159
|
+
const preferenceHistory = [preferences[deviceKey] ?? []]
|
|
160
|
+
.flat()
|
|
161
|
+
.filter((p) => p.selectedDeviceId !== nextPreference.selectedDeviceId &&
|
|
162
|
+
(p.selectedDeviceLabel === '' ||
|
|
163
|
+
p.selectedDeviceLabel !== nextPreference.selectedDeviceLabel));
|
|
164
|
+
window.localStorage.setItem(key, JSON.stringify({
|
|
165
|
+
...preferences,
|
|
166
|
+
mic: undefined, // for backwards compatibility
|
|
167
|
+
[deviceKey]: [
|
|
168
|
+
{
|
|
169
|
+
...nextPreference,
|
|
170
|
+
muted: state.isMute,
|
|
171
|
+
},
|
|
172
|
+
...preferenceHistory,
|
|
173
|
+
].slice(0, 3),
|
|
174
|
+
}));
|
|
195
175
|
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
176
|
+
const selectDevice = async (manager, preference, devices) => {
|
|
177
|
+
for (const p of preference) {
|
|
178
|
+
if (p.selectedDeviceId === defaultDevice) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const device = devices.find((d) => d.deviceId === p.selectedDeviceId) ??
|
|
182
|
+
devices.find((d) => d.label === p.selectedDeviceLabel);
|
|
183
|
+
if (device) {
|
|
184
|
+
await manager.select(device.deviceId);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
205
188
|
};
|
|
206
|
-
const
|
|
189
|
+
const getSelectedDevicePreference = (devices, selectedDevice) => ({
|
|
190
|
+
selectedDeviceId: selectedDevice || defaultDevice,
|
|
191
|
+
selectedDeviceLabel: devices?.find((d) => d.deviceId === selectedDevice)?.label ?? '',
|
|
192
|
+
});
|
|
207
193
|
|
|
208
194
|
const SCROLL_THRESHOLD = 10;
|
|
209
195
|
/**
|
|
@@ -673,7 +659,7 @@ const Video$1 = ({ enabled = true, mirror, trackType, participant, className, Vi
|
|
|
673
659
|
}), "data-user-id": userId, "data-session-id": sessionId, ref: (element) => {
|
|
674
660
|
setVideoElement(element);
|
|
675
661
|
refs?.setVideoElement?.(element);
|
|
676
|
-
} })), isPiP && (jsxRuntime.jsx(
|
|
662
|
+
} })), isPiP && PictureInPicturePlaceholder && (jsxRuntime.jsx(PictureInPicturePlaceholder, { style: { position: 'absolute' }, participant: participant })), (hasNoVideoOrInvisible || isVideoPaused) && VideoPlaceholder && (jsxRuntime.jsx(VideoPlaceholder, { style: { position: 'absolute' }, participant: participant, ref: refs?.setVideoPlaceholderElement }))] }));
|
|
677
663
|
};
|
|
678
664
|
Video$1.displayName = 'Video';
|
|
679
665
|
|
|
@@ -838,8 +824,10 @@ const useRenderer = (tfLite) => {
|
|
|
838
824
|
output,
|
|
839
825
|
stop: () => {
|
|
840
826
|
renderer?.dispose();
|
|
841
|
-
|
|
842
|
-
|
|
827
|
+
if (videoRef.current)
|
|
828
|
+
videoRef.current.srcObject = null;
|
|
829
|
+
if (outputStream)
|
|
830
|
+
videoClient.disposeOfMediaStream(outputStream);
|
|
843
831
|
},
|
|
844
832
|
};
|
|
845
833
|
}, [backgroundBlurLevel, backgroundFilter, backgroundImage, tfLite]);
|
|
@@ -1033,7 +1021,7 @@ const RecordCallConfirmationButton = ({ caption, }) => {
|
|
|
1033
1021
|
}
|
|
1034
1022
|
const title = isAwaitingResponse
|
|
1035
1023
|
? t('Waiting for recording to start...')
|
|
1036
|
-
: caption ?? t('Record call');
|
|
1024
|
+
: (caption ?? t('Record call'));
|
|
1037
1025
|
return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
|
|
1038
1026
|
videoClient.OwnCapability.START_RECORD_CALL,
|
|
1039
1027
|
videoClient.OwnCapability.STOP_RECORD_CALL,
|
|
@@ -1284,7 +1272,7 @@ const DeviceSelectorDropdown = (props) => {
|
|
|
1284
1272
|
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))) })] }));
|
|
1285
1273
|
};
|
|
1286
1274
|
const DeviceSelector = (props) => {
|
|
1287
|
-
const { visualType = 'list', icon,
|
|
1275
|
+
const { visualType = 'list', icon, ...rest } = props;
|
|
1288
1276
|
if (visualType === 'list') {
|
|
1289
1277
|
return jsxRuntime.jsx(DeviceSelectorList, { ...rest });
|
|
1290
1278
|
}
|
|
@@ -1339,7 +1327,7 @@ const ToggleAudioPreviewButton = (props) => {
|
|
|
1339
1327
|
const handleClick = createCallControlHandler(props, () => microphone.toggle());
|
|
1340
1328
|
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1341
1329
|
? t('Check your browser audio permissions')
|
|
1342
|
-
: caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission, "data-testid": optimisticIsMute
|
|
1330
|
+
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission, "data-testid": optimisticIsMute
|
|
1343
1331
|
? 'preview-audio-unmute-button'
|
|
1344
1332
|
: 'preview-audio-mute-button', onClick: handleClick, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1345
1333
|
setTooltipDisabled(shown);
|
|
@@ -1365,7 +1353,7 @@ const ToggleAudioPublishingButton = (props) => {
|
|
|
1365
1353
|
? t('You have no permission to share your audio')
|
|
1366
1354
|
: !hasBrowserPermission
|
|
1367
1355
|
? t('Check your browser mic permissions')
|
|
1368
|
-
: caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission || !hasPermission, "data-testid": optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1356
|
+
: (caption ?? t('Mic')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission || !hasPermission, "data-testid": optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1369
1357
|
setTooltipDisabled(shown);
|
|
1370
1358
|
onMenuToggle?.(shown);
|
|
1371
1359
|
}, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (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: "?" }))] }) }) }) }));
|
|
@@ -1380,7 +1368,7 @@ const ToggleVideoPreviewButton = (props) => {
|
|
|
1380
1368
|
const handleClick = createCallControlHandler(props, () => camera.toggle());
|
|
1381
1369
|
return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
|
|
1382
1370
|
? t('Check your browser video permissions')
|
|
1383
|
-
: caption ?? t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optimisticIsMute
|
|
1371
|
+
: (caption ?? t('Video')), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optimisticIsMute
|
|
1384
1372
|
? 'preview-video-unmute-button'
|
|
1385
1373
|
: 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, onMenuToggle: (shown) => {
|
|
1386
1374
|
setTooltipDisabled(shown);
|
|
@@ -1690,7 +1678,7 @@ const SearchInput = ({ exitSearch, isActive, ...rest }) => {
|
|
|
1690
1678
|
}), children: [jsxRuntime.jsx("input", { placeholder: "Search", ...rest, ref: setInputElement }), isActive ? (jsxRuntime.jsx("button", { className: "str-video__search-input__clear-btn", onClick: exitSearch, children: jsxRuntime.jsx("span", { className: "str-video__search-input__icon--active" }) })) : (jsxRuntime.jsx("span", { className: "str-video__search-input__icon" }))] }));
|
|
1691
1679
|
};
|
|
1692
1680
|
|
|
1693
|
-
|
|
1681
|
+
function SearchResults({ EmptySearchResultComponent, LoadingIndicator: LoadingIndicator$1 = LoadingIndicator, searchQueryInProgress, searchResults, SearchResultList, }) {
|
|
1694
1682
|
if (searchQueryInProgress) {
|
|
1695
1683
|
return (jsxRuntime.jsx("div", { className: "str-video__search-results--loading", children: jsxRuntime.jsx(LoadingIndicator$1, {}) }));
|
|
1696
1684
|
}
|
|
@@ -1698,7 +1686,7 @@ const SearchResults = ({ EmptySearchResultComponent, LoadingIndicator: LoadingIn
|
|
|
1698
1686
|
return jsxRuntime.jsx(EmptySearchResultComponent, {});
|
|
1699
1687
|
}
|
|
1700
1688
|
return jsxRuntime.jsx(SearchResultList, { data: searchResults });
|
|
1701
|
-
}
|
|
1689
|
+
}
|
|
1702
1690
|
|
|
1703
1691
|
const useSearch = ({ debounceInterval, searchFn, searchQuery = '', }) => {
|
|
1704
1692
|
const [searchResults, setSearchResults] = react.useState([]);
|
|
@@ -2309,7 +2297,7 @@ function checkConditions(obj, conditions) {
|
|
|
2309
2297
|
for (const key of Object.keys(conditions)) {
|
|
2310
2298
|
const operator = conditions[key];
|
|
2311
2299
|
const maybeOperator = operator && typeof operator === 'object';
|
|
2312
|
-
|
|
2300
|
+
const value = obj[key];
|
|
2313
2301
|
if (maybeOperator && '$eq' in operator) {
|
|
2314
2302
|
const eqOperator = operator;
|
|
2315
2303
|
match && (match = eqOperator.$eq === value);
|
|
@@ -2680,7 +2668,7 @@ const LivestreamPlayer = (props) => {
|
|
|
2680
2668
|
return (jsxRuntime.jsx(StreamCall, { call: call, children: jsxRuntime.jsx(LivestreamLayout, { ...layoutProps }) }));
|
|
2681
2669
|
};
|
|
2682
2670
|
|
|
2683
|
-
const [major, minor, patch] = ("1.
|
|
2671
|
+
const [major, minor, patch] = ("1.13.0").split('.');
|
|
2684
2672
|
videoClient.setSdkInfo({
|
|
2685
2673
|
type: videoClient.SfuModels.SdkType.REACT,
|
|
2686
2674
|
major,
|