From 7bd5ab7a135c709f818d3626e3d25f2f48c150c5 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Fri, 31 Jul 2026 14:32:31 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20expose=20media=20state=20?= =?UTF-8?q?to=20external=20gateways?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a hidden div in the DOM that reflects the current state of the microphone and camera, so that external SIP media gateways (e.g. the Renater one) can observe it and keep an accurate view of the media state. Also emit a custom event from the page whenever the microphone or camera state changes, so external consumers can subscribe to updates instead of polling the DOM. --- CHANGELOG.md | 1 + .../livekit/components/MediaStateObserver.tsx | 42 +++++++++++++++++++ .../rooms/livekit/prefabs/VideoConference.tsx | 2 + 3 files changed, 45 insertions(+) create mode 100644 src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef49df0..3cd8f588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to - ✨(frontend) introduce an "unauthenticated" participant badge - ✨(backend) add roomkit viewset to start a room without WebRTC join - ✨(frontend) let users set default configuration for generated links +- ✨(frontend) expose media state to external gateways ### Changed diff --git a/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx b/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx new file mode 100644 index 00000000..63ffc518 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx @@ -0,0 +1,42 @@ +import { useLocalParticipant } from '@livekit/components-react' +import { useEffect } from 'react' + +export const MEDIA_STATE_ELEMENT_ID = 'media-state' +export const MEDIA_STATE_CHANGED_EVENT = 'media-state-changed' + +export type MediaStateChangedDetail = { + microphoneEnabled: boolean + cameraEnabled: boolean +} + +/** + * Exposes the local participant's media state in the DOM so external tools + * (e.g. bots automating the frontend) can reliably read the microphone and + * camera state, and watch for changes with a MutationObserver: + * + * const el = document.getElementById('media-state') + * new MutationObserver(...).observe(el, { attributes: true }) + */ +export const MediaStateObserver = () => { + const { isMicrophoneEnabled, isCameraEnabled } = useLocalParticipant() + + useEffect(() => { + window.dispatchEvent( + new CustomEvent(MEDIA_STATE_CHANGED_EVENT, { + detail: { + microphoneEnabled: isMicrophoneEnabled, + cameraEnabled: isCameraEnabled, + }, + }) + ) + }, [isMicrophoneEnabled, isCameraEnabled]) + + return ( +
+ ) +} diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx index c28981dc..a92e0289 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx @@ -11,6 +11,7 @@ import { SidePanel } from '../components/SidePanel' import { RecordingProvider } from '@/features/recording' import { ScreenShareErrorModal } from '../components/ScreenShareErrorModal' import { ConnectionObserver } from '../components/ConnectionObserver' +import { MediaStateObserver } from '../components/MediaStateObserver' import { RoomMetadataSynchronizer } from '../components/RoomMetadataSynchronizer' import { useRoomPageTitle } from '../hooks/useRoomPageTitle' import { useNoiseReduction } from '../hooks/useNoiseReduction' @@ -63,6 +64,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) { <> +