✨(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:
committed by
aleb_the_flash
parent
26bd947541
commit
6a76041a70
19
src/frontend/src/features/rooms/api/muteParticipants.ts
Normal file
19
src/frontend/src/features/rooms/api/muteParticipants.ts
Normal 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 }
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -443,6 +443,7 @@
|
||||
"raisedHands": "Meldungen",
|
||||
"lowerParticipantHand": "{{name}}’s Hand senken",
|
||||
"lowerParticipantsHand": "Alle Hände senken",
|
||||
"muteParticipants": "Alle Mikrofone stummschalten",
|
||||
"waiting": {
|
||||
"title": "Warteraum",
|
||||
"accept": {
|
||||
|
||||
@@ -443,6 +443,7 @@
|
||||
"raisedHands": "Raised hands",
|
||||
"lowerParticipantHand": "Lower {{name}}'s hand",
|
||||
"lowerParticipantsHand": "Lower all hands",
|
||||
"muteParticipants": "Mute all microphones",
|
||||
"waiting": {
|
||||
"title": "Lobby",
|
||||
"accept": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user