Skip to content

Commit

Permalink
Show connection information for each user
Browse files Browse the repository at this point in the history
  • Loading branch information
third774 committed Aug 7, 2024
1 parent d06530b commit 7257474
Show file tree
Hide file tree
Showing 15 changed files with 140 additions and 35 deletions.
61 changes: 61 additions & 0 deletions app/components/ConnectionInformation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState } from 'react'
import type { User } from '~/types/Messages'
import { cn } from '~/utils/style'
import { Icon } from './Icon/Icon'
import { Tooltip } from './Tooltip'

export function ConnectionInformation(props: { user: User }) {
const { inboundPacketLoss, outboundPacketLoss } =
props.user.connectionInformation
const [open, setOpen] = useState(false)

const inbound = (inboundPacketLoss * 100).toFixed(2)
const outbound = (outboundPacketLoss * 100).toFixed(2)

const connectionGood = inboundPacketLoss <= 0.01 && outboundPacketLoss <= 0.01
const connectionUnstable =
(inboundPacketLoss > 0.01 && inboundPacketLoss <= 0.03) ||
(outboundPacketLoss > 0.01 && outboundPacketLoss <= 0.03)
const connectionBad = inboundPacketLoss > 0.03 || outboundPacketLoss > 0.03

return (
<Tooltip
open={open}
onOpenChange={setOpen}
content={
<div className="text-gray-700 dark:text-gray-400">
<div>Packet Loss</div>
<div className="flex gap-4">
<div className="flex items-center gap-1">
<div className="sr-only">Outbound</div>
<Icon
className="text-gray-400 dark:text-gray-300"
type="ArrowUpOnSquareIcon"
/>
<span>{outbound}%</span>
</div>
<div className="flex items-center gap-1">
<div className="sr-only">Inbound</div>
<Icon
className="text-gray-400 dark:text-gray-300"
type="ArrowDownOnSquareIcon"
/>
<span>{inbound}%</span>
</div>
</div>
</div>
}
>
<button className="flex items-center" onClick={() => setOpen(!open)}>
<Icon
className={cn(
connectionGood && 'text-green-400',
connectionUnstable && 'text-yellow-400',
connectionBad && 'text-red-400'
)}
type={connectionBad ? 'SignalSlashIcon' : 'SignalIcon'}
/>
</button>
</Tooltip>
)
}
2 changes: 1 addition & 1 deletion app/components/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export const DialogContent = forwardRef<
'dark:shadow-none'
)}
>
{props.children}
<DialogClose />
{props.children}
</RadixDialog.DialogContent>
))

Expand Down
27 changes: 7 additions & 20 deletions app/components/HighPacketLossWarningsToast.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
import { useMemo } from 'react'
import Toast, { Root } from '~/components/Toast'
import { useSubscribedState } from '~/hooks/rxjsHooks'
import { useConditionForAtLeast } from '~/hooks/useConditionForAtLeast'
import { getPacketLossStats$ } from '~/utils/rxjs/getPacketLossStats$'
import { useRoomContext } from '../hooks/useRoomContext'
import { useRoomContext } from '~/hooks/useRoomContext'
import { Icon } from './Icon/Icon'

function useStats() {
const { peer } = useRoomContext()
const stats$ = useMemo(
() => getPacketLossStats$(peer.peerConnection$),
[peer.peerConnection$]
)
const stats = useSubscribedState(stats$, {
inboundPacketLossPercentage: 0,
outboundPacketLossPercentage: 0,
})

return stats
}

