mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-12 19:56:53 +00:00
5bac1668fe
Simplify source serialization and validation logic while improving type safety around room configuration handling. Introduce a dedicated TypeScript type matching the backend Pydantic model more precisely. Also harmonize track source casing between frontend and backend to remove redundant conversion logic and resolve #1282.
161 lines
4.7 KiB
TypeScript
161 lines
4.7 KiB
TypeScript
import { Track } from 'livekit-client'
|
|
import { useCallback, useMemo } from 'react'
|
|
import { queryClient } from '@/api/queryClient'
|
|
import { keys } from '@/api/queryKeys'
|
|
import { useConfig } from '@/api/useConfig'
|
|
import { usePatchRoom } from '@/features/rooms/api/patchRoom'
|
|
import { useRemoteParticipants } from '@livekit/components-react'
|
|
import { useUpdateParticipantsPermissions } from '@/features/rooms/api/updateParticipantsPermissions'
|
|
import { useRoomData } from '@/features/rooms/livekit/hooks/useRoomData'
|
|
import { isSubsetOf } from '@/features/rooms/utils/isSubsetOf'
|
|
import { getParticipantIsRoomAdmin } from '@/features/rooms/utils/getParticipantIsRoomAdmin'
|
|
import Source = Track.Source
|
|
import {
|
|
NotificationType,
|
|
useNotifyParticipants,
|
|
} from '@/features/notifications'
|
|
|
|
export const updatePublishSources = (
|
|
currentSources: Source[],
|
|
sources: Source[],
|
|
enabled: boolean
|
|
): Source[] => {
|
|
if (enabled) {
|
|
const combined = [...currentSources, ...sources]
|
|
return Array.from(new Set(combined))
|
|
} else {
|
|
return currentSources.filter(
|
|
(source) => !sources.some((newSource) => newSource === source)
|
|
)
|
|
}
|
|
}
|
|
|
|
export const usePublishSourcesManager = () => {
|
|
const { mutateAsync: patchRoom } = usePatchRoom()
|
|
|
|
const data = useRoomData()
|
|
const { data: configData } = useConfig()
|
|
const configuration = data?.configuration
|
|
|
|
const { notifyParticipants } = useNotifyParticipants()
|
|
|
|
// The name can be misleading—use the slug instead to ensure the correct React Query key is updated.
|
|
const roomId = data?.slug
|
|
|
|
const { updateParticipantsPermissions } = useUpdateParticipantsPermissions()
|
|
const remoteParticipants = useRemoteParticipants()
|
|
|
|
const unprivilegedRemoteParticipants = remoteParticipants.filter(
|
|
(participant) => !getParticipantIsRoomAdmin(participant)
|
|
)
|
|
|
|
const currentSources = useMemo(() => {
|
|
const defaultSources = configData?.livekit?.default_sources ?? []
|
|
|
|
if (
|
|
configuration?.can_publish_sources == undefined ||
|
|
!Array.isArray(configuration?.can_publish_sources)
|
|
) {
|
|
return defaultSources
|
|
}
|
|
return configuration.can_publish_sources
|
|
}, [configData, configuration?.can_publish_sources])
|
|
|
|
const updateSource = useCallback(
|
|
async (sources: Source[], enabled: boolean) => {
|
|
if (!roomId || currentSources == undefined) return
|
|
|
|
try {
|
|
const newSources = updatePublishSources(
|
|
currentSources,
|
|
sources,
|
|
enabled
|
|
)
|
|
|
|
const newConfiguration = {
|
|
...configuration,
|
|
can_publish_sources: newSources,
|
|
}
|
|
|
|
const room = await patchRoom({
|
|
roomId,
|
|
room: { configuration: newConfiguration },
|
|
})
|
|
|
|
queryClient.setQueryData([keys.room, roomId], room)
|
|
|
|
await updateParticipantsPermissions(
|
|
unprivilegedRemoteParticipants,
|
|
newSources
|
|
)
|
|
|
|
if (!enabled) {
|
|
/*
|
|
* We can't rely solely on the ParticipantPermissionsChanged event here,
|
|
* because for local participants it is emitted twice (once from Participant, once from LocalParticipant).
|
|
* livekit/client-sdk-js/issues/1637
|
|
* */
|
|
await notifyParticipants({
|
|
type: NotificationType.PermissionsRemoved,
|
|
destinationIdentities: unprivilegedRemoteParticipants.map(
|
|
(p) => p.identity
|
|
),
|
|
additionalData: {
|
|
data: {
|
|
removedSources: sources,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return { configuration: newConfiguration }
|
|
} catch (error) {
|
|
console.error(`Failed to update ${sources}:`, error)
|
|
return { success: false, error }
|
|
}
|
|
},
|
|
[
|
|
notifyParticipants,
|
|
configuration,
|
|
currentSources,
|
|
roomId,
|
|
patchRoom,
|
|
unprivilegedRemoteParticipants,
|
|
updateParticipantsPermissions,
|
|
]
|
|
)
|
|
|
|
const toggleMicrophone = useCallback(
|
|
(enabled: boolean) => updateSource([Source.Microphone], enabled),
|
|
[updateSource]
|
|
)
|
|
|
|
const toggleCamera = useCallback(
|
|
(enabled: boolean) => updateSource([Source.Camera], enabled),
|
|
[updateSource]
|
|
)
|
|
|
|
const toggleScreenShare = useCallback(
|
|
(enabled: boolean) =>
|
|
updateSource([Source.ScreenShare, Source.ScreenShareAudio], enabled),
|
|
[updateSource]
|
|
)
|
|
|
|
const isMicrophoneEnabled = isSubsetOf([Source.Microphone], currentSources)
|
|
const isCameraEnabled = isSubsetOf([Source.Camera], currentSources)
|
|
const isScreenShareEnabled = isSubsetOf(
|
|
[Source.ScreenShare, Source.ScreenShareAudio],
|
|
currentSources
|
|
)
|
|
|
|
return {
|
|
updateSource,
|
|
toggleMicrophone,
|
|
toggleCamera,
|
|
toggleScreenShare,
|
|
isMicrophoneEnabled,
|
|
isCameraEnabled,
|
|
isScreenShareEnabled,
|
|
}
|
|
}
|