added network performance tab
This commit is contained in:
parent
fff932f979
commit
a65e97ee9a
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PeopleAside } from '@custom/shared/components/Aside/PeopleAside';
|
import { NetworkAside } from '@custom/shared/components/Aside';
|
||||||
|
import { PeopleAside } from '@custom/shared/components/Aside';
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
|
|
||||||
export const Asides = () => {
|
export const Asides = () => {
|
||||||
|
|
@ -8,6 +9,7 @@ export const Asides = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PeopleAside />
|
<PeopleAside />
|
||||||
|
<NetworkAside />
|
||||||
{asides.map((AsideComponent) => (
|
{asides.map((AsideComponent) => (
|
||||||
<AsideComponent key={AsideComponent.name} />
|
<AsideComponent key={AsideComponent.name} />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["next/babel"],
|
|
||||||
"plugins": ["inline-react-svg"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import App from '@custom/basic-call/components/App';
|
|
||||||
import { RecordingProvider } from '../../contexts/RecordingProvider';
|
|
||||||
|
|
||||||
// Extend our basic call app component with the recording context
|
|
||||||
export const AppWithRecording = () => (
|
|
||||||
<RecordingProvider>
|
|
||||||
<App />
|
|
||||||
</RecordingProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default AppWithRecording;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { AppWithRecording as default } from './App';
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { TrayButton } from '@custom/shared/components/Tray';
|
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
|
||||||
import { ReactComponent as IconRecord } from '@custom/shared/icons/record-md.svg';
|
|
||||||
|
|
||||||
import {
|
|
||||||
RECORDING_ERROR,
|
|
||||||
RECORDING_RECORDING,
|
|
||||||
RECORDING_SAVED,
|
|
||||||
RECORDING_UPLOADING,
|
|
||||||
useRecording,
|
|
||||||
} from '../../contexts/RecordingProvider';
|
|
||||||
import { RECORDING_MODAL } from '../RecordingModal';
|
|
||||||
|
|
||||||
export const Tray = () => {
|
|
||||||
const { openModal } = useUIState();
|
|
||||||
const { recordingState } = useRecording();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(`⏺️ Recording state: ${recordingState}`);
|
|
||||||
|
|
||||||
if (recordingState === RECORDING_ERROR) {
|
|
||||||
// show error modal here
|
|
||||||
}
|
|
||||||
}, [recordingState]);
|
|
||||||
|
|
||||||
const isRecording = [
|
|
||||||
RECORDING_RECORDING,
|
|
||||||
RECORDING_UPLOADING,
|
|
||||||
RECORDING_SAVED,
|
|
||||||
].includes(recordingState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TrayButton
|
|
||||||
label={isRecording ? 'Recording' : 'Record'}
|
|
||||||
orange={isRecording}
|
|
||||||
onClick={() => openModal(RECORDING_MODAL)}
|
|
||||||
>
|
|
||||||
<IconRecord />
|
|
||||||
</TrayButton>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Tray;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { Tray as default } from './Tray';
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
|
|
||||||
DAILY_DOMAIN=
|
|
||||||
|
|
||||||
# Obtained from https://dashboard.daily.co/developers
|
|
||||||
DAILY_API_KEY=
|
|
||||||
|
|
||||||
# Daily REST API endpoint
|
|
||||||
DAILY_REST_DOMAIN=https://api.daily.co/v1
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
const withPlugins = require('next-compose-plugins');
|
|
||||||
const withTM = require('next-transpile-modules')([
|
|
||||||
'@custom/shared',
|
|
||||||
'@custom/basic-call',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
|
||||||
|
|
||||||
module.exports = withPlugins([withTM], {
|
|
||||||
env: {
|
|
||||||
PROJECT_TITLE: packageJson.description,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@custom/live-fitness",
|
|
||||||
"description": "Live Fitness",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"build": "next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@custom/shared": "*",
|
|
||||||
"@custom/basic-call": "*",
|
|
||||||
"next": "^11.0.0",
|
|
||||||
"pluralize": "^8.0.0",
|
|
||||||
"react": "^17.0.2",
|
|
||||||
"react-dom": "^17.0.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
|
||||||
"next-compose-plugins": "^2.2.1",
|
|
||||||
"next-transpile-modules": "^8.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import App from '@custom/basic-call/pages/_app';
|
|
||||||
import AppWithRecording from '../components/App';
|
|
||||||
|
|
||||||
import { RecordingModal } from '../components/RecordingModal';
|
|
||||||
import Tray from '../components/Tray';
|
|
||||||
|
|
||||||
App.modals = [RecordingModal];
|
|
||||||
App.customAppComponent = <AppWithRecording />;
|
|
||||||
App.customTrayComponent = <Tray />;
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
../../basic-call/pages/api
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import Index from '@custom/basic-call/pages';
|
|
||||||
import getDemoProps from '@custom/shared/lib/demoProps';
|
|
||||||
|
|
||||||
export async function getStaticProps() {
|
|
||||||
const defaultProps = getDemoProps();
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
...defaultProps,
|
|
||||||
forceFetchToken: true,
|
|
||||||
forceOwner: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Index;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
../basic-call/public
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
import React, { useCallback, useMemo, useEffect, useState } from 'react';
|
||||||
|
import { Aside } from '@custom/shared/components/Aside';
|
||||||
|
import { useCallState } from '../../contexts/CallProvider';
|
||||||
|
import { useUIState } from '../../contexts/UIStateProvider';
|
||||||
|
import Capsule from '../Capsule';
|
||||||
|
|
||||||
|
export const NETWORK_ASIDE = 'network';
|
||||||
|
|
||||||
|
const NETWORK_QUALITY_LABELS = {
|
||||||
|
low: 'Low',
|
||||||
|
'very-low': 'Very Low',
|
||||||
|
good: 'Good',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NetworkAside = () => {
|
||||||
|
const { callObject } = useCallState();
|
||||||
|
const { showAside, setShowAside } = useUIState();
|
||||||
|
const [networkStats, setNetworkStats] = useState();
|
||||||
|
|
||||||
|
const updateStats = useCallback(async () => {
|
||||||
|
setNetworkStats(await callObject.getNetworkStats());
|
||||||
|
}, [callObject]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!callObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStats();
|
||||||
|
|
||||||
|
const i = setInterval(updateStats, 2000);
|
||||||
|
|
||||||
|
return () => clearInterval(i);
|
||||||
|
}, [callObject, updateStats]);
|
||||||
|
|
||||||
|
const downloadKbs = useMemo(
|
||||||
|
() =>
|
||||||
|
Math.round(
|
||||||
|
(networkStats?.stats?.latest?.videoRecvBitsPerSecond ?? 0) / 1000
|
||||||
|
),
|
||||||
|
[networkStats]
|
||||||
|
);
|
||||||
|
|
||||||
|
const uploadKbs = useMemo(
|
||||||
|
() =>
|
||||||
|
Math.round(
|
||||||
|
(networkStats?.stats?.latest?.videoSendBitsPerSecond ?? 0) / 1000
|
||||||
|
),
|
||||||
|
[networkStats]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!showAside || showAside !== NETWORK_ASIDE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Aside onClose={() => setShowAside(false)}>
|
||||||
|
<div className="network-aside">
|
||||||
|
{networkStats ? (
|
||||||
|
<>
|
||||||
|
<div className="panel">
|
||||||
|
<h4>Packet Loss:</h4>
|
||||||
|
Your network quality is:
|
||||||
|
<Capsule variant="success">
|
||||||
|
{NETWORK_QUALITY_LABELS[networkStats.threshold]}
|
||||||
|
</Capsule>
|
||||||
|
</div>
|
||||||
|
<div className="panel">
|
||||||
|
<h4>Download rate:</h4>
|
||||||
|
{downloadKbs} kbps
|
||||||
|
</div>
|
||||||
|
<div className="panel">
|
||||||
|
<h4>Upload rate:</h4>
|
||||||
|
{uploadKbs} kbps
|
||||||
|
</div>
|
||||||
|
<div className="note">
|
||||||
|
Download and upload rates reflect bandwidth used by this call.
|
||||||
|
Updated every 2 seconds.
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>Fetching network stats...</>
|
||||||
|
)}
|
||||||
|
<style jsx>{`
|
||||||
|
.panel {
|
||||||
|
background-color: var(--gray-wash);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
margin: var(--spacing-xxs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel h4 {
|
||||||
|
margin: 0 0 var(--spacing-xxs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
margin: var(--spacing-xxs);
|
||||||
|
color: var(--text-mid);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
</Aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetworkAside;
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { Aside } from './Aside';
|
export { Aside } from './Aside';
|
||||||
export { PeopleAside } from './PeopleAside';
|
export { PeopleAside } from './PeopleAside';
|
||||||
|
export { NetworkAside } from './NetworkAside';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
export const Capsule = ({ children, variant }) => (
|
||||||
|
<span className={classNames('capsule', variant)}>
|
||||||
|
{children}
|
||||||
|
<style jsx>{`
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin: 0 6px;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: var(--weight-bold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
|
.capsule.success {
|
||||||
|
background-color: var(--green-default);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.capsule.warning {
|
||||||
|
background-color: var(--secondary-default);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.capsule.error {
|
||||||
|
background-color: var(--red-default);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
Capsule.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Capsule;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { Capsule as default } from './Capsule';
|
||||||
|
|
@ -15,7 +15,7 @@ export const MessageCard = ({
|
||||||
hideBack = false,
|
hideBack = false,
|
||||||
onBack,
|
onBack,
|
||||||
}) => (
|
}) => (
|
||||||
<Card className={error && 'error'}>
|
<Card className={error ? 'error' : ''}>
|
||||||
{header && <CardHeader>{header}</CardHeader>}
|
{header && <CardHeader>{header}</CardHeader>}
|
||||||
{children && <CardBody>{children}</CardBody>}
|
{children && <CardBody>{children}</CardBody>}
|
||||||
{!hideBack && (
|
{!hideBack && (
|
||||||
|
|
@ -23,7 +23,7 @@ export const MessageCard = ({
|
||||||
{onBack ? (
|
{onBack ? (
|
||||||
<Button onClick={() => onBack()}>Go back</Button>
|
<Button onClick={() => onBack()}>Go back</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button href="/">Go back</Button>
|
<Button onClick={() => window.location.reload()}>Go back</Button>
|
||||||
)}
|
)}
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside';
|
||||||
import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
|
import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
|
||||||
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
|
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
|
|
@ -9,6 +10,7 @@ import { ReactComponent as IconCameraOn } from '@custom/shared/icons/camera-on-m
|
||||||
import { ReactComponent as IconLeave } from '@custom/shared/icons/leave-md.svg';
|
import { ReactComponent as IconLeave } from '@custom/shared/icons/leave-md.svg';
|
||||||
import { ReactComponent as IconMicOff } from '@custom/shared/icons/mic-off-md.svg';
|
import { ReactComponent as IconMicOff } from '@custom/shared/icons/mic-off-md.svg';
|
||||||
import { ReactComponent as IconMicOn } from '@custom/shared/icons/mic-on-md.svg';
|
import { ReactComponent as IconMicOn } from '@custom/shared/icons/mic-on-md.svg';
|
||||||
|
import { ReactComponent as IconNetwork } from '@custom/shared/icons/network-md.svg';
|
||||||
import { ReactComponent as IconPeople } from '@custom/shared/icons/people-md.svg';
|
import { ReactComponent as IconPeople } from '@custom/shared/icons/people-md.svg';
|
||||||
import { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg';
|
import { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg';
|
||||||
import { Tray, TrayButton } from './Tray';
|
import { Tray, TrayButton } from './Tray';
|
||||||
|
|
@ -47,7 +49,9 @@ export const BasicTray = () => {
|
||||||
<TrayButton label="Settings" onClick={() => openModal(DEVICE_MODAL)}>
|
<TrayButton label="Settings" onClick={() => openModal(DEVICE_MODAL)}>
|
||||||
<IconSettings />
|
<IconSettings />
|
||||||
</TrayButton>
|
</TrayButton>
|
||||||
|
<TrayButton label="Network" onClick={() => toggleAside(NETWORK_ASIDE)}>
|
||||||
|
<IconNetwork />
|
||||||
|
</TrayButton>
|
||||||
<TrayButton label="People" onClick={() => toggleAside(PEOPLE_ASIDE)}>
|
<TrayButton label="People" onClick={() => toggleAside(PEOPLE_ASIDE)}>
|
||||||
<IconPeople />
|
<IconPeople />
|
||||||
</TrayButton>
|
</TrayButton>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ export const useCallUI = ({
|
||||||
callEnded()
|
callEnded()
|
||||||
) : (
|
) : (
|
||||||
<MessageCard>
|
<MessageCard>
|
||||||
You have left the call. We hope you had fun!
|
You have left the call (either manually or because the room
|
||||||
|
expired). We hope you had fun!
|
||||||
</MessageCard>
|
</MessageCard>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
@ -89,11 +90,7 @@ export const useCallUI = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageCard
|
<MessageCard error header="An unknown error occured">
|
||||||
error
|
|
||||||
header="An unknown error occured"
|
|
||||||
onBack={() => window.location.reload()}
|
|
||||||
>
|
|
||||||
A fatal error occured in the call loop. Please check you have entered a
|
A fatal error occured in the call loop. Please check you have entered a
|
||||||
valid <code>DAILY_DOMAIN</code> and <code>DAILY_API_KEY</code>{' '}
|
valid <code>DAILY_DOMAIN</code> and <code>DAILY_API_KEY</code>{' '}
|
||||||
environmental variables.
|
environmental variables.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" stroke="currentColor"><polyline points="5 14 8 17 11 14" stroke="currentColor"></polyline><line x1="8" y1="17" x2="8" y2="1.751" stroke="currentColor"></line><polyline points="13 10 16 7 19 10" stroke="currentColor"></polyline><line x1="16" y1="7" x2="16" y2="22.246" stroke="currentColor"></line><circle cx="12" cy="12" r="11"></circle></g></svg>
|
||||||
|
After Width: | Height: | Size: 509 B |
Loading…
Reference in New Issue