export function HighPacketLossWarningsToast() {
const { inboundPacketLossPercentage, outboundPacketLossPercentage } =
useStats()
const {
connectionStats: {
inboundPacketLossPercentage,
outboundPacketLossPercentage,
},
} = useRoomContext()

const hasIssues = useConditionForAtLeast(
inboundPacketLossPercentage !== undefined &&
Expand Down
2 changes: 2 additions & 0 deletions app/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PhoneXMarkIcon,
PlusIcon,
ServerStackIcon,
SignalIcon,
SignalSlashIcon,
UserGroupIcon,
VideoCameraIcon,
Expand Down Expand Up @@ -52,6 +53,7 @@ const iconMap = {
EllipsisVerticalIcon,
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
SignalIcon,
SignalSlashIcon,
ExclamationCircleIcon,
ServerStackIcon,
Expand Down
24 changes: 14 additions & 10 deletions app/components/Participant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { cn } from '~/utils/style'
import { AudioGlow } from './AudioGlow'
import { AudioIndicator } from './AudioIndicator'
import { Button } from './Button'
import { ConnectionInformation } from './ConnectionInformation'
import { HoverFade } from './HoverFade'
import { Icon } from './Icon/Icon'
import { MuteUserButton } from './MuteUserButton'
Expand Down Expand Up @@ -169,16 +170,19 @@ export const Participant = forwardRef<
)}
</div>
)}
{data?.displayName && user.transceiverSessionId && (
<OptionalLink
className="absolute m-2 leading-none text-shadow left-1 bottom-1"
href={populateTraceLink(user.transceiverSessionId, traceLink)}
target="_blank"
rel="noopener noreferrer"
>
{data.displayName}
</OptionalLink>
)}
<div className="absolute m-2 left-1 bottom-1 flex gap-2 items-center">
<ConnectionInformation user={user} />
{data?.displayName && user.transceiverSessionId && (
<OptionalLink
className="leading-none text-shadow"
href={populateTraceLink(user.transceiverSessionId, traceLink)}
target="_blank"
rel="noopener noreferrer"
>
{data.displayName}
</OptionalLink>
)}
</div>
<div className="absolute top-0 right-0 flex gap-4 p-4">
{user.raisedHand && (
<Tooltip content="Hand is raised">
Expand Down
3 changes: 2 additions & 1 deletion app/components/ParticipantsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import populateTraceLink from '~/utils/populateTraceLink'
import { cn } from '~/utils/style'
import { AudioIndicator } from './AudioIndicator'
import { Button } from './Button'
import { ConnectionInformation } from './ConnectionInformation'
import { Dialog, DialogContent, DialogOverlay, Portal, Trigger } from './Dialog'
import { Icon } from './Icon/Icon'
import { MuteUserButton } from './MuteUserButton'
Expand Down Expand Up @@ -40,6 +41,7 @@ const UserListItem: FC<{
<AudioIndicator audioTrack={audioTrack} />
</div>
)}
<ConnectionInformation user={user} />
<MuteUserButton user={user} />
</li>
)
Expand Down Expand Up @@ -93,7 +95,6 @@ export const ParticipantsDialog: FC<ParticipantDialogProps> = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
{children}

<Portal>
<DialogOverlay />
<DialogContent>
Expand Down
10 changes: 8 additions & 2 deletions app/components/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import type { FC, ReactNode } from 'react'

interface TooltipProps {
open?: boolean
onOpenChange?: (open: boolean) => void
content?: ReactNode
children: ReactNode
}

