From 80d37595adc8cfded734ab734f7bc12989545770 Mon Sep 17 00:00:00 2001 From: moustique82 Date: Mon, 31 Aug 2026 16:05:00 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20expose=20publish=20permis?= =?UTF-8?q?sions=20in=20MediaStateObserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The component exposes the local microphone and camera state so external tools automating the frontend can read it. It does not expose whether publication is allowed, so such a tool cannot tell a muted microphone from one it is not permitted to unmute, and ends up offering a control that silently does nothing. Expose canPublishMicrophone and canPublishCamera in the event detail and as data attributes, reusing the useCanPublishTrack hook that already gates the web client controls. The permissions are part of the effect dependencies so a mid-meeting permission change emits the event. --- CHANGELOG.md | 1 + .../livekit/components/MediaStateObserver.tsx | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5664fc24..353bc7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to - ✨(agent) support Voxtral realtime as inference engine - 🌐(i18n) add Spanish language support +- ✨(frontend) expose publish permissions on the media state element #1661 ### Changed diff --git a/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx b/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx index 63ffc518..5d8ed43e 100644 --- a/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx +++ b/src/frontend/src/features/rooms/livekit/components/MediaStateObserver.tsx @@ -1,5 +1,7 @@ import { useLocalParticipant } from '@livekit/components-react' +import { TrackSource } from '@livekit/protocol' import { useEffect } from 'react' +import { useCanPublishTrack } from '@/features/rooms/livekit/hooks/useCanPublishTrack' export const MEDIA_STATE_ELEMENT_ID = 'media-state' export const MEDIA_STATE_CHANGED_EVENT = 'media-state-changed' @@ -7,6 +9,8 @@ export const MEDIA_STATE_CHANGED_EVENT = 'media-state-changed' export type MediaStateChangedDetail = { microphoneEnabled: boolean cameraEnabled: boolean + canPublishMicrophone: boolean + canPublishCamera: boolean } /** @@ -16,9 +20,15 @@ export type MediaStateChangedDetail = { * * const el = document.getElementById('media-state') * new MutationObserver(...).observe(el, { attributes: true }) + * + * The publish permissions are exposed alongside the state: an external tool + * cannot tell a muted microphone from one it is not allowed to unmute, and + * would otherwise offer a control that silently does nothing. */ export const MediaStateObserver = () => { const { isMicrophoneEnabled, isCameraEnabled } = useLocalParticipant() + const canPublishMicrophone = useCanPublishTrack(TrackSource.MICROPHONE) + const canPublishCamera = useCanPublishTrack(TrackSource.CAMERA) useEffect(() => { window.dispatchEvent( @@ -26,10 +36,17 @@ export const MediaStateObserver = () => { detail: { microphoneEnabled: isMicrophoneEnabled, cameraEnabled: isCameraEnabled, + canPublishMicrophone, + canPublishCamera, }, }) ) - }, [isMicrophoneEnabled, isCameraEnabled]) + }, [ + isMicrophoneEnabled, + isCameraEnabled, + canPublishMicrophone, + canPublishCamera, + ]) return (
{ style={{ display: 'none' }} data-microphone-enabled={isMicrophoneEnabled ? 'true' : 'false'} data-camera-enabled={isCameraEnabled ? 'true' : 'false'} + data-can-publish-microphone={canPublishMicrophone ? 'true' : 'false'} + data-can-publish-camera={canPublishCamera ? 'true' : 'false'} /> ) }