diff --git a/src/models/Call.ts b/src/models/Call.ts index 7c42719563..147642d873 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -892,6 +892,10 @@ export class ElementCall extends Call { this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); + this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, async (ev) => { + ev.preventDefault(); + await this.messaging!.transport.reply(ev.detail, {}); // ack + }); if (!this.widget.data?.skipLobby) { // If we do not skip the lobby we need to wait until the widget has diff --git a/src/stores/widgets/ElementWidgetActions.ts b/src/stores/widgets/ElementWidgetActions.ts index 36b6e2b6fc..593f181843 100644 --- a/src/stores/widgets/ElementWidgetActions.ts +++ b/src/stores/widgets/ElementWidgetActions.ts @@ -21,10 +21,6 @@ export enum ElementWidgetActions { JoinCall = "io.element.join", HangupCall = "im.vector.hangup", CallParticipants = "io.element.participants", - MuteAudio = "io.element.mute_audio", - UnmuteAudio = "io.element.unmute_audio", - MuteVideo = "io.element.mute_video", - UnmuteVideo = "io.element.unmute_video", StartLiveStream = "im.vector.start_live_stream", // Actions for switching layouts @@ -32,11 +28,28 @@ export enum ElementWidgetActions { SpotlightLayout = "io.element.spotlight_layout", OpenIntegrationManager = "integration_manager_open", - /** * @deprecated Use MSC2931 instead */ ViewRoom = "io.element.view_room", + + // This action type is used as a `fromWidget` and a `toWidget` action. + // 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 + // } + // NOTE: this is currently unused. Its only here to make EW aware + // of this action so it does not throw errors. + DeviceMute = "io.element.device_mute", } export interface IHangupCallApiRequest extends IWidgetApiRequest { diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index 5748b507ac..85f0700cb1 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -965,6 +965,18 @@ describe("ElementCall", () => { expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.TileLayout, {}); }); + it("acknowledges mute_device widget action", async () => { + await callConnectProcedure(call); + const preventDefault = jest.fn(); + const mockEv = { + preventDefault, + detail: { video_enabled: false }, + }; + messaging.emit(`action:${ElementWidgetActions.DeviceMute}`, mockEv); + expect(messaging.transport.reply).toHaveBeenCalledWith({ video_enabled: false }, {}); + expect(preventDefault).toHaveBeenCalled(); + }); + it("emits events when connection state changes", async () => { // const wait = jest.spyOn(CallModule, "waitForEvent"); const onConnectionState = jest.fn();