From 71eb9ae557529f77c80c74ce494994c929a46d45 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 17 Jul 2024 15:14:42 +0200 Subject: [PATCH] Add DeviceMute widget action `io.element.device_mute`. This allows to send mute requests ("toWidget") and get the current mute state as a response. And it will update the client about each change of mute states. --- src/room/MuteStates.ts | 51 +++++++++++++++++++++++++++++++++++++++++- src/widget.ts | 14 ++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/room/MuteStates.ts b/src/room/MuteStates.ts index 2688c51e..236699e3 100644 --- a/src/room/MuteStates.ts +++ b/src/room/MuteStates.ts @@ -14,10 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Dispatch, SetStateAction, useMemo } from "react"; +import { Dispatch, SetStateAction, useEffect, useMemo } from "react"; +import { IWidgetApiRequest } from "matrix-widget-api"; import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext"; import { useReactiveState } from "../useReactiveState"; +import { ElementWidgetActions, widget } from "../widget"; /** * If there already are this many participants in the call, we automatically mute @@ -74,5 +76,52 @@ export function useMuteStates(): MuteStates { const audio = useMuteState(devices.audioInput, () => true); const video = useMuteState(devices.videoInput, () => true); + useEffect(() => { + widget?.api.transport.send(ElementWidgetActions.DeviceMute, { + audio_enabled: audio.enabled, + video_enabled: video.enabled, + }); + }, [audio, video]); + + useEffect(() => { + if (widget) { + const onMuteStateChangeRequest = ( + ev: CustomEvent, + ): void => { + const newState = { + audio_enabled: audio.enabled, + video_enabled: video.enabled, + }; + if ( + ev.detail.data.audio_enabled != null && + typeof ev.detail.data.audio_enabled === "boolean" + ) { + audio.setEnabled?.(ev.detail.data.audio_enabled); + newState.audio_enabled = ev.detail.data.audio_enabled; + } + if ( + ev.detail.data.video_enabled != null && + typeof ev.detail.data.video_enabled === "boolean" + ) { + video.setEnabled?.(ev.detail.data.video_enabled); + newState.video_enabled = ev.detail.data.video_enabled; + } + widget!.api.transport.reply(ev.detail, newState); + }; + + widget.lazyActions.on( + ElementWidgetActions.DeviceMute, + onMuteStateChangeRequest, + ); + + return (): void => { + widget!.lazyActions.off( + ElementWidgetActions.DeviceMute, + onMuteStateChangeRequest, + ); + }; + } + }, [audio, video]); + return useMemo(() => ({ audio, video }), [audio, video]); } diff --git a/src/widget.ts b/src/widget.ts index 57e4d83b..af90cec4 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -46,6 +46,19 @@ export enum ElementWidgetActions { // host -> Element Call telling EC to stop screen sharing, or that // the user cancelled when selecting a source after a ScreenshareRequest ScreenshareStop = "io.element.screenshare_stop", + // This can be sent as form or to widget + // fromWidget: updates the client about the current device mute state + // toWidget: the client requests a specific device mute configuration + // (the reply will always be the resulting configuration) + // (it is possible to sent an empty configuration + // -> this will allow the client to only get the current state) + // + // The data of the widget action request and the response are: + // { + // audio_enabled?: boolean, + // video_enabled?: boolean + // } + DeviceMute = "io.element.device_mute", } export interface JoinCallData { @@ -88,6 +101,7 @@ export const widget = ((): WidgetHelpers | null => { ElementWidgetActions.SpotlightLayout, ElementWidgetActions.ScreenshareStart, ElementWidgetActions.ScreenshareStop, + ElementWidgetActions.DeviceMute, ].forEach((action) => { api.on(`action:${action}`, (ev: CustomEvent) => { ev.preventDefault();