@100mslive/roomkit-react 0.3.14-alpha.0 → 0.3.14-alpha.10
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/dist/Diagnostics/AudioTest.d.ts +2 -0
- package/dist/Diagnostics/BrowserTest.d.ts +81 -0
- package/dist/Diagnostics/ConnectivityTest.d.ts +7 -0
- package/dist/Diagnostics/Diagnostics.d.ts +2 -0
- package/dist/Diagnostics/VideoTest.d.ts +2 -0
- package/dist/Diagnostics/components.d.ts +18 -0
- package/dist/Diagnostics/hms.d.ts +9 -0
- package/dist/Diagnostics/index.d.ts +1 -0
- package/dist/{HLSView-USRUP6VG.js → HLSView-PL2BEA32.js} +2 -2
- package/dist/{HLSView-7LHIA6HH.css → HLSView-TAAU7UCF.css} +3 -3
- package/dist/{HLSView-7LHIA6HH.css.map → HLSView-TAAU7UCF.css.map} +1 -1
- package/dist/Prebuilt/App.d.ts +1 -0
- package/dist/Prebuilt/components/Notifications/PermissionErrorModal.d.ts +5 -1
- package/dist/Stats/index.d.ts +1 -0
- package/dist/{chunk-DYDYPNYY.js → chunk-EKH2S2VL.js} +13835 -2768
- package/dist/chunk-EKH2S2VL.js.map +7 -0
- package/dist/index.cjs.css +2 -2
- package/dist/index.cjs.css.map +1 -1
- package/dist/index.cjs.js +14870 -3708
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -1
- package/dist/meta.cjs.json +972 -197
- package/dist/meta.esbuild.json +984 -205
- package/package.json +7 -7
- package/src/Diagnostics/AudioTest.tsx +176 -0
- package/src/Diagnostics/BrowserTest.tsx +139 -0
- package/src/Diagnostics/ConnectivityTest.tsx +359 -0
- package/src/Diagnostics/DeviceSelector.jsx +71 -0
- package/src/Diagnostics/Diagnostics.tsx +134 -0
- package/src/Diagnostics/VideoTest.tsx +68 -0
- package/src/Diagnostics/components.tsx +96 -0
- package/src/Diagnostics/hms.ts +9 -0
- package/src/Diagnostics/index.ts +1 -0
- package/src/Prebuilt/App.tsx +3 -0
- package/src/Prebuilt/components/Chat/ChatFooter.tsx +20 -3
- package/src/Prebuilt/components/Header/common.jsx +3 -0
- package/src/Prebuilt/components/Notifications/Notifications.tsx +2 -2
- package/src/Prebuilt/components/Notifications/PermissionErrorModal.tsx +13 -11
- package/src/Prebuilt/components/StatsForNerds.jsx +1 -13
- package/src/Stats/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/dist/chunk-DYDYPNYY.js.map +0 -7
- /package/dist/{HLSView-USRUP6VG.js.map → HLSView-PL2BEA32.js.map} +0 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
import React, { useContext, useState } from 'react';
|
2
|
+
import { ConnectivityCheckResult, ConnectivityState, DiagnosticsRTCStats } from '@100mslive/react-sdk';
|
3
|
+
import { CheckCircleIcon, CrossCircleIcon, LinkIcon } from '@100mslive/react-icons';
|
4
|
+
import { DiagnosticsContext, TestContainer, TestFooter } from './components';
|
5
|
+
import { Button } from '../Button';
|
6
|
+
import { Box, Flex } from '../Layout';
|
7
|
+
import { Loading } from '../Loading';
|
8
|
+
import { formatBytes } from '../Stats';
|
9
|
+
import { Text } from '../Text';
|
10
|
+
import { hmsDiagnostics } from './hms';
|
11
|
+
|
12
|
+
const Regions = {
|
13
|
+
in: 'India',
|
14
|
+
eu: 'Europe',
|
15
|
+
us: 'United States',
|
16
|
+
};
|
17
|
+
|
18
|
+
const ConnectivityStateMessage = {
|
19
|
+
[ConnectivityState.STARTING]: 'Fetching Init',
|
20
|
+
[ConnectivityState.INIT_FETCHED]: 'Connecting to signal server',
|
21
|
+
[ConnectivityState.SIGNAL_CONNECTED]: 'Establishing ICE connection',
|
22
|
+
[ConnectivityState.ICE_ESTABLISHED]: 'Capturing Media',
|
23
|
+
[ConnectivityState.MEDIA_CAPTURED]: 'Publishing Media',
|
24
|
+
[ConnectivityState.MEDIA_PUBLISHED]: 'Finishing Up',
|
25
|
+
[ConnectivityState.COMPLETED]: 'Completed',
|
26
|
+
};
|
27
|
+
|
28
|
+
export const ConnectivityTestStepResult = ({
|
29
|
+
title,
|
30
|
+
success,
|
31
|
+
children,
|
32
|
+
}: {
|
33
|
+
title: string;
|
34
|
+
success?: boolean;
|
35
|
+
children: React.ReactNode;
|
36
|
+
}) => {
|
37
|
+
return (
|
38
|
+
<Box css={{ my: '$10', p: '$10', r: '$1', bg: '$surface_bright' }}>
|
39
|
+
<Text css={{ c: '$on_primary_medium', mb: '$6' }}>{title}</Text>
|
40
|
+
{success ? (
|
41
|
+
<Flex>
|
42
|
+
<Text css={{ c: '$alert_success' }}>
|
43
|
+
<CheckCircleIcon width="1.5rem" height="1.5rem" />
|
44
|
+
</Text>
|
45
|
+
<Text variant="lg" css={{ ml: '$4' }}>
|
46
|
+
Connected
|
47
|
+
</Text>
|
48
|
+
</Flex>
|
49
|
+
) : (
|
50
|
+
<Flex>
|
51
|
+
<Text css={{ c: '$alert_error_bright' }}>
|
52
|
+
<CrossCircleIcon width="1.5rem" height="1.5rem" />
|
53
|
+
</Text>
|
54
|
+
<Text variant="lg" css={{ ml: '$4' }}>
|
55
|
+
Failed
|
56
|
+
</Text>
|
57
|
+
</Flex>
|
58
|
+
)}
|
59
|
+
<Box>{children}</Box>
|
60
|
+
</Box>
|
61
|
+
);
|
62
|
+
};
|
63
|
+
|
64
|
+
const DetailedInfo = ({
|
65
|
+
title,
|
66
|
+
value,
|
67
|
+
Icon,
|
68
|
+
}: {
|
69
|
+
title: string;
|
70
|
+
value: string;
|
71
|
+
Icon?: (props: React.SVGProps<SVGSVGElement>) => React.JSX.Element;
|
72
|
+
}) => {
|
73
|
+
return (
|
74
|
+
<Box css={{ flex: '50%', mt: '$6' }}>
|
75
|
+
<Text variant="caption" css={{ fontWeight: '$semiBold', c: '$on_primary_medium' }}>
|
76
|
+
{title}
|
77
|
+
</Text>
|
78
|
+
<Flex css={{ mt: '$xs', alignItems: 'flex-start' }}>
|
79
|
+
{Icon && (
|
80
|
+
<Text css={{ mr: '$4' }}>
|
81
|
+
<Icon width="1rem" height="1rem" />
|
82
|
+
</Text>
|
83
|
+
)}
|
84
|
+
<Text variant="caption">{value}</Text>
|
85
|
+
</Flex>
|
86
|
+
</Box>
|
87
|
+
);
|
88
|
+
};
|
89
|
+
|
90
|
+
const MediaServerResult = ({ result }: { result?: ConnectivityCheckResult['mediaServerReport'] }) => {
|
91
|
+
return (
|
92
|
+
<ConnectivityTestStepResult
|
93
|
+
title="Media server connection test"
|
94
|
+
success={result?.isPublishICEConnected && result.isSubscribeICEConnected}
|
95
|
+
>
|
96
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
97
|
+
<DetailedInfo
|
98
|
+
title="Media Captured"
|
99
|
+
value={result?.stats?.audio.bytesSent ? 'Yes' : 'No'}
|
100
|
+
Icon={result?.stats?.audio.bytesSent ? CheckCircleIcon : CrossCircleIcon}
|
101
|
+
/>
|
102
|
+
<DetailedInfo
|
103
|
+
title="Media Published"
|
104
|
+
value={result?.stats?.audio.bitrateSent ? 'Yes' : 'No'}
|
105
|
+
Icon={result?.stats?.audio.bytesSent ? CheckCircleIcon : CrossCircleIcon}
|
106
|
+
/>
|
107
|
+
{result?.connectionQualityScore ? (
|
108
|
+
<DetailedInfo
|
109
|
+
title="Connection Quality Score (CQS)"
|
110
|
+
value={`${result.connectionQualityScore.toFixed(2)} (out of 5)`}
|
111
|
+
/>
|
112
|
+
) : null}
|
113
|
+
</Flex>
|
114
|
+
</ConnectivityTestStepResult>
|
115
|
+
);
|
116
|
+
};
|
117
|
+
|
118
|
+
const SignallingResult = ({ result }: { result?: ConnectivityCheckResult['signallingReport'] }) => {
|
119
|
+
return (
|
120
|
+
<ConnectivityTestStepResult title="Signalling server connection test" success={result?.isConnected}>
|
121
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
122
|
+
<DetailedInfo
|
123
|
+
title="Signalling Gateway"
|
124
|
+
value={result?.isConnected ? 'Reachable' : 'Unreachable'}
|
125
|
+
Icon={result?.isConnected ? CheckCircleIcon : CrossCircleIcon}
|
126
|
+
/>
|
127
|
+
<DetailedInfo title="Websocket URL" value={result?.websocketUrl || 'N/A'} Icon={LinkIcon} />
|
128
|
+
</Flex>
|
129
|
+
</ConnectivityTestStepResult>
|
130
|
+
);
|
131
|
+
};
|
132
|
+
|
133
|
+
const AudioStats = ({ stats }: { stats: DiagnosticsRTCStats | undefined }) => {
|
134
|
+
return (
|
135
|
+
<ConnectivityTestStepResult title="Audio" success={!!stats?.bytesSent}>
|
136
|
+
{stats && (
|
137
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
138
|
+
<DetailedInfo title="Bytes Sent" value={formatBytes(stats.bytesSent)} />
|
139
|
+
<DetailedInfo title="Bytes Received" value={formatBytes(stats.bytesReceived)} />
|
140
|
+
<DetailedInfo title="Packets Received" value={stats.packetsReceived.toString()} />
|
141
|
+
<DetailedInfo title="Packets Lost" value={stats.packetsLost.toString()} />
|
142
|
+
<DetailedInfo title="Bitrate Sent" value={formatBytes(stats.bitrateSent, 'b/s')} />
|
143
|
+
<DetailedInfo title="Bitrate Received" value={formatBytes(stats.bitrateReceived, 'b/s')} />
|
144
|
+
<DetailedInfo title="Round Trip Time" value={`${stats.roundTripTime} ms`} />
|
145
|
+
</Flex>
|
146
|
+
)}
|
147
|
+
</ConnectivityTestStepResult>
|
148
|
+
);
|
149
|
+
};
|
150
|
+
|
151
|
+
const VideoStats = ({ stats }: { stats: DiagnosticsRTCStats | undefined }) => {
|
152
|
+
return (
|
153
|
+
<ConnectivityTestStepResult title="Video" success={!!stats?.bytesSent}>
|
154
|
+
{stats && (
|
155
|
+
<Flex css={{ flexWrap: 'wrap' }}>
|
156
|
+
<DetailedInfo title="Bytes Sent" value={formatBytes(stats.bytesSent)} />
|
157
|
+
<DetailedInfo title="Bytes Received" value={formatBytes(stats.bytesReceived)} />
|
158
|
+
<DetailedInfo title="Packets Received" value={stats.packetsReceived.toString()} />
|
159
|
+
<DetailedInfo title="Packets Lost" value={stats.packetsLost.toString()} />
|
160
|
+
<DetailedInfo title="Bitrate Sent" value={formatBytes(stats.bitrateSent, 'b/s')} />
|
161
|
+
<DetailedInfo title="Bitrate Received" value={formatBytes(stats.bitrateReceived, 'b/s')} />
|
162
|
+
<DetailedInfo title="Round Trip Time" value={`${stats.roundTripTime} ms`} />
|
163
|
+
</Flex>
|
164
|
+
)}
|
165
|
+
</ConnectivityTestStepResult>
|
166
|
+
);
|
167
|
+
};
|
168
|
+
|
169
|
+
const Footer = ({
|
170
|
+
error,
|
171
|
+
result,
|
172
|
+
restart,
|
173
|
+
}: {
|
174
|
+
result?: ConnectivityCheckResult;
|
175
|
+
restart: () => void;
|
176
|
+
error?: Error;
|
177
|
+
}) => {
|
178
|
+
return (
|
179
|
+
<TestFooter error={error}>
|
180
|
+
<Flex css={{ gap: '$8', '@lg': { flexDirection: 'column' } }}>
|
181
|
+
<Button variant="standard" onClick={restart}>
|
182
|
+
Restart Test
|
183
|
+
</Button>
|
184
|
+
<Button disabled={!result} onClick={() => result && downloadJson(result, 'hms_diagnostics_results')}>
|
185
|
+
Download Test Report
|
186
|
+
</Button>
|
187
|
+
</Flex>
|
188
|
+
</TestFooter>
|
189
|
+
);
|
190
|
+
};
|
191
|
+
|
192
|
+
const ConnectivityTestReport = ({
|
193
|
+
error,
|
194
|
+
result,
|
195
|
+
progress,
|
196
|
+
startTest,
|
197
|
+
}: {
|
198
|
+
error?: Error;
|
199
|
+
result?: ConnectivityCheckResult;
|
200
|
+
progress?: ConnectivityState;
|
201
|
+
startTest: () => void;
|
202
|
+
}) => {
|
203
|
+
if (error) {
|
204
|
+
return (
|
205
|
+
<>
|
206
|
+
<TestContainer css={{ textAlign: 'center' }}>
|
207
|
+
<Text css={{ c: '$alert_error_default', mb: '$4' }}>
|
208
|
+
<CrossCircleIcon />
|
209
|
+
</Text>
|
210
|
+
<Text variant="h6">Connectivity Test Failed</Text>
|
211
|
+
<Text variant="body2" css={{ c: '$on_primary_medium' }}>
|
212
|
+
{error.message}
|
213
|
+
</Text>
|
214
|
+
</TestContainer>
|
215
|
+
<Footer restart={startTest} error={error} />
|
216
|
+
</>
|
217
|
+
);
|
218
|
+
}
|
219
|
+
|
220
|
+
if (result) {
|
221
|
+
// for debugging and quick view of results
|
222
|
+
console.log(result);
|
223
|
+
return (
|
224
|
+
<>
|
225
|
+
<TestContainer>
|
226
|
+
<Text css={{ c: '$on_primary_medium' }}>Connectivity test has been completed.</Text>
|
227
|
+
<SignallingResult result={result?.signallingReport} />
|
228
|
+
<MediaServerResult result={result?.mediaServerReport} />
|
229
|
+
<AudioStats stats={result?.mediaServerReport?.stats?.audio} />
|
230
|
+
<VideoStats stats={result?.mediaServerReport?.stats?.video} />
|
231
|
+
</TestContainer>
|
232
|
+
<Footer result={result} restart={startTest} error={error} />
|
233
|
+
</>
|
234
|
+
);
|
235
|
+
}
|
236
|
+
|
237
|
+
if (progress !== undefined) {
|
238
|
+
return (
|
239
|
+
<TestContainer css={{ textAlign: 'center' }}>
|
240
|
+
<Text css={{ c: '$primary_bright', textAlign: 'center' }}>
|
241
|
+
<Loading size="3.5rem" color="currentColor" />
|
242
|
+
</Text>
|
243
|
+
<Text variant="h6" css={{ mt: '$8' }}>
|
244
|
+
Checking your connection...
|
245
|
+
</Text>
|
246
|
+
<Text
|
247
|
+
variant="body2"
|
248
|
+
css={{ c: '$on_primary_medium', mt: '$4' }}
|
249
|
+
>{`${ConnectivityStateMessage[progress]}...`}</Text>
|
250
|
+
</TestContainer>
|
251
|
+
);
|
252
|
+
}
|
253
|
+
|
254
|
+
return null;
|
255
|
+
};
|
256
|
+
|
257
|
+
const RegionSelector = ({
|
258
|
+
region,
|
259
|
+
setRegion,
|
260
|
+
startTest,
|
261
|
+
}: {
|
262
|
+
region?: string;
|
263
|
+
startTest?: () => void;
|
264
|
+
setRegion: (region: string) => void;
|
265
|
+
}) => {
|
266
|
+
return (
|
267
|
+
<TestContainer css={{ borderBottom: '1px solid $border_default' }}>
|
268
|
+
<Text variant="body1">Select a region</Text>
|
269
|
+
<Text variant="body2" css={{ c: '$on_secondary_low' }}>
|
270
|
+
Select the closest region for best results
|
271
|
+
</Text>
|
272
|
+
<Flex
|
273
|
+
justify="between"
|
274
|
+
css={{
|
275
|
+
mt: '$md',
|
276
|
+
'@lg': {
|
277
|
+
flexDirection: 'column',
|
278
|
+
gap: '$8',
|
279
|
+
},
|
280
|
+
}}
|
281
|
+
>
|
282
|
+
<Flex
|
283
|
+
css={{
|
284
|
+
gap: '$4',
|
285
|
+
'@lg': {
|
286
|
+
flexDirection: 'column',
|
287
|
+
},
|
288
|
+
}}
|
289
|
+
>
|
290
|
+
{Object.entries(Regions).map(([key, value]) => (
|
291
|
+
<Button
|
292
|
+
key={key}
|
293
|
+
outlined={region !== key}
|
294
|
+
variant={region === key ? 'primary' : 'standard'}
|
295
|
+
css={region === key ? { bg: '$primary_dim' } : {}}
|
296
|
+
onClick={() => setRegion(key)}
|
297
|
+
>
|
298
|
+
{value}
|
299
|
+
</Button>
|
300
|
+
))}
|
301
|
+
</Flex>
|
302
|
+
<Flex css={{ '@lg': { flexDirection: 'column' } }}>
|
303
|
+
<Button variant="primary" onClick={startTest} disabled={!startTest}>
|
304
|
+
{startTest ? 'Start Test' : 'Testing...'}
|
305
|
+
</Button>
|
306
|
+
</Flex>
|
307
|
+
</Flex>
|
308
|
+
</TestContainer>
|
309
|
+
);
|
310
|
+
};
|
311
|
+
|
312
|
+
export const ConnectivityTest = () => {
|
313
|
+
const { setConnectivityTested } = useContext(DiagnosticsContext);
|
314
|
+
const [region, setRegion] = useState<string | undefined>(Object.keys(Regions)[0]);
|
315
|
+
const [error, setError] = useState<Error | undefined>();
|
316
|
+
const [progress, setProgress] = useState<ConnectivityState>();
|
317
|
+
const [result, setResult] = useState<ConnectivityCheckResult | undefined>();
|
318
|
+
|
319
|
+
const startTest = () => {
|
320
|
+
setConnectivityTested(false);
|
321
|
+
setError(undefined);
|
322
|
+
setResult(undefined);
|
323
|
+
hmsDiagnostics
|
324
|
+
.startConnectivityCheck(
|
325
|
+
state => {
|
326
|
+
setProgress(state);
|
327
|
+
},
|
328
|
+
result => {
|
329
|
+
setConnectivityTested(true);
|
330
|
+
setResult(result);
|
331
|
+
},
|
332
|
+
region,
|
333
|
+
)
|
334
|
+
.catch(error => {
|
335
|
+
setError(error);
|
336
|
+
});
|
337
|
+
};
|
338
|
+
|
339
|
+
return (
|
340
|
+
<>
|
341
|
+
<RegionSelector
|
342
|
+
region={region}
|
343
|
+
setRegion={setRegion}
|
344
|
+
startTest={progress === undefined || progress === ConnectivityState.COMPLETED ? startTest : undefined}
|
345
|
+
/>
|
346
|
+
<ConnectivityTestReport error={error} result={result} progress={progress} startTest={startTest} />
|
347
|
+
</>
|
348
|
+
);
|
349
|
+
};
|
350
|
+
|
351
|
+
const downloadJson = (obj: object, fileName: string) => {
|
352
|
+
const a = document.createElement('a');
|
353
|
+
const file = new Blob([JSON.stringify(obj, null, 2)], {
|
354
|
+
type: 'application/json',
|
355
|
+
});
|
356
|
+
a.href = URL.createObjectURL(file);
|
357
|
+
a.download = `${fileName}.json`;
|
358
|
+
a.click();
|
359
|
+
};
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Dropdown } from '../Dropdown';
|
4
|
+
import { Box, Flex } from '../Layout';
|
5
|
+
import { DialogDropdownTrigger } from '../Prebuilt/primitives/DropdownTrigger';
|
6
|
+
import { Text } from '../Text';
|
7
|
+
|
8
|
+
export const DeviceSelector = ({ title, devices, selection, onChange, icon, children = null }) => {
|
9
|
+
const [open, setOpen] = useState(false);
|
10
|
+
const ref = useRef(null);
|
11
|
+
return (
|
12
|
+
<Box css={{ mb: '$6' }}>
|
13
|
+
<Text css={{ mb: '$4' }}>{title}</Text>
|
14
|
+
<Flex
|
15
|
+
align="center"
|
16
|
+
css={{
|
17
|
+
gap: '$4',
|
18
|
+
'@md': {
|
19
|
+
flexDirection: children ? 'column' : 'row',
|
20
|
+
alignItems: children ? 'start' : 'center',
|
21
|
+
},
|
22
|
+
}}
|
23
|
+
>
|
24
|
+
<Dropdown.Root open={open} onOpenChange={setOpen}>
|
25
|
+
<DialogDropdownTrigger
|
26
|
+
ref={ref}
|
27
|
+
icon={icon}
|
28
|
+
title={devices.find(({ deviceId }) => deviceId === selection)?.label || 'Select device from list'}
|
29
|
+
open={open}
|
30
|
+
/>
|
31
|
+
<Dropdown.Portal>
|
32
|
+
<Dropdown.Content
|
33
|
+
align="start"
|
34
|
+
sideOffset={8}
|
35
|
+
css={{
|
36
|
+
w:
|
37
|
+
// @ts-ignore
|
38
|
+
ref.current?.clientWidth,
|
39
|
+
zIndex: 1001,
|
40
|
+
}}
|
41
|
+
>
|
42
|
+
{devices.map(device => {
|
43
|
+
return (
|
44
|
+
<Dropdown.Item
|
45
|
+
key={device.label}
|
46
|
+
onSelect={() => onChange(device.deviceId)}
|
47
|
+
css={{
|
48
|
+
px: '$9',
|
49
|
+
}}
|
50
|
+
>
|
51
|
+
{device.label}
|
52
|
+
</Dropdown.Item>
|
53
|
+
);
|
54
|
+
})}
|
55
|
+
</Dropdown.Content>
|
56
|
+
</Dropdown.Portal>
|
57
|
+
</Dropdown.Root>
|
58
|
+
{children}
|
59
|
+
</Flex>
|
60
|
+
</Box>
|
61
|
+
);
|
62
|
+
};
|
63
|
+
|
64
|
+
DeviceSelector.propTypes = {
|
65
|
+
title: PropTypes.string.isRequired,
|
66
|
+
devices: PropTypes.array.isRequired,
|
67
|
+
selection: PropTypes.string,
|
68
|
+
onChange: PropTypes.func.isRequired,
|
69
|
+
icon: PropTypes.node,
|
70
|
+
children: PropTypes.node,
|
71
|
+
};
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import React, { useContext } from 'react';
|
2
|
+
import { HMSRoomProvider } from '@100mslive/react-sdk';
|
3
|
+
import { CheckCircleIcon, ConnectivityIcon, GlobeIcon, MicOnIcon, VideoOnIcon } from '@100mslive/react-icons';
|
4
|
+
import { DiagnosticsContext, DiagnosticsSteps } from './components';
|
5
|
+
import { Box, Flex } from '../Layout';
|
6
|
+
import { Text } from '../Text';
|
7
|
+
import { HMSThemeProvider } from '../Theme';
|
8
|
+
import { AudioTest } from './AudioTest';
|
9
|
+
import { BrowserTest } from './BrowserTest';
|
10
|
+
import { ConnectivityTest } from './ConnectivityTest';
|
11
|
+
import { hmsActions, hmsNotifications, hmsStats, hmsStore } from './hms';
|
12
|
+
import { VideoTest } from './VideoTest';
|
13
|
+
|
14
|
+
const DiagnosticsStepIcon: Record<string, React.ReactNode> = {
|
15
|
+
video: <VideoOnIcon width="2rem" height="2rem" />,
|
16
|
+
audio: <MicOnIcon width="2rem" height="2rem" />,
|
17
|
+
browser: <GlobeIcon width="2rem" height="2rem" />,
|
18
|
+
connectivity: <ConnectivityIcon width="2rem" height="2rem" />,
|
19
|
+
};
|
20
|
+
|
21
|
+
const Container = ({ children }: { children: React.ReactNode }) => (
|
22
|
+
<Box
|
23
|
+
css={{
|
24
|
+
px: '120px',
|
25
|
+
py: '120px',
|
26
|
+
bg: '$background_dim',
|
27
|
+
lineHeight: '1.5',
|
28
|
+
'-webkit-text-size-adjust': '100%',
|
29
|
+
position: 'relative',
|
30
|
+
minHeight: '100vh',
|
31
|
+
'@lg': {
|
32
|
+
p: '$12',
|
33
|
+
},
|
34
|
+
}}
|
35
|
+
>
|
36
|
+
{children}
|
37
|
+
</Box>
|
38
|
+
);
|
39
|
+
|
40
|
+
const DiagnosticsStepTest = () => {
|
41
|
+
const { activeStep } = useContext(DiagnosticsContext);
|
42
|
+
|
43
|
+
let TestComponent = () => <></>;
|
44
|
+
|
45
|
+
if (activeStep === 'audio') {
|
46
|
+
TestComponent = AudioTest;
|
47
|
+
} else if (activeStep === 'video') {
|
48
|
+
TestComponent = VideoTest;
|
49
|
+
} else if (activeStep === 'browser') {
|
50
|
+
TestComponent = BrowserTest;
|
51
|
+
} else if (activeStep === 'connectivity') {
|
52
|
+
TestComponent = ConnectivityTest;
|
53
|
+
}
|
54
|
+
|
55
|
+
return <TestComponent key={activeStep} />;
|
56
|
+
};
|
57
|
+
|
58
|
+
const DiagnosticsStepHeader = () => {
|
59
|
+
const { activeStep } = useContext(DiagnosticsContext);
|
60
|
+
return (
|
61
|
+
<Flex css={{ py: '$8', px: '$10', alignItems: 'center', borderBottom: '1px solid $border_default' }}>
|
62
|
+
<Text css={{ c: '$primary_bright', mt: '$xs' }}>{DiagnosticsStepIcon[activeStep]}</Text>
|
63
|
+
<Text css={{ fontSize: '$h6', ml: '$9' }}>{DiagnosticsSteps[activeStep]}</Text>
|
64
|
+
</Flex>
|
65
|
+
);
|
66
|
+
};
|
67
|
+
|
68
|
+
const DiagnosticsStep = () => {
|
69
|
+
return (
|
70
|
+
<Box css={{ border: '1px solid $border_default', r: '$1', w: '75%', maxWidth: '65rem', '@lg': { w: '100%' } }}>
|
71
|
+
<DiagnosticsStepHeader />
|
72
|
+
<Box css={{ maxHeight: '55vh', overflowY: 'auto' }}>
|
73
|
+
<DiagnosticsStepTest />
|
74
|
+
</Box>
|
75
|
+
</Box>
|
76
|
+
);
|
77
|
+
};
|
78
|
+
|
79
|
+
const DiagnosticsStepsList = () => {
|
80
|
+
const { activeStep, connectivityTested } = useContext(DiagnosticsContext);
|
81
|
+
|
82
|
+
return (
|
83
|
+
<Box css={{ w: '25%', '@lg': { display: 'none' } }}>
|
84
|
+
{Object.keys(DiagnosticsSteps).map(key => {
|
85
|
+
const keys = Object.keys(DiagnosticsSteps);
|
86
|
+
const activeStepIndex = keys.indexOf(activeStep);
|
87
|
+
const keyIndex = keys.indexOf(key);
|
88
|
+
const isStepCompleted = activeStepIndex > keyIndex || (activeStep === 'connectivity' && connectivityTested);
|
89
|
+
|
90
|
+
let color = '$on_primary_low';
|
91
|
+
if (activeStep === key) {
|
92
|
+
color = '$on_primary_high';
|
93
|
+
}
|
94
|
+
if (isStepCompleted) {
|
95
|
+
color = '$primary_bright';
|
96
|
+
}
|
97
|
+
|
98
|
+
return (
|
99
|
+
<Flex key={key} css={{ mb: '$10', c: color, gap: '$4', alignItems: 'center' }}>
|
100
|
+
{isStepCompleted ? (
|
101
|
+
<CheckCircleIcon width="1rem" height="1rem" />
|
102
|
+
) : (
|
103
|
+
<Text css={{ c: color, fontSize: '1.75rem' }}>•</Text>
|
104
|
+
)}
|
105
|
+
<Text css={{ c: color }}>{DiagnosticsSteps[key]}</Text>
|
106
|
+
</Flex>
|
107
|
+
);
|
108
|
+
})}
|
109
|
+
</Box>
|
110
|
+
);
|
111
|
+
};
|
112
|
+
|
113
|
+
export const Diagnostics = () => {
|
114
|
+
const [activeStep, setActiveStep] = React.useState(Object.keys(DiagnosticsSteps)[0]);
|
115
|
+
const [connectivityTested, setConnectivityTested] = React.useState(false);
|
116
|
+
return (
|
117
|
+
<HMSRoomProvider store={hmsStore} actions={hmsActions} notifications={hmsNotifications} stats={hmsStats}>
|
118
|
+
<HMSThemeProvider themeType="default">
|
119
|
+
<DiagnosticsContext.Provider value={{ activeStep, setActiveStep, connectivityTested, setConnectivityTested }}>
|
120
|
+
<Container>
|
121
|
+
<Text variant="h4">Pre-call Test</Text>
|
122
|
+
<Text variant="md" css={{ c: '$on_primary_medium' }}>
|
123
|
+
Make sure your devices and network are good to go, let's get started.
|
124
|
+
</Text>
|
125
|
+
<Flex css={{ direction: 'column', mt: '$12', justifyItems: 'center' }}>
|
126
|
+
<DiagnosticsStepsList />
|
127
|
+
<DiagnosticsStep />
|
128
|
+
</Flex>
|
129
|
+
</Container>
|
130
|
+
</DiagnosticsContext.Provider>
|
131
|
+
</HMSThemeProvider>
|
132
|
+
</HMSRoomProvider>
|
133
|
+
);
|
134
|
+
};
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import {
|
3
|
+
HMSException,
|
4
|
+
selectDevices,
|
5
|
+
selectLocalMediaSettings,
|
6
|
+
selectLocalVideoTrackID,
|
7
|
+
useHMSStore,
|
8
|
+
} from '@100mslive/react-sdk';
|
9
|
+
import { VideoOnIcon } from '@100mslive/react-icons';
|
10
|
+
import { PermissionErrorModal } from '../Prebuilt/components/Notifications/PermissionErrorModal';
|
11
|
+
import { TestContainer, TestFooter } from './components';
|
12
|
+
import { Flex } from '../Layout';
|
13
|
+
import { Text } from '../Text';
|
14
|
+
import { Video } from '../Video';
|
15
|
+
import { StyledVideoTile } from '../VideoTile';
|
16
|
+
// @ts-ignore: No implicit any
|
17
|
+
import { DeviceSelector } from './DeviceSelector';
|
18
|
+
import { hmsDiagnostics } from './hms';
|
19
|
+
|
20
|
+
export const VideoTest = () => {
|
21
|
+
const allDevices = useHMSStore(selectDevices);
|
22
|
+
const { videoInput } = allDevices;
|
23
|
+
const trackID = useHMSStore(selectLocalVideoTrackID);
|
24
|
+
const sdkSelectedDevices = useHMSStore(selectLocalMediaSettings);
|
25
|
+
const [error, setError] = useState<HMSException | undefined>();
|
26
|
+
|
27
|
+
useEffect(() => {
|
28
|
+
hmsDiagnostics.startCameraCheck().catch(err => setError(err));
|
29
|
+
}, []);
|
30
|
+
|
31
|
+
return (
|
32
|
+
<>
|
33
|
+
<TestContainer css={{ display: 'flex', '@lg': { flexDirection: 'column', alignItems: 'center' } }}>
|
34
|
+
{trackID && (
|
35
|
+
<StyledVideoTile.Container
|
36
|
+
css={{
|
37
|
+
width: '90%',
|
38
|
+
aspectRatio: '16/9',
|
39
|
+
mr: '$10',
|
40
|
+
'@lg': { mr: 0, mb: '$10', aspectRatio: '1/1' },
|
41
|
+
}}
|
42
|
+
>
|
43
|
+
<Video mirror={true} trackId={trackID} />
|
44
|
+
</StyledVideoTile.Container>
|
45
|
+
)}
|
46
|
+
<Flex direction="column" css={{ w: '100%' }}>
|
47
|
+
<Text variant="body2" css={{ c: '$on_primary_medium', mb: '$10' }}>
|
48
|
+
Move in front of your camera to make sure it's working. If you don't see your video, try changing the
|
49
|
+
selected camera. If the camera isn't part of your computer, check your settings to make sure your system
|
50
|
+
recognizes it.
|
51
|
+
</Text>
|
52
|
+
<DeviceSelector
|
53
|
+
title="Video"
|
54
|
+
devices={videoInput || []}
|
55
|
+
icon={<VideoOnIcon />}
|
56
|
+
selection={sdkSelectedDevices.videoInputDeviceId}
|
57
|
+
onChange={async (deviceId: string) => {
|
58
|
+
hmsDiagnostics.stopCameraCheck();
|
59
|
+
hmsDiagnostics.startCameraCheck(deviceId);
|
60
|
+
}}
|
61
|
+
/>
|
62
|
+
</Flex>
|
63
|
+
</TestContainer>
|
64
|
+
<TestFooter error={error} ctaText="Does your video look good?" />
|
65
|
+
<PermissionErrorModal error={error} />
|
66
|
+
</>
|
67
|
+
);
|
68
|
+
};
|