From be033ae6c9a9e7e99e9ab27aea516474c6961f78 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Wed, 15 Jul 2026 16:14:28 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F(frontend)=20minimize=20obser?= =?UTF-8?q?vables=20consumed=20by=20useRaisedHandPosition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useRaisedHandPosition is called on every participant tile, so any unnecessary subscription in it multiplies re-renders across the whole conference. Reduce the number of events subscribed to for remote participants to the strict minimum, while keeping the subscription to the relevant attributes of the local participant intact. --- .../rooms/livekit/hooks/useRaisedHand.ts | 65 ++++++++++++++----- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts b/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts index dd77ef7a..01612abc 100644 --- a/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts +++ b/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts @@ -1,7 +1,9 @@ -import type { Participant } from 'livekit-client' +import { Participant, RoomEvent } from 'livekit-client' import { + useRoomContext, useParticipantAttribute, - useParticipants, + useParticipantInfo, + useRemoteParticipants, } from '@livekit/components-react' import { isLocal } from '@/utils/livekit' import { useMemo } from 'react' @@ -12,28 +14,55 @@ type useRaisedHandProps = { } export function useRaisedHandPosition({ participant }: useRaisedHandProps) { - const { isHandRaised } = useRaisedHand({ participant }) + const room = useRoomContext() + const { identity } = useParticipantInfo({ participant }) + const localIdentity = room.localParticipant.identity - const participants = useParticipants() + const localHandRaisedAt = useParticipantAttribute('handRaisedAt', { + participant: room.localParticipant, + }) + + const remoteParticipants = useRemoteParticipants({ + updateOnlyOn: [RoomEvent.ParticipantAttributesChanged], + }) + + const raisedHands = useMemo(() => { + const byIdentity = new Map() + + const add = (id: string, value?: string) => { + const time = new Date(value ?? '').getTime() + if (Number.isNaN(time)) return + const existing = byIdentity.get(id) + if (existing === undefined || time < existing) byIdentity.set(id, time) + } + + if (localIdentity) add(localIdentity, localHandRaisedAt) + + remoteParticipants.forEach((p) => + add(p.identity, p.attributes.handRaisedAt) + ) + + return byIdentity + }, [remoteParticipants, localIdentity, localHandRaisedAt]) + + const sortedHands = useMemo( + () => + [...raisedHands.entries()] + .map(([identity, time]) => ({ identity, time })) + .sort( + (a, b) => a.time - b.time || a.identity.localeCompare(b.identity) + ), + [raisedHands] + ) const positionInQueue = useMemo(() => { - if (!isHandRaised) return - - return ( - participants - .filter((p) => !!p.attributes.handRaisedAt) - .sort((a, b) => { - const dateA = new Date(a.attributes.handRaisedAt) - const dateB = new Date(b.attributes.handRaisedAt) - return dateA.getTime() - dateB.getTime() - }) - .findIndex((p) => p.identity === participant.identity) + 1 - ) - }, [participants, participant, isHandRaised]) + const index = sortedHands.findIndex((h) => h.identity === identity) + return index === -1 ? undefined : index + 1 + }, [sortedHands, identity]) return { positionInQueue, - firstInQueue: positionInQueue == 1, + firstInQueue: positionInQueue === 1, } }