(frontend) add "mute all participants" feature for admins

Implement admin capability to mute everyone's microphone in large rooms
where participants forget to mute themselves and are hard to identify
quickly.

Feature requested by @arnaud-robin. Provides instant room-wide muting
without individual confirmation popups, enabling efficient moderation
in busy conference scenarios.
This commit is contained in:
lebaudantoine
2025-08-27 17:57:42 +02:00
committed by aleb_the_flash
parent 26bd947541
commit 6a76041a70
8 changed files with 73 additions and 2 deletions

View File

@@ -0,0 +1,19 @@
import { Participant } from 'livekit-client'
import { useMuteParticipant } from './muteParticipant'
export const useMuteParticipants = () => {
const { muteParticipant } = useMuteParticipant()
const muteParticipants = (participants: Array<Participant>) => {
try {
const promises = participants.map((participant) =>
muteParticipant(participant)
)
return Promise.all(promises)
} catch (error) {
console.error('An error occurred while muting participants :', error)
throw new Error('An error occurred while muting participants.')
}
}
return { muteParticipants }
}

View File

@@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next'
import { Participant } from 'livekit-client'
import { useLowerHandParticipants } from '@/features/rooms/api/lowerHandParticipants'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { css } from '@/styled-system/css'
import { RiHand } from '@remixicon/react'
type LowerAllHandsButtonProps = {
participants: Array<Participant>
@@ -22,10 +24,14 @@ export const LowerAllHandsButton = ({
aria-label={t('participants.lowerParticipantsHand')}
size="sm"
fullWidth
variant="text"
variant="tertiary"
onPress={() => lowerHandParticipants(participants)}
data-attr="participants-lower-hands"
className={css({
marginBottom: '0.5rem',
})}
>
<RiHand size={16} />
{t('participants.lowerParticipantsHand')}
</Button>
)

View File

@@ -0,0 +1,38 @@
import { Button } from '@/primitives'
import { useTranslation } from 'react-i18next'
import { Participant } from 'livekit-client'
import { useIsAdminOrOwner } from '@/features/rooms/livekit/hooks/useIsAdminOrOwner'
import { useMuteParticipants } from '@/features/rooms/api/muteParticipants'
import { RiMicOffLine } from '@remixicon/react'
import { css } from '@/styled-system/css'
type MuteEveryoneButtonProps = {
participants: Array<Participant>
}
export const MuteEveryoneButton = ({
participants,
}: MuteEveryoneButtonProps) => {
const { muteParticipants } = useMuteParticipants()
const { t } = useTranslation('rooms')
const isAdminOrOwner = useIsAdminOrOwner()
if (!isAdminOrOwner || !participants.length) return null
return (
<Button
aria-label={t('participants.muteParticipants')}
size="sm"
fullWidth
variant="tertiary"
onPress={() => muteParticipants(participants)}
data-attr="participants-mute"
className={css({
marginBottom: '0.5rem',
})}
>
<RiMicOffLine size={16} />
{t('participants.muteParticipants')}
</Button>
)
}

View File

@@ -11,6 +11,7 @@ import { WaitingParticipantListItem } from './WaitingParticipantListItem'
import { useWaitingParticipants } from '@/features/rooms/hooks/useWaitingParticipants'
import { Participant } from 'livekit-client'
import { WaitingParticipant } from '@/features/rooms/api/listWaitingParticipants'
import { MuteEveryoneButton } from './MuteEveryoneButton'
// TODO: Optimize rendering performance, especially for longer participant lists, even though they are generally short.
export const ParticipantsList = () => {
@@ -104,6 +105,9 @@ export const ParticipantsList = () => {
participant={participant}
/>
)}
action={() => (
<MuteEveryoneButton participants={sortedRemoteParticipants} />
)}
/>
</Div>
)

View File

@@ -443,6 +443,7 @@
"raisedHands": "Meldungen",
"lowerParticipantHand": "{{name}}s Hand senken",
"lowerParticipantsHand": "Alle Hände senken",
"muteParticipants": "Alle Mikrofone stummschalten",
"waiting": {
"title": "Warteraum",
"accept": {

View File

@@ -443,6 +443,7 @@
"raisedHands": "Raised hands",
"lowerParticipantHand": "Lower {{name}}'s hand",
"lowerParticipantsHand": "Lower all hands",
"muteParticipants": "Mute all microphones",
"waiting": {
"title": "Lobby",
"accept": {

View File

@@ -442,7 +442,8 @@
},
"raisedHands": "Mains levées",
"lowerParticipantHand": "Baisser la main de {{name}}",
"lowerParticipantsHand": "Baisser la main de tous les participants",
"lowerParticipantsHand": "Baisser toutes les mains",
"muteParticipants": "Couper tous les micros",
"waiting": {
"title": "Salle d'attente",
"accept": {

View File

@@ -443,6 +443,7 @@
"raisedHands": "Opgestoken handen",
"lowerParticipantHand": "Laat {{name}}'s hand zakken",
"lowerParticipantsHand": "Laat alle handen zakken",
"muteParticipants": "Alle microfoons dempen",
"waiting": {
"title": "Wachtkamer",
"accept": {