added network performance tab
This commit is contained in:
parent
fff932f979
commit
a65e97ee9a
|
|
@ -1,5 +1,6 @@
|
|||
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';
|
||||
|
||||
export const Asides = () => {
|
||||
|
|
@ -8,6 +9,7 @@ export const Asides = () => {
|
|||
return (
|
||||
<>
|
||||
<PeopleAside />
|
||||
<NetworkAside />
|
||||
{asides.map((AsideComponent) => (
|
||||
<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 { 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,
|
||||
onBack,
|
||||
}) => (
|
||||
<Card className={error && 'error'}>
|
||||
<Card className={error ? 'error' : ''}>
|
||||
{header && <CardHeader>{header}</CardHeader>}
|
||||
{children && <CardBody>{children}</CardBody>}
|
||||
{!hideBack && (
|
||||
|
|
@ -23,7 +23,7 @@ export const MessageCard = ({
|
|||
{onBack ? (
|
||||
<Button onClick={() => onBack()}>Go back</Button>
|
||||
) : (
|
||||
<Button href="/">Go back</Button>
|
||||
<Button onClick={() => window.location.reload()}>Go back</Button>
|
||||
)}
|
||||
</CardFooter>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside';
|
||||
import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
|
||||
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
|
||||
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 IconMicOff } from '@custom/shared/icons/mic-off-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 IconSettings } from '@custom/shared/icons/settings-md.svg';
|
||||
import { Tray, TrayButton } from './Tray';
|
||||
|
|
@ -47,7 +49,9 @@ export const BasicTray = () => {
|
|||
<TrayButton label="Settings" onClick={() => openModal(DEVICE_MODAL)}>
|
||||
<IconSettings />
|
||||
</TrayButton>
|
||||
|
||||
<TrayButton label="Network" onClick={() => toggleAside(NETWORK_ASIDE)}>
|
||||
<IconNetwork />
|
||||
</TrayButton>
|
||||
<TrayButton label="People" onClick={() => toggleAside(PEOPLE_ASIDE)}>
|
||||
<IconPeople />
|
||||
</TrayButton>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ export const useCallUI = ({
|
|||
callEnded()
|
||||
) : (
|
||||
<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>
|
||||
);
|
||||
default:
|
||||
|
|
@ -89,11 +90,7 @@ export const useCallUI = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<MessageCard
|
||||
error
|
||||
header="An unknown error occured"
|
||||
onBack={() => window.location.reload()}
|
||||
>
|
||||
<MessageCard error header="An unknown error occured">
|
||||
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>{' '}
|
||||
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