export const Tooltip: FC<TooltipProps> = ({ children, content, open }) => {
export const Tooltip: FC<TooltipProps> = ({
children,
content,
open,
onOpenChange,
}) => {
if (content === undefined) return <>{children}</>

return (
<RadixTooltip.Provider>
<RadixTooltip.Root open={open}>
<RadixTooltip.Root open={open} onOpenChange={onOpenChange}>
<RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger>
<RadixTooltip.Portal>
<RadixTooltip.Content className="bg-zinc-100 dark:bg-zinc-600 text-sm px-2 py-1 drop-shadow-md dark:drop-shadow-none rounded">
Expand Down
4 changes: 4 additions & 0 deletions app/durableObjects/ChatRoom.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export class ChatRoom extends Server<Env> {
videoEnabled: false,
screenShareEnabled: false,
},
connectionInformation: {
inboundPacketLoss: 0,
outboundPacketLoss: 0,
},
}

connection.setState(user)
Expand Down
12 changes: 12 additions & 0 deletions app/hooks/useBroadcastStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Config {
identity?: User
websocket: PartySocket
pushedTracks: RoomContextType['pushedTracks']
connectionStats: RoomContextType['connectionStats']
raisedHand: boolean
speaking: boolean
}
Expand All @@ -26,6 +27,7 @@ export default function useBroadcastStatus({
pushedTracks,
raisedHand,
speaking,
connectionStats,
}: Config) {
const { audioEnabled, videoEnabled, screenShareEnabled } = userMedia
const { audio, video, screenshare } = pushedTracks
Expand All @@ -50,6 +52,10 @@ export default function useBroadcastStatus({
audio,
screenshare,
},
connectionInformation: {
inboundPacketLoss: connectionStats.inboundPacketLossPercentage,
outboundPacketLoss: connectionStats.outboundPacketLossPercentage,
},
}

function sendUserUpdate() {
Expand Down Expand Up @@ -82,6 +88,8 @@ export default function useBroadcastStatus({
screenShareEnabled,
raisedHand,
speaking,
connectionStats.inboundPacketLossPercentage,
connectionStats.outboundPacketLossPercentage,
])

useUnmount(() => {
Expand All @@ -97,6 +105,10 @@ export default function useBroadcastStatus({
speaking,
transceiverSessionId: sessionId,
tracks: {},
connectionInformation: {
inboundPacketLoss: 0,
outboundPacketLoss: 0,
},
},
} satisfies ClientMessage)
)
Expand Down
17 changes: 17 additions & 0 deletions app/hooks/useConnectionStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useMemo } from 'react'
import { getPacketLossStats$ } from '~/utils/rxjs/getPacketLossStats$'
import type { RxjsPeer } from '~/utils/rxjs/RxjsPeer.client'
import { useSubscribedState } from './rxjsHooks'

export function useConnectionStats(peer: RxjsPeer) {
const stats$ = useMemo(
() => getPacketLossStats$(peer.peerConnection$),
[peer.peerConnection$]
)
const stats = useSubscribedState(stats$, {
inboundPacketLossPercentage: 0,
outboundPacketLossPercentage: 0,
})

return stats
}
2 changes: 2 additions & 0 deletions app/hooks/useRoomContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useOutletContext } from '@remix-run/react'
import type { Dispatch, SetStateAction } from 'react'
import type { UserMedia } from '~/hooks/useUserMedia'
import type { RxjsPeer } from '~/utils/rxjs/RxjsPeer.client'
import type { useConnectionStats } from './useConnectionStats'
import type useRoom from './useRoom'
import type { useRoomHistory } from './useRoomHistory'

Expand All @@ -21,6 +22,7 @@ export type RoomContextType = {
audio?: string
screenshare?: string
}
connectionStats: ReturnType<typeof useConnectionStats>
}

export function useRoomContext() {
Expand Down
2 changes: 2 additions & 0 deletions app/routes/_room.$roomName.room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ function JoinedRoom({ bugReportsEnabled }: { bugReportsEnabled: boolean }) {
peer,
pushedTracks,
room: { otherUsers, websocket, identity },
connectionStats,
} = useRoomContext()

const { GridDebugControls, fakeUsers } = useGridDebugControls({
Expand Down Expand Up @@ -156,6 +157,7 @@ function JoinedRoom({ bugReportsEnabled }: { bugReportsEnabled: boolean }) {
pushedTracks,
raisedHand,
speaking,
connectionStats,
})

useSounds(otherUsers)
Expand Down
3 changes: 3 additions & 0 deletions app/routes/_room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EnsureOnline } from '~/components/EnsureOnline'
import { EnsurePermissions } from '~/components/EnsurePermissions'
import { Icon } from '~/components/Icon/Icon'
import { useStateObservable, useSubscribedState } from '~/hooks/rxjsHooks'
import { useConnectionStats } from '~/hooks/useConnectionStats'

import { usePeerConnection } from '~/hooks/usePeerConnection'
import useRoom from '~/hooks/useRoom'
Expand Down Expand Up @@ -172,6 +173,7 @@ function Room() {
)
}, [peer, userMedia.screenShareVideoTrack$])
const pushedScreenSharingTrack = useSubscribedState(pushedScreenSharingTrack$)
const connectionStats = useConnectionStats(peer)

const context: RoomContextType = {
joined,
Expand All @@ -184,6 +186,7 @@ function Room() {
roomHistory,
iceConnectionState,
room,
connectionStats,
pushedTracks: {
video: trackObjectToString(pushedVideoTrack),
audio: trackObjectToString(pushedAudioTrack),
Expand Down
4 changes: 4 additions & 0 deletions app/types/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export type User = {
screenshare?: string
screenShareEnabled?: boolean
}
connectionInformation: {
inboundPacketLoss: number
outboundPacketLoss: number
}
}

export type RoomState = {
Expand Down
2 changes: 1 addition & 1 deletion app/utils/rxjs/getPacketLossStats$.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getPacketLossStats$(
) {
const inboundPacketLossPercentageEwma = new Ewma(2000, 0)
const outboundPacketLossPercentageEwma = new Ewma(2000, 0)
return combineLatest([peerConnection$, interval(1000)]).pipe(
return combineLatest([peerConnection$, interval(3000)]).pipe(
switchMap(([peerConnection]) => peerConnection.getStats()),
pairwise(),
map(([previousStatsReport, newStatsReport]) => {
Expand Down

0 comments on commit 7257474

Please sign in to comment.