🐛(frontend) restore automatic lower-hand on speaking

Following the re-rendering optimization refactoring, the automatic
lower-hand feature broke: the way `isSpeaking` was read no longer
made sense once we limited how often components in the app
re-render.

Fix the detection so the raised hand is again lowered automatically
when the participant starts speaking, without relying on frequent
re-renders.
This commit is contained in:
lebaudantoine
2026-09-03 23:23:46 +02:00
parent ac4be27445
commit 035f8fb44f
3 changed files with 29 additions and 6 deletions
+1
View File
@@ -22,6 +22,7 @@ and this project adheres to
- 🐛(backend) allow any printable ASCII characters in user sub field #1673
- 🐛(frontend) keep the sending resolution picked while the camera is off #1667
- 🐛(frontend) restore automatic lower-hand on speaking
## [1.30.0] - 2026-09-01
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next'
import { RiHand } from '@remixicon/react'
import { ToggleButton } from '@/primitives'
import { css } from '@/styled-system/css'
import { useRoomContext } from '@livekit/components-react'
import { useIsSpeaking, useRoomContext } from '@livekit/components-react'
import { useRaisedHand } from '@/features/rooms/livekit/hooks/useRaisedHand'
import { useEffect, useRef, useState } from 'react'
import {
@@ -25,11 +25,11 @@ export const HandToggle = ({
const { t } = useTranslation('rooms', { keyPrefix: 'controls.hand' })
const room = useRoomContext()
const { isHandRaised, toggleRaisedHand } = useRaisedHand({
const { isHandRaised, toggleRaisedHand, lowerHand } = useRaisedHand({
participant: room.localParticipant,
})
const isSpeaking = room.localParticipant.isSpeaking
const isSpeaking = useIsSpeaking(room.localParticipant)
const speakingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const [hasShownToast, setHasShownToast] = useState(false)
@@ -57,9 +57,10 @@ export const HandToggle = ({
if (shouldShowToast && !speakingTimerRef.current) {
speakingTimerRef.current = setTimeout(() => {
speakingTimerRef.current = null
setHasShownToast(true)
const onClose = () => {
if (isHandRaised) toggleRaisedHand()
lowerHand()
resetToastState()
}
showLowerHandToast(room.localParticipant, onClose)
@@ -70,7 +71,17 @@ export const HandToggle = ({
speakingTimerRef.current = null
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSpeaking, isHandRaised, hasShownToast, toggleRaisedHand])
}, [isSpeaking, isHandRaised, hasShownToast, lowerHand])
// Clear any pending timer on unmount
useEffect(() => {
return () => {
if (speakingTimerRef.current) {
clearTimeout(speakingTimerRef.current)
speakingTimerRef.current = null
}
}
}, [])
const tooltipLabel = isHandRaised ? 'lower' : 'raise'
@@ -86,5 +86,16 @@ export function useRaisedHand({ participant }: useRaisedHandProps) {
}
}
return { isHandRaised, toggleRaisedHand }
const lowerHand = async () => {
if (!isLocal(participant)) return
try {
await raiseHand(false)
} catch (e) {
reportError('generic_failure', e, {
context: 'lower_raised_hand',
})
}
}
return { isHandRaised, toggleRaisedHand, lowerHand }
}