Compare commits

...

5 Commits

Author SHA1 Message Date
Timo
e222dfefa4 use useCallback 2024-07-29 16:24:47 +02:00
Timo
9a4376b35b add comments 2024-07-29 13:47:22 +02:00
Timo
2271afe08d review 2024-07-18 10:34:55 +02:00
Timo
e539edbb3d review + better explanation 2024-07-17 16:34:01 +02:00
Timo
71eb9ae557 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.
2024-07-17 15:22:29 +02:00
2 changed files with 82 additions and 1 deletions

View File

@@ -14,10 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Dispatch, SetStateAction, useMemo } from "react";
import {
Dispatch,
SetStateAction,
useCallback,
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 +82,62 @@ 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]);
const onMuteStateChangeRequest = useCallback(
(ev: CustomEvent<IWidgetApiRequest>) => {
// First copy the current state into our new state.
const newState = {
audio_enabled: audio.enabled,
video_enabled: video.enabled,
};
// Update new state if there are any requested changes from the widget action
// in `ev.detail.data`.
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;
}
// Always reply with the new (now "current") state.
// This allows to also use this action to just get the unaltered current state
// by using a fromWidget request with: `ev.detail.data = {}`
widget!.api.transport.reply(ev.detail, newState);
},
[audio, video],
);
useEffect(() => {
// We setup a event listener for the widget action ElementWidgetActions.DeviceMute.
if (widget) {
// only setup the listener in widget mode
widget.lazyActions.on(
ElementWidgetActions.DeviceMute,
onMuteStateChangeRequest,
);
return (): void => {
// return a call to `off` so that we always clean up our listener.
widget?.lazyActions.off(
ElementWidgetActions.DeviceMute,
onMuteStateChangeRequest,
);
};
}
}, [onMuteStateChangeRequest]);
return useMemo(() => ({ audio, video }), [audio, video]);
}

View File

@@ -46,6 +46,21 @@ 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 from 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 to retrieve the current values or
// just one of the fields to update that particular value
// An undefined field means that EC will keep the mute state as is.
// -> 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 +103,7 @@ export const widget = ((): WidgetHelpers | null => {
ElementWidgetActions.SpotlightLayout,
ElementWidgetActions.ScreenshareStart,
ElementWidgetActions.ScreenshareStop,
ElementWidgetActions.DeviceMute,
].forEach((action) => {
api.on(`action:${action}`, (ev: CustomEvent<IWidgetApiRequest>) => {
ev.preventDefault();