* feat: [#15] implement getPeerConnectionTypes * feat: [#15] display connection type icon * refactor: extract PeerListItem to its own file * feat: [#15] show connection details via tooltip * fix: style stable peer name
This commit is contained in:
parent
87ffd1df56
commit
4cf75b15b0
@ -20,7 +20,10 @@ export const PeerNameDisplay = ({
|
||||
return (
|
||||
<Typography component="span" {...rest}>
|
||||
{friendlyName}
|
||||
<Typography variant="caption"> ({getPeerName(userId)})</Typography>
|
||||
<Typography variant="caption" {...rest}>
|
||||
{' '}
|
||||
({getPeerName(userId)})
|
||||
</Typography>
|
||||
</Typography>
|
||||
)
|
||||
} else {
|
||||
|
@ -54,6 +54,7 @@ export function useRoom(
|
||||
const {
|
||||
peerList,
|
||||
setPeerList,
|
||||
setPeerConnectionTypes,
|
||||
tabHasFocus,
|
||||
showAlert,
|
||||
setRoomId,
|
||||
@ -368,6 +369,12 @@ export function useRoom(
|
||||
sendPeerMetadata({ customUsername, userId })
|
||||
}, [customUsername, userId, sendPeerMetadata])
|
||||
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
setPeerConnectionTypes(await peerRoom.getPeerConnectionTypes())
|
||||
})()
|
||||
}, [peerList, peerRoom, setPeerConnectionTypes])
|
||||
|
||||
return {
|
||||
isPrivate,
|
||||
handleInlineMediaUpload,
|
||||
|
@ -10,12 +10,11 @@ import VolumeUp from '@mui/icons-material/VolumeUp'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
|
||||
import { PeerListHeader } from 'components/Shell/PeerListHeader'
|
||||
import { AudioVolume } from 'components/AudioVolume'
|
||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||
import { Username } from 'components/Username/Username'
|
||||
import { AudioState, Peer } from 'models/chat'
|
||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
||||
|
||||
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
|
||||
import { PeerListItem } from './PeerListItem'
|
||||
|
||||
export const peerListWidth = 300
|
||||
|
||||
@ -24,6 +23,7 @@ export interface PeerListProps extends PropsWithChildren {
|
||||
isPeerListOpen: boolean
|
||||
onPeerListClose: () => void
|
||||
peerList: Peer[]
|
||||
peerConnectionTypes: Record<string, PeerConnectionType>
|
||||
audioState: AudioState
|
||||
peerAudios: Record<string, HTMLAudioElement>
|
||||
}
|
||||
@ -33,6 +33,7 @@ export const PeerList = ({
|
||||
isPeerListOpen,
|
||||
onPeerListClose,
|
||||
peerList,
|
||||
peerConnectionTypes,
|
||||
audioState,
|
||||
peerAudios,
|
||||
}: PeerListProps) => {
|
||||
@ -72,15 +73,12 @@ export const PeerList = ({
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
{peerList.map((peer: Peer) => (
|
||||
<ListItem key={peer.peerId} divider={true}>
|
||||
<PeerDownloadFileButton peer={peer} />
|
||||
<ListItemText>
|
||||
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
||||
{peer.peerId in peerAudios && (
|
||||
<AudioVolume audioEl={peerAudios[peer.peerId]} />
|
||||
)}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
<PeerListItem
|
||||
key={peer.peerId}
|
||||
peer={peer}
|
||||
peerConnectionTypes={peerConnectionTypes}
|
||||
peerAudios={peerAudios}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</MuiDrawer>
|
||||
|
76
src/components/Shell/PeerListItem.tsx
Normal file
76
src/components/Shell/PeerListItem.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { Box } from '@mui/system'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
import SyncAltIcon from '@mui/icons-material/SyncAlt'
|
||||
import NetworkPingIcon from '@mui/icons-material/NetworkPing'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import { Tooltip } from '@mui/material'
|
||||
|
||||
import { AudioVolume } from 'components/AudioVolume'
|
||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||
import { Peer } from 'models/chat'
|
||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
||||
|
||||
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
|
||||
|
||||
interface PeerListItemProps {
|
||||
peer: Peer
|
||||
peerConnectionTypes: Record<string, PeerConnectionType>
|
||||
peerAudios: Record<string, HTMLAudioElement>
|
||||
}
|
||||
export const PeerListItem = ({
|
||||
peer,
|
||||
peerConnectionTypes,
|
||||
peerAudios,
|
||||
}: PeerListItemProps): JSX.Element => {
|
||||
const hasPeerConnection = peer.peerId in peerConnectionTypes
|
||||
|
||||
const isPeerConnectionDirect =
|
||||
peerConnectionTypes[peer.peerId] === PeerConnectionType.DIRECT
|
||||
|
||||
return (
|
||||
<ListItem key={peer.peerId} divider={true}>
|
||||
<PeerDownloadFileButton peer={peer} />
|
||||
<ListItemText>
|
||||
{hasPeerConnection ? (
|
||||
<Tooltip
|
||||
title={
|
||||
isPeerConnectionDirect ? (
|
||||
<>
|
||||
You are connected directly to{' '}
|
||||
<PeerNameDisplay
|
||||
sx={{ fontSize: 'inherit', fontWeight: 'inherit' }}
|
||||
>
|
||||
{peer.userId}
|
||||
</PeerNameDisplay>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
You are connected to{' '}
|
||||
<PeerNameDisplay
|
||||
sx={{ fontSize: 'inherit', fontWeight: 'inherit' }}
|
||||
>
|
||||
{peer.userId}
|
||||
</PeerNameDisplay>{' '}
|
||||
via a relay server. Your connection is still private and
|
||||
encrypted, but performance may be degraded.
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Box component="span" sx={{ pr: 1, cursor: 'pointer' }}>
|
||||
{isPeerConnectionDirect ? (
|
||||
<SyncAltIcon color="success" />
|
||||
) : (
|
||||
<NetworkPingIcon color="warning" />
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
||||
{peer.peerId in peerAudios && (
|
||||
<AudioVolume audioEl={peerAudios[peer.peerId]} />
|
||||
)}
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)
|
||||
}
|
@ -19,6 +19,8 @@ import { AlertOptions } from 'models/shell'
|
||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||
|
||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
||||
|
||||
import { Drawer } from './Drawer'
|
||||
import { UpgradeDialog } from './UpgradeDialog'
|
||||
import { ShellAppBar } from './ShellAppBar'
|
||||
@ -65,6 +67,9 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||
const [isPeerListOpen, setIsPeerListOpen] = useState(defaultSidebarsOpen)
|
||||
const [peerList, setPeerList] = useState<Peer[]>([]) // except self
|
||||
const [peerConnectionTypes, setPeerConnectionTypes] = useState<
|
||||
Record<string, PeerConnectionType>
|
||||
>({})
|
||||
const [tabHasFocus, setTabHasFocus] = useState(true)
|
||||
const [audioState, setAudioState] = useState<AudioState>(AudioState.STOPPED)
|
||||
const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED)
|
||||
@ -101,6 +106,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
setIsPeerListOpen,
|
||||
peerList,
|
||||
setPeerList,
|
||||
peerConnectionTypes,
|
||||
setPeerConnectionTypes,
|
||||
audioState,
|
||||
setAudioState,
|
||||
videoState,
|
||||
@ -120,6 +127,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
password,
|
||||
setPassword,
|
||||
peerList,
|
||||
peerConnectionTypes,
|
||||
tabHasFocus,
|
||||
showRoomControls,
|
||||
setShowRoomControls,
|
||||
@ -315,6 +323,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
||||
isPeerListOpen={isPeerListOpen}
|
||||
onPeerListClose={handlePeerListClick}
|
||||
peerList={peerList}
|
||||
peerConnectionTypes={peerConnectionTypes}
|
||||
audioState={audioState}
|
||||
peerAudios={peerAudios}
|
||||
/>
|
||||
|
@ -2,6 +2,7 @@ import { createContext, Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import { AlertOptions } from 'models/shell'
|
||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
||||
|
||||
interface ShellContextProps {
|
||||
tabHasFocus: boolean
|
||||
@ -17,6 +18,10 @@ interface ShellContextProps {
|
||||
setIsPeerListOpen: Dispatch<SetStateAction<boolean>>
|
||||
peerList: Peer[]
|
||||
setPeerList: Dispatch<SetStateAction<Peer[]>>
|
||||
peerConnectionTypes: Record<string, PeerConnectionType>
|
||||
setPeerConnectionTypes: Dispatch<
|
||||
SetStateAction<Record<string, PeerConnectionType>>
|
||||
>
|
||||
audioState: AudioState
|
||||
setAudioState: Dispatch<SetStateAction<AudioState>>
|
||||
videoState: VideoState
|
||||
@ -43,6 +48,8 @@ export const ShellContext = createContext<ShellContextProps>({
|
||||
setIsPeerListOpen: () => {},
|
||||
peerList: [],
|
||||
setPeerList: () => {},
|
||||
peerConnectionTypes: {},
|
||||
setPeerConnectionTypes: () => {},
|
||||
audioState: AudioState.STOPPED,
|
||||
setAudioState: () => {},
|
||||
videoState: VideoState.STOPPED,
|
||||
|
@ -17,6 +17,11 @@ export enum PeerStreamType {
|
||||
SCREEN = 'SCREEN',
|
||||
}
|
||||
|
||||
export enum PeerConnectionType {
|
||||
DIRECT = 'DIRECT',
|
||||
RELAY = 'RELAY',
|
||||
}
|
||||
|
||||
const streamQueueAddDelay = 1000
|
||||
|
||||
export class PeerRoom {
|
||||
@ -112,9 +117,44 @@ export class PeerRoom {
|
||||
|
||||
getPeers = () => {
|
||||
const peers = this.room.getPeers()
|
||||
|
||||
return Object.keys(peers)
|
||||
}
|
||||
|
||||
getPeerConnectionTypes = async () => {
|
||||
const peers = this.room.getPeers()
|
||||
|
||||
const peerConnections: Record<string, PeerConnectionType> = {}
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(peers).map(async ([peerId, rtcPeerConnection]) => {
|
||||
const stats = await rtcPeerConnection.getStats()
|
||||
let selectedLocalCandidate
|
||||
|
||||
// https://stackoverflow.com/a/61571171/470685
|
||||
for (const { type, state, localCandidateId } of stats.values())
|
||||
if (
|
||||
type === 'candidate-pair' &&
|
||||
state === 'succeeded' &&
|
||||
localCandidateId
|
||||
) {
|
||||
selectedLocalCandidate = localCandidateId
|
||||
break
|
||||
}
|
||||
|
||||
const isRelay =
|
||||
!!selectedLocalCandidate &&
|
||||
stats.get(selectedLocalCandidate)?.candidateType === 'relay'
|
||||
|
||||
peerConnections[peerId] = isRelay
|
||||
? PeerConnectionType.RELAY
|
||||
: PeerConnectionType.DIRECT
|
||||
})
|
||||
)
|
||||
|
||||
return peerConnections
|
||||
}
|
||||
|
||||
makeAction = <T>(namespace: string) => {
|
||||
return this.room.makeAction<T>(namespace)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user