diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d23a0d..cb1e4417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx b/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx index 1c23d9dd..902a89f9 100644 --- a/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx +++ b/src/frontend/src/features/rooms/livekit/components/controls/HandToggle.tsx @@ -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 | 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' diff --git a/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts b/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts index f42271db..9cb612eb 100644 --- a/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts +++ b/src/frontend/src/features/rooms/livekit/hooks/useRaisedHand.ts @@ -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 } }