@100mslive/roomkit-react 0.2.6 → 0.2.7-alpha.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.
- package/README.md +30 -33
- package/dist/{HLSView-A5DVXKEU.js → HLSView-4B5MUDFR.js} +2 -2
- package/dist/Prebuilt/common/constants.d.ts +4 -3
- package/dist/Prebuilt/components/AudioVideoToggle.d.ts +9 -0
- package/dist/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.d.ts +11 -0
- package/dist/Prebuilt/components/hooks/useAudioOutputTest.d.ts +8 -0
- package/dist/{chunk-KUVM2TEZ.js → chunk-KST24BRA.js} +4227 -3807
- package/dist/chunk-KST24BRA.js.map +7 -0
- package/dist/index.cjs.js +5518 -5092
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +1 -1
- package/dist/meta.cjs.json +666 -597
- package/dist/meta.esbuild.json +673 -604
- package/package.json +7 -6
- package/src/Prebuilt/common/constants.ts +7 -4
- package/src/Prebuilt/components/AppData/useUISettings.js +1 -1
- package/src/Prebuilt/components/AudioVideoToggle.tsx +308 -0
- package/src/Prebuilt/components/Footer/RoleOptions.tsx +1 -0
- package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.tsx +159 -0
- package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +0 -2
- package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +13 -2
- package/src/Prebuilt/components/Notifications/Notifications.tsx +1 -18
- package/src/Prebuilt/components/Settings/DeviceSettings.jsx +10 -17
- package/src/Prebuilt/components/hooks/useAudioOutputTest.tsx +20 -0
- package/dist/chunk-KUVM2TEZ.js.map +0 -7
- package/src/Prebuilt/components/AudioVideoToggle.jsx +0 -171
- package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.jsx +0 -121
- /package/dist/{HLSView-A5DVXKEU.js.map → HLSView-4B5MUDFR.js.map} +0 -0
package/package.json
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
"prebuilt",
|
11
11
|
"roomkit"
|
12
12
|
],
|
13
|
-
"version": "0.2.
|
13
|
+
"version": "0.2.7-alpha.1",
|
14
14
|
"author": "100ms",
|
15
15
|
"license": "MIT",
|
16
16
|
"repository": {
|
@@ -82,10 +82,11 @@
|
|
82
82
|
"react": ">=17.0.2 <19.0.0"
|
83
83
|
},
|
84
84
|
"dependencies": {
|
85
|
-
"@100mslive/hls-player": "0.2.
|
86
|
-
"@100mslive/hms-
|
87
|
-
"@100mslive/
|
88
|
-
"@100mslive/react-
|
85
|
+
"@100mslive/hls-player": "0.2.7-alpha.1",
|
86
|
+
"@100mslive/hms-noise-cancellation": "0.0.0-alpha.1",
|
87
|
+
"@100mslive/hms-virtual-background": "1.12.7-alpha.1",
|
88
|
+
"@100mslive/react-icons": "0.9.7-alpha.1",
|
89
|
+
"@100mslive/react-sdk": "0.9.7-alpha.1",
|
89
90
|
"@100mslive/types-prebuilt": "0.12.6",
|
90
91
|
"@emoji-mart/data": "^1.0.6",
|
91
92
|
"@emoji-mart/react": "^1.0.1",
|
@@ -120,5 +121,5 @@
|
|
120
121
|
"uuid": "^8.3.2",
|
121
122
|
"worker-timers": "^7.0.40"
|
122
123
|
},
|
123
|
-
"gitHead": "
|
124
|
+
"gitHead": "d9ed012d59612c39bc68dc427b5749405e4eeb38"
|
124
125
|
}
|
@@ -92,11 +92,12 @@ export const HLS_TIMED_METADATA_DOC_URL =
|
|
92
92
|
|
93
93
|
export const REMOTE_STOP_SCREENSHARE_TYPE = 'REMOTE_STOP_SCREENSHARE';
|
94
94
|
|
95
|
-
|
96
|
-
export const
|
97
|
-
export const
|
95
|
+
// mweb could have browser name as "Mobile Chrome" or "Mobile Firefox"
|
96
|
+
export const isChrome = parsedUserAgent.getBrowser()?.name?.toLowerCase().includes('chrome');
|
97
|
+
export const isFirefox = parsedUserAgent.getBrowser()?.name?.toLowerCase().includes('firefox');
|
98
|
+
export const isSafari = parsedUserAgent.getBrowser()?.name?.toLowerCase().includes('safari');
|
98
99
|
export const isIOS = parsedUserAgent.getOS()?.name?.toLowerCase() === 'ios';
|
99
|
-
export const isMacOS = parsedUserAgent.getOS()?.name?.toLowerCase() === '
|
100
|
+
export const isMacOS = parsedUserAgent.getOS()?.name?.toLowerCase() === 'macos';
|
100
101
|
export const isAndroid = parsedUserAgent.getOS()?.name?.toLowerCase() === 'android';
|
101
102
|
export const isIPadOS = false;
|
102
103
|
// typeof navigator !== "undefined" &&
|
@@ -136,3 +137,5 @@ export enum QUESTION_TYPE {
|
|
136
137
|
export const ROLE_CHANGE_DECLINED = 'role_change_declined';
|
137
138
|
|
138
139
|
export const DEFAULT_PORTAL_CONTAINER = '.prebuilt-container';
|
140
|
+
|
141
|
+
export const TEST_AUDIO_URL = 'https://100ms.live/test-audio.wav';
|
@@ -90,7 +90,7 @@ export const usePinnedTrack = () => {
|
|
90
90
|
|
91
91
|
export const useSubscribedNotifications = notificationKey => {
|
92
92
|
const notificationPreference = useHMSStore(selectAppDataByPath(APP_DATA.subscribedNotifications, notificationKey));
|
93
|
-
return notificationPreference;
|
93
|
+
return notificationPreference || {};
|
94
94
|
};
|
95
95
|
|
96
96
|
export const useIsNotificationDisabled = () => {
|
@@ -0,0 +1,308 @@
|
|
1
|
+
import React, { Fragment, useEffect, useState } from 'react';
|
2
|
+
import { HMSKrispPlugin } from '@100mslive/hms-noise-cancellation';
|
3
|
+
import {
|
4
|
+
DeviceType,
|
5
|
+
HMSRoomState,
|
6
|
+
selectIsLocalAudioPluginPresent,
|
7
|
+
selectLocalAudioTrackID,
|
8
|
+
selectLocalVideoTrackID,
|
9
|
+
selectRoomState,
|
10
|
+
selectVideoTrackByID,
|
11
|
+
useAVToggle,
|
12
|
+
useDevices,
|
13
|
+
useHMSActions,
|
14
|
+
useHMSStore,
|
15
|
+
} from '@100mslive/react-sdk';
|
16
|
+
import {
|
17
|
+
AudioLevelIcon,
|
18
|
+
CameraFlipIcon,
|
19
|
+
CheckIcon,
|
20
|
+
MicOffIcon,
|
21
|
+
MicOnIcon,
|
22
|
+
SettingsIcon,
|
23
|
+
SpeakerIcon,
|
24
|
+
VideoOffIcon,
|
25
|
+
VideoOnIcon,
|
26
|
+
} from '@100mslive/react-icons';
|
27
|
+
import { IconButtonWithOptions } from './IconButtonWithOptions/IconButtonWithOptions';
|
28
|
+
// @ts-ignore: No implicit Any
|
29
|
+
import SettingsModal from './Settings/SettingsModal';
|
30
|
+
// @ts-ignore: No implicit Any
|
31
|
+
import { ToastManager } from './Toast/ToastManager';
|
32
|
+
import { Dropdown } from '../../Dropdown';
|
33
|
+
import { Box, Flex } from '../../Layout';
|
34
|
+
import { Switch } from '../../Switch';
|
35
|
+
import { Text } from '../../Text';
|
36
|
+
import { Tooltip } from '../../Tooltip';
|
37
|
+
import IconButton from '../IconButton';
|
38
|
+
import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
39
|
+
import { useAudioOutputTest } from './hooks/useAudioOutputTest';
|
40
|
+
import { isMacOS, TEST_AUDIO_URL } from '../common/constants';
|
41
|
+
|
42
|
+
// const optionsCSS = { fontWeight: '$semiBold', color: '$on_surface_high', w: '100%' };
|
43
|
+
|
44
|
+
export const Options = ({
|
45
|
+
options,
|
46
|
+
selectedDeviceId,
|
47
|
+
onClick,
|
48
|
+
}: {
|
49
|
+
options?: Array<MediaDeviceInfo | InputDeviceInfo>;
|
50
|
+
selectedDeviceId?: string;
|
51
|
+
onClick: (deviceId: string) => Promise<void>;
|
52
|
+
}) => {
|
53
|
+
return (
|
54
|
+
<>
|
55
|
+
{options?.map(option => (
|
56
|
+
<Dropdown.Item
|
57
|
+
key={option.label}
|
58
|
+
css={{
|
59
|
+
backgroundColor: selectedDeviceId === option.deviceId ? '$surface_bright' : '$surface_dim',
|
60
|
+
p: '$4 $8',
|
61
|
+
h: '$15',
|
62
|
+
fontSize: '$xs',
|
63
|
+
justifyContent: 'space-between',
|
64
|
+
}}
|
65
|
+
onClick={() => {
|
66
|
+
onClick(option.deviceId);
|
67
|
+
}}
|
68
|
+
>
|
69
|
+
{option.label}
|
70
|
+
{selectedDeviceId === option.deviceId ? <CheckIcon width={16} height={16} /> : null}
|
71
|
+
</Dropdown.Item>
|
72
|
+
))}
|
73
|
+
</>
|
74
|
+
);
|
75
|
+
};
|
76
|
+
|
77
|
+
const OptionLabel = ({ children, icon }: { children: React.ReactNode; icon: React.ReactNode }) => {
|
78
|
+
return (
|
79
|
+
<Dropdown.Label
|
80
|
+
css={{
|
81
|
+
h: '$16',
|
82
|
+
p: '$4 $8',
|
83
|
+
color: '$on_surface_medium',
|
84
|
+
bg: 'transparent',
|
85
|
+
fontSize: '$xs',
|
86
|
+
gap: '$4',
|
87
|
+
alignItems: 'center',
|
88
|
+
}}
|
89
|
+
>
|
90
|
+
<Flex css={{ alignItems: 'center', justifyContent: 'center', '& svg': { size: '$8' } }}>{icon}</Flex> {children}
|
91
|
+
</Dropdown.Label>
|
92
|
+
);
|
93
|
+
};
|
94
|
+
|
95
|
+
const plugin = new HMSKrispPlugin();
|
96
|
+
const NoiseCancellation = () => {
|
97
|
+
const localPeerAudioTrackID = useHMSStore(selectLocalAudioTrackID);
|
98
|
+
const isPluginAdded = useHMSStore(selectIsLocalAudioPluginPresent(plugin.getName()));
|
99
|
+
const [active, setActive] = useState(isPluginAdded);
|
100
|
+
const [inProgress, setInProgress] = useState(false);
|
101
|
+
const actions = useHMSActions();
|
102
|
+
|
103
|
+
useEffect(() => {
|
104
|
+
(async () => {
|
105
|
+
setInProgress(true);
|
106
|
+
if (active && !isPluginAdded) {
|
107
|
+
await actions.addPluginToAudioTrack(plugin);
|
108
|
+
}
|
109
|
+
if (!active && isPluginAdded) {
|
110
|
+
await actions.removePluginFromAudioTrack(plugin);
|
111
|
+
}
|
112
|
+
setInProgress(false);
|
113
|
+
})();
|
114
|
+
}, [actions, active, isPluginAdded]);
|
115
|
+
|
116
|
+
if (!plugin.isSupported() || !localPeerAudioTrackID) {
|
117
|
+
return null;
|
118
|
+
}
|
119
|
+
|
120
|
+
return (
|
121
|
+
<Dropdown.Item
|
122
|
+
css={{
|
123
|
+
p: '$4 $8',
|
124
|
+
h: '$15',
|
125
|
+
fontSize: '$xs',
|
126
|
+
justifyContent: 'space-between',
|
127
|
+
}}
|
128
|
+
onClick={e => {
|
129
|
+
e.preventDefault();
|
130
|
+
setActive(value => !value);
|
131
|
+
}}
|
132
|
+
>
|
133
|
+
<Text css={{ display: 'flex', alignItems: 'center', gap: '$2', fontSize: '$xs', '& svg': { size: '$8' } }}>
|
134
|
+
<AudioLevelIcon />
|
135
|
+
Reduce Noise
|
136
|
+
</Text>
|
137
|
+
<Switch
|
138
|
+
id="noise_cancellation"
|
139
|
+
checked={active}
|
140
|
+
disabled={inProgress}
|
141
|
+
onClick={e => e.stopPropagation()}
|
142
|
+
onCheckedChange={value => {
|
143
|
+
setActive(value);
|
144
|
+
}}
|
145
|
+
/>
|
146
|
+
</Dropdown.Item>
|
147
|
+
);
|
148
|
+
};
|
149
|
+
|
150
|
+
const AudioOutputLabel = ({ deviceId }: { deviceId: string }) => {
|
151
|
+
const { playing, setPlaying, audioRef } = useAudioOutputTest({ deviceId });
|
152
|
+
return (
|
153
|
+
<OptionLabel icon={<SpeakerIcon />}>
|
154
|
+
<Box css={{ flex: '1 1 0' }}>Speakers</Box>
|
155
|
+
<Text
|
156
|
+
variant="xs"
|
157
|
+
css={{ color: '$primary_bright', '&:hover': { cursor: 'pointer' } }}
|
158
|
+
onClick={async () => {
|
159
|
+
if (playing) {
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
await audioRef.current?.play();
|
163
|
+
}}
|
164
|
+
>
|
165
|
+
<audio
|
166
|
+
ref={audioRef}
|
167
|
+
src={TEST_AUDIO_URL}
|
168
|
+
onEnded={() => setPlaying(false)}
|
169
|
+
onPlay={() => setPlaying(true)}
|
170
|
+
style={{ display: 'none' }}
|
171
|
+
/>
|
172
|
+
{playing ? 'Playing Sound...' : 'Play Test Sound'}
|
173
|
+
</Text>
|
174
|
+
</OptionLabel>
|
175
|
+
);
|
176
|
+
};
|
177
|
+
|
178
|
+
const AudioSettings = ({ onClick }: { onClick: () => void }) => {
|
179
|
+
return (
|
180
|
+
<>
|
181
|
+
<Dropdown.Item
|
182
|
+
css={{
|
183
|
+
backgroundColor: '$surface_dim',
|
184
|
+
p: '$4 $8',
|
185
|
+
h: '$15',
|
186
|
+
alignItems: 'center',
|
187
|
+
gap: '$2',
|
188
|
+
fontSize: '$xs',
|
189
|
+
'& svg': { size: '$8' },
|
190
|
+
}}
|
191
|
+
onClick={onClick}
|
192
|
+
>
|
193
|
+
<SettingsIcon /> Audio Settings
|
194
|
+
</Dropdown.Item>
|
195
|
+
</>
|
196
|
+
);
|
197
|
+
};
|
198
|
+
|
199
|
+
export const AudioVideoToggle = ({ hideOptions = false }) => {
|
200
|
+
const { allDevices, selectedDeviceIDs, updateDevice } = useDevices();
|
201
|
+
const { videoInput, audioInput, audioOutput } = allDevices;
|
202
|
+
const { isLocalVideoEnabled, isLocalAudioEnabled, toggleAudio, toggleVideo } = useAVToggle();
|
203
|
+
const actions = useHMSActions();
|
204
|
+
const videoTrackId = useHMSStore(selectLocalVideoTrackID);
|
205
|
+
const localVideoTrack = useHMSStore(selectVideoTrackByID(videoTrackId));
|
206
|
+
const roomState = useHMSStore(selectRoomState);
|
207
|
+
const hasAudioDevices = Number(audioInput?.length) > 0;
|
208
|
+
const hasVideoDevices = Number(videoInput?.length) > 0;
|
209
|
+
const shouldShowAudioOutput = 'setSinkId' in HTMLMediaElement.prototype && Number(audioOutput?.length) > 0;
|
210
|
+
const { screenType } = useRoomLayoutConferencingScreen();
|
211
|
+
const [showSettings, setShowSettings] = useState(false);
|
212
|
+
|
213
|
+
if (!toggleAudio && !toggleVideo) {
|
214
|
+
return null;
|
215
|
+
}
|
216
|
+
|
217
|
+
return (
|
218
|
+
<Fragment>
|
219
|
+
{toggleAudio ? (
|
220
|
+
<IconButtonWithOptions
|
221
|
+
disabled={!toggleAudio}
|
222
|
+
hideOptions={hideOptions || !hasAudioDevices}
|
223
|
+
onDisabledClick={toggleAudio}
|
224
|
+
tooltipMessage={`Turn ${isLocalAudioEnabled ? 'off' : 'on'} audio (${isMacOS ? '⌘' : 'ctrl'} + d)`}
|
225
|
+
icon={
|
226
|
+
!isLocalAudioEnabled ? <MicOffIcon data-testid="audio_off_btn" /> : <MicOnIcon data-testid="audio_on_btn" />
|
227
|
+
}
|
228
|
+
active={isLocalAudioEnabled}
|
229
|
+
onClick={toggleAudio}
|
230
|
+
key="toggleAudio"
|
231
|
+
>
|
232
|
+
<Dropdown.Group>
|
233
|
+
<OptionLabel icon={<MicOnIcon />}>{!shouldShowAudioOutput ? 'Audio' : 'Microphone'}</OptionLabel>
|
234
|
+
<Options
|
235
|
+
options={audioInput}
|
236
|
+
selectedDeviceId={selectedDeviceIDs.audioInput}
|
237
|
+
onClick={deviceId => updateDevice({ deviceId, deviceType: DeviceType.audioInput })}
|
238
|
+
/>
|
239
|
+
</Dropdown.Group>
|
240
|
+
<Dropdown.ItemSeparator css={{ mx: 0 }} />
|
241
|
+
{shouldShowAudioOutput && (
|
242
|
+
<>
|
243
|
+
<AudioOutputLabel deviceId={selectedDeviceIDs.audioOutput || ''} />
|
244
|
+
<Dropdown.Group>
|
245
|
+
<Options
|
246
|
+
options={audioOutput}
|
247
|
+
selectedDeviceId={selectedDeviceIDs.audioOutput}
|
248
|
+
onClick={deviceId => updateDevice({ deviceId, deviceType: DeviceType.audioOutput })}
|
249
|
+
/>
|
250
|
+
</Dropdown.Group>
|
251
|
+
</>
|
252
|
+
)}
|
253
|
+
<Dropdown.ItemSeparator css={{ mx: 0 }} />
|
254
|
+
<NoiseCancellation />
|
255
|
+
<Dropdown.ItemSeparator css={{ mx: 0 }} />
|
256
|
+
<AudioSettings onClick={() => setShowSettings(true)} />
|
257
|
+
</IconButtonWithOptions>
|
258
|
+
) : null}
|
259
|
+
|
260
|
+
{toggleVideo ? (
|
261
|
+
<IconButtonWithOptions
|
262
|
+
disabled={!toggleVideo}
|
263
|
+
hideOptions={hideOptions || !hasVideoDevices}
|
264
|
+
onDisabledClick={toggleVideo}
|
265
|
+
tooltipMessage={`Turn ${isLocalVideoEnabled ? 'off' : 'on'} video (${isMacOS ? '⌘' : 'ctrl'} + e)`}
|
266
|
+
icon={
|
267
|
+
!isLocalVideoEnabled ? (
|
268
|
+
<VideoOffIcon data-testid="video_off_btn" />
|
269
|
+
) : (
|
270
|
+
<VideoOnIcon data-testid="video_on_btn" />
|
271
|
+
)
|
272
|
+
}
|
273
|
+
key="toggleVideo"
|
274
|
+
active={isLocalVideoEnabled}
|
275
|
+
onClick={toggleVideo}
|
276
|
+
>
|
277
|
+
<Options
|
278
|
+
options={videoInput}
|
279
|
+
selectedDeviceId={selectedDeviceIDs.videoInput}
|
280
|
+
onClick={deviceId => updateDevice({ deviceId, deviceType: DeviceType.videoInput })}
|
281
|
+
/>
|
282
|
+
</IconButtonWithOptions>
|
283
|
+
) : null}
|
284
|
+
|
285
|
+
{localVideoTrack?.facingMode && roomState === HMSRoomState.Preview ? (
|
286
|
+
<Tooltip title="Switch Camera" key="switchCamera">
|
287
|
+
<IconButton
|
288
|
+
onClick={async () => {
|
289
|
+
try {
|
290
|
+
await actions.switchCamera();
|
291
|
+
} catch (e) {
|
292
|
+
ToastManager.addToast({
|
293
|
+
title: `Error while flipping camera ${(e as Error).message || ''}`,
|
294
|
+
variant: 'error',
|
295
|
+
});
|
296
|
+
}
|
297
|
+
}}
|
298
|
+
>
|
299
|
+
<CameraFlipIcon />
|
300
|
+
</IconButton>
|
301
|
+
</Tooltip>
|
302
|
+
) : null}
|
303
|
+
{showSettings && (
|
304
|
+
<SettingsModal open={showSettings} onOpenChange={() => setShowSettings(false)} screenType={screenType} />
|
305
|
+
)}
|
306
|
+
</Fragment>
|
307
|
+
);
|
308
|
+
};
|
@@ -80,6 +80,7 @@ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList
|
|
80
80
|
return (
|
81
81
|
<Dropdown.Root open={openOptions} onOpenChange={setOpenOptions}>
|
82
82
|
<Dropdown.Trigger
|
83
|
+
data-testid="role_group_options"
|
83
84
|
onClick={e => e.stopPropagation()}
|
84
85
|
className="role_actions"
|
85
86
|
asChild
|
@@ -0,0 +1,159 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { VerticalMenuIcon } from '@100mslive/react-icons';
|
3
|
+
import { Dropdown } from '../../../Dropdown';
|
4
|
+
import { Flex } from '../../../Layout';
|
5
|
+
import { styled } from '../../../Theme';
|
6
|
+
import { Tooltip } from '../../../Tooltip';
|
7
|
+
import IconButton from '../../IconButton';
|
8
|
+
|
9
|
+
const variants = {
|
10
|
+
disabled: {
|
11
|
+
true: {
|
12
|
+
bg: '$surface_brighter',
|
13
|
+
},
|
14
|
+
},
|
15
|
+
active: {
|
16
|
+
false: {
|
17
|
+
bg: '$secondary_dim',
|
18
|
+
},
|
19
|
+
},
|
20
|
+
};
|
21
|
+
|
22
|
+
const IconSection = styled(IconButton, {
|
23
|
+
w: 'unset',
|
24
|
+
h: '$14',
|
25
|
+
p: '$4',
|
26
|
+
r: '$1',
|
27
|
+
bg: '$transparent',
|
28
|
+
borderTopRightRadius: 0,
|
29
|
+
borderColor: '$border_bright',
|
30
|
+
borderBottomRightRadius: 0,
|
31
|
+
position: 'relative',
|
32
|
+
'&:not([disabled]):focus-visible': {
|
33
|
+
zIndex: 1,
|
34
|
+
},
|
35
|
+
'@md': {
|
36
|
+
mx: 0,
|
37
|
+
borderTopRightRadius: '$1',
|
38
|
+
borderBottomRightRadius: '$1',
|
39
|
+
},
|
40
|
+
variants: {
|
41
|
+
...variants,
|
42
|
+
hideOptions: {
|
43
|
+
true: {
|
44
|
+
borderTopRightRadius: '$1',
|
45
|
+
borderBottomRightRadius: '$1',
|
46
|
+
},
|
47
|
+
},
|
48
|
+
},
|
49
|
+
});
|
50
|
+
|
51
|
+
const OptionsSection = styled(IconButton, {
|
52
|
+
w: 'unset',
|
53
|
+
h: '$14',
|
54
|
+
p: '$4 $2',
|
55
|
+
r: '$1',
|
56
|
+
borderTopLeftRadius: 0,
|
57
|
+
borderColor: '$border_bright',
|
58
|
+
borderBottomLeftRadius: 0,
|
59
|
+
borderLeftWidth: 0,
|
60
|
+
position: 'relative',
|
61
|
+
'&:not([disabled]):focus-visible': {
|
62
|
+
zIndex: 1,
|
63
|
+
},
|
64
|
+
'@md': {
|
65
|
+
display: 'none',
|
66
|
+
},
|
67
|
+
variants,
|
68
|
+
});
|
69
|
+
|
70
|
+
const Icon = styled(Flex, {
|
71
|
+
alignItems: 'center',
|
72
|
+
justifyContent: 'center',
|
73
|
+
color: '$on_primary_high',
|
74
|
+
variants: {
|
75
|
+
disabled: {
|
76
|
+
true: {
|
77
|
+
color: '$on_surface_low',
|
78
|
+
},
|
79
|
+
},
|
80
|
+
active: {
|
81
|
+
true: {
|
82
|
+
color: '$on_surface_high',
|
83
|
+
},
|
84
|
+
},
|
85
|
+
},
|
86
|
+
});
|
87
|
+
|
88
|
+
export const IconButtonWithOptions = ({
|
89
|
+
disabled = false,
|
90
|
+
onDisabledClick = () => {
|
91
|
+
return;
|
92
|
+
},
|
93
|
+
tooltipMessage = '',
|
94
|
+
icon,
|
95
|
+
children,
|
96
|
+
active,
|
97
|
+
hideOptions = false,
|
98
|
+
onClick = () => {
|
99
|
+
return;
|
100
|
+
},
|
101
|
+
}: {
|
102
|
+
onClick: () => void;
|
103
|
+
onDisabledClick: () => void;
|
104
|
+
icon: React.ReactNode;
|
105
|
+
children: React.ReactNode;
|
106
|
+
hideOptions?: boolean;
|
107
|
+
active: boolean;
|
108
|
+
disabled?: boolean;
|
109
|
+
tooltipMessage?: string;
|
110
|
+
}) => {
|
111
|
+
const commonProps = { disabled, active };
|
112
|
+
return (
|
113
|
+
<Flex>
|
114
|
+
<IconSection {...commonProps} onClick={onClick} hideOptions={hideOptions}>
|
115
|
+
<Tooltip disabled={!tooltipMessage} title={tooltipMessage}>
|
116
|
+
<Icon {...commonProps}>{icon}</Icon>
|
117
|
+
</Tooltip>
|
118
|
+
</IconSection>
|
119
|
+
{!hideOptions && children ? (
|
120
|
+
<Dropdown.Root>
|
121
|
+
<Dropdown.Trigger
|
122
|
+
asChild
|
123
|
+
// onClick does not work
|
124
|
+
onPointerDown={e => {
|
125
|
+
if (disabled) {
|
126
|
+
e.preventDefault();
|
127
|
+
onDisabledClick();
|
128
|
+
}
|
129
|
+
}}
|
130
|
+
>
|
131
|
+
<OptionsSection {...commonProps}>
|
132
|
+
<Tooltip title="View Options">
|
133
|
+
<Icon {...commonProps}>
|
134
|
+
<VerticalMenuIcon />
|
135
|
+
</Icon>
|
136
|
+
</Tooltip>
|
137
|
+
</OptionsSection>
|
138
|
+
</Dropdown.Trigger>
|
139
|
+
<Dropdown.Content
|
140
|
+
sideOffset={5}
|
141
|
+
alignOffset={-44}
|
142
|
+
align="start"
|
143
|
+
side="top"
|
144
|
+
css={{
|
145
|
+
w: 344,
|
146
|
+
maxWidth: '100%',
|
147
|
+
maxHeight: 'unset',
|
148
|
+
p: 0,
|
149
|
+
border: 'none',
|
150
|
+
bg: '$surface_dim',
|
151
|
+
}}
|
152
|
+
>
|
153
|
+
{children}
|
154
|
+
</Dropdown.Content>
|
155
|
+
</Dropdown.Root>
|
156
|
+
) : null}
|
157
|
+
</Flex>
|
158
|
+
);
|
159
|
+
};
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import React, { Fragment, useState } from 'react';
|
2
2
|
import { ConferencingScreen } from '@100mslive/types-prebuilt';
|
3
|
-
// @ts-ignore: No implicit Any
|
4
3
|
import { selectIsConnectedToRoom, selectPermissions, useHMSStore, useRecordingStreaming } from '@100mslive/react-sdk';
|
5
|
-
// @ts-ignore: No implicit Any
|
6
4
|
import { ExitIcon, StopIcon, VerticalMenuIcon } from '@100mslive/react-icons';
|
7
5
|
import { Dropdown } from '../../../Dropdown';
|
8
6
|
import { Box, Flex } from '../../../Layout';
|
@@ -11,25 +11,36 @@ import {
|
|
11
11
|
// @ts-ignore: No implicit Any
|
12
12
|
import { ToastBatcher } from '../Toast/ToastBatcher';
|
13
13
|
import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
14
|
+
// @ts-ignore: No implicit Any
|
15
|
+
import { useSubscribedNotifications } from '../AppData/useUISettings';
|
14
16
|
|
15
17
|
export const HandRaisedNotifications = () => {
|
16
18
|
const notification = useHMSNotifications(HMSNotificationTypes.HAND_RAISE_CHANGED);
|
17
19
|
const roomState = useHMSStore(selectRoomState);
|
18
20
|
const vanillaStore = useHMSVanillaStore();
|
19
21
|
const { on_stage_exp } = useRoomLayoutConferencingScreen().elements || {};
|
22
|
+
const subscribedNotifications = useSubscribedNotifications();
|
20
23
|
|
21
24
|
useEffect(() => {
|
22
25
|
if (!notification?.data) {
|
23
26
|
return;
|
24
27
|
}
|
25
|
-
|
28
|
+
|
29
|
+
// Don't show toast message in case of local peer.
|
30
|
+
if (
|
31
|
+
roomState !== HMSRoomState.Connected ||
|
32
|
+
notification.data.isLocal ||
|
33
|
+
!on_stage_exp ||
|
34
|
+
!subscribedNotifications.METADATA_UPDATED
|
35
|
+
) {
|
26
36
|
return;
|
27
37
|
}
|
28
38
|
const hasPeerHandRaised = vanillaStore.getState(selectHasPeerHandRaised(notification.data.id));
|
29
39
|
if (hasPeerHandRaised) {
|
30
40
|
ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
|
41
|
+
console.debug('Metadata updated', notification.data);
|
31
42
|
}
|
32
|
-
}, [notification, on_stage_exp, roomState, vanillaStore]);
|
43
|
+
}, [notification, on_stage_exp, roomState, subscribedNotifications.METADATA_UPDATED, vanillaStore]);
|
33
44
|
|
34
45
|
return null;
|
35
46
|
};
|
@@ -16,8 +16,6 @@ import { GroupIcon } from '@100mslive/react-icons';
|
|
16
16
|
import { Box, Button } from '../../..';
|
17
17
|
import { useUpdateRoomLayout } from '../../provider/roomLayoutProvider';
|
18
18
|
// @ts-ignore: No implicit Any
|
19
|
-
import { ToastBatcher } from '../Toast/ToastBatcher';
|
20
|
-
// @ts-ignore: No implicit Any
|
21
19
|
import { ToastManager } from '../Toast/ToastManager';
|
22
20
|
import { AutoplayBlockedModal } from './AutoplayBlockedModal';
|
23
21
|
import { ChatNotifications } from './ChatNotifications';
|
@@ -34,8 +32,6 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
|
|
34
32
|
import { usePollViewToggle } from '../AppData/useSidepane';
|
35
33
|
// @ts-ignore: No implicit Any
|
36
34
|
import { useIsNotificationDisabled, useSubscribedNotifications } from '../AppData/useUISettings';
|
37
|
-
// @ts-ignore: No implicit Any
|
38
|
-
import { getMetadata } from '../../common/utils';
|
39
35
|
import { ROLE_CHANGE_DECLINED } from '../../common/constants';
|
40
36
|
|
41
37
|
const pollToastKey: Record<string, string> = {};
|
@@ -43,7 +39,7 @@ const pollToastKey: Record<string, string> = {};
|
|
43
39
|
export function Notifications() {
|
44
40
|
const localPeerID = useHMSStore(selectLocalPeerID);
|
45
41
|
const notification = useHMSNotifications();
|
46
|
-
const subscribedNotifications = useSubscribedNotifications()
|
42
|
+
const subscribedNotifications = useSubscribedNotifications();
|
47
43
|
const roomState = useHMSStore(selectRoomState);
|
48
44
|
const updateRoomLayoutForRole = useUpdateRoomLayout();
|
49
45
|
const isNotificationDisabled = useIsNotificationDisabled();
|
@@ -65,19 +61,6 @@ export function Notifications() {
|
|
65
61
|
return;
|
66
62
|
}
|
67
63
|
switch (notification.type) {
|
68
|
-
case HMSNotificationTypes.METADATA_UPDATED:
|
69
|
-
if (roomState !== HMSRoomState.Connected) {
|
70
|
-
return;
|
71
|
-
}
|
72
|
-
// Don't show toast message when metadata is updated and raiseHand is false.
|
73
|
-
// Don't show toast message in case of local peer.
|
74
|
-
const metadata = getMetadata(notification.data?.metadata);
|
75
|
-
if (!metadata?.isHandRaised || notification.data.isLocal) return;
|
76
|
-
|
77
|
-
console.debug('Metadata updated', notification.data);
|
78
|
-
if (!subscribedNotifications.METADATA_UPDATED) return;
|
79
|
-
ToastBatcher.showToast({ notification, type: 'RAISE_HAND' });
|
80
|
-
break;
|
81
64
|
case HMSNotificationTypes.NAME_UPDATED:
|
82
65
|
console.log(notification.data.id + ' changed their name to ' + notification.data.name);
|
83
66
|
break;
|
@@ -14,9 +14,10 @@ import { Box, Button, Dropdown, Flex, StyledVideoTile, Text, Video } from '../..
|
|
14
14
|
import { config as cssConfig } from '../../../Theme';
|
15
15
|
import { DialogDropdownTrigger } from '../../primitives/DropdownTrigger';
|
16
16
|
import { useUISettings } from '../AppData/useUISettings';
|
17
|
+
import { useAudioOutputTest } from '../hooks/useAudioOutputTest';
|
17
18
|
import { useDropdownSelection } from '../hooks/useDropdownSelection';
|
18
19
|
import { settingOverflow } from './common';
|
19
|
-
import { UI_SETTINGS } from '../../common/constants';
|
20
|
+
import { TEST_AUDIO_URL, UI_SETTINGS } from '../../common/constants';
|
20
21
|
|
21
22
|
/**
|
22
23
|
* wrap the button on click of whom settings should open, this component will take care of the rest,
|
@@ -182,22 +183,8 @@ const DeviceSelector = ({ title, devices, selection, onChange, icon, children =
|
|
182
183
|
);
|
183
184
|
};
|
184
185
|
|
185
|
-
const TEST_AUDIO_URL = 'https://100ms.live/test-audio.wav';
|
186
|
-
|
187
186
|
const TestAudio = ({ id }) => {
|
188
|
-
const audioRef =
|
189
|
-
const [playing, setPlaying] = useState(false);
|
190
|
-
useEffect(() => {
|
191
|
-
if (audioRef.current && id) {
|
192
|
-
try {
|
193
|
-
if (typeof audioRef.current.setSinkId !== 'undefined') {
|
194
|
-
audioRef.current.setSinkId(id);
|
195
|
-
}
|
196
|
-
} catch (error) {
|
197
|
-
console.log(error);
|
198
|
-
}
|
199
|
-
}
|
200
|
-
}, [id]);
|
187
|
+
const { playing, setPlaying, audioRef } = useAudioOutputTest({ deviceId: id });
|
201
188
|
return (
|
202
189
|
<>
|
203
190
|
<Button
|
@@ -218,7 +205,13 @@ const TestAudio = ({ id }) => {
|
|
218
205
|
speaker
|
219
206
|
</Text>
|
220
207
|
</Button>
|
221
|
-
<audio
|
208
|
+
<audio
|
209
|
+
ref={audioRef}
|
210
|
+
src={TEST_AUDIO_URL}
|
211
|
+
onEnded={() => setPlaying(false)}
|
212
|
+
onPlay={() => setPlaying(true)}
|
213
|
+
css={{ display: 'none' }}
|
214
|
+
/>
|
222
215
|
</>
|
223
216
|
);
|
224
217
|
};
|