From f3a880f1c31388eb79491fee7f70bde642fd5bf0 Mon Sep 17 00:00:00 2001 From: Will Hunt <2072976+Half-Shot@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:50:22 +0000 Subject: [PATCH] Support using Element Call for voice calls in DMs (#30817) * Add voiceOnly options. * tweaks * Nearly working demo * Lots of minor fixes * Better working version * remove unused payload * bits and pieces * Cleanup based on new hints * Simple refactor for skipLobby (and remove returnToLobby) * Tidyup * Remove unused tests * Update tests for voice calls * Add video room support. * Add a test for video rooms * tidy * remove console log line * lint and tests * Bunch of fixes * Fixes * Use correct title * make linter happier * Update tests * cleanup * Drop only * update snaps * Document * lint * Update snapshots * Remove duplicate test * add brackets * fix jest --- playwright/e2e/voip/element-call.spec.ts | 138 ++++++++++++++---- ...ming-call-dm-video-toast-checked-linux.png | Bin 13096 -> 12224 bytes ...ng-call-dm-video-toast-unchecked-linux.png | Bin 13389 -> 12519 bytes .../incoming-call-dm-voice-toast-linux.png | Bin 0 -> 10204 bytes .../incoming-call-group-voice-toast-linux.png | Bin 0 -> 11693 bytes res/css/views/rooms/_LiveContentSummary.pcss | 4 + .../roomlist/RoomListItemViewModel.tsx | 17 ++- .../views/rooms/LiveContentSummary.tsx | 18 +-- .../views/rooms/NotificationDecoration.tsx | 17 ++- .../rooms/RoomListPanel/RoomListItemView.tsx | 2 +- src/dispatcher/actions.ts | 2 +- src/dispatcher/payloads/ViewRoomPayload.ts | 1 + src/hooks/room/useRoomCall.tsx | 22 ++- src/i18n/strings/en_EN.json | 3 + src/models/Call.ts | 91 +++++++++++- src/stores/RoomViewStore.tsx | 4 +- src/toasts/IncomingCallToast.tsx | 83 +++++++---- src/utils/room/placeCall.ts | 4 +- test/test-utils/call.ts | 1 + .../rooms/NotificationDecoration-test.tsx | 28 ++-- .../RoomListPanel/RoomListItemView-test.tsx | 2 + .../RoomListItemView-test.tsx.snap | 11 ++ .../NotificationDecoration-test.tsx.snap | 24 ++- .../components/views/voip/CallView-test.tsx | 2 + .../toasts/IncomingCallToast-test.tsx | 3 + 25 files changed, 365 insertions(+), 112 deletions(-) create mode 100644 playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-voice-toast-linux.png create mode 100644 playwright/snapshots/voip/element-call.spec.ts/incoming-call-group-voice-toast-linux.png diff --git a/playwright/e2e/voip/element-call.spec.ts b/playwright/e2e/voip/element-call.spec.ts index 1911843046..b3c31be7ac 100644 --- a/playwright/e2e/voip/element-call.spec.ts +++ b/playwright/e2e/voip/element-call.spec.ts @@ -9,7 +9,7 @@ import type { EventType, Preset } from "matrix-js-sdk/src/matrix"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import { test, expect } from "../../element-web-test"; import type { Credentials } from "../../plugins/homeserver"; -import type { Bot } from "../../pages/bot"; +import { Bot } from "../../pages/bot"; function assertCommonCallParameters( url: URLSearchParams, @@ -27,27 +27,28 @@ function assertCommonCallParameters( expect(hash.get("preload")).toEqual("false"); } -async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "notification") { +async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "notification", intent?: string) { const resp = await bot.sendStateEvent( roomId, "org.matrix.msc3401.call.member", { - application: "m.call", - call_id: "", - device_id: "OiDFxsZrjz", - expires: 180000000, - foci_preferred: [ + "application": "m.call", + "call_id": "", + "m.call.intent": intent, + "device_id": "OiDFxsZrjz", + "expires": 180000000, + "foci_preferred": [ { livekit_alias: roomId, livekit_service_url: "https://example.org", type: "livekit", }, ], - focus_active: { + "focus_active": { focus_selection: "oldest_membership", type: "livekit", }, - scope: "m.room", + "scope": "m.room", }, `_@${bot.credentials.userId}_OiDFxsZrjz_m.call`, ); @@ -64,6 +65,7 @@ async function sendRTCState(bot: Bot, roomId: string, notification?: "ring" | "n event_id: resp.event_id, rel_type: "org.matrix.msc4075.rtc.notification.parent", }, + "m.call.intent": intent, "notification_type": notification, "sender_ts": 1758611895996, }); @@ -103,15 +105,21 @@ test.describe("Element Call", () => { }); test.describe("Group Chat", () => { + let charlie: Bot; test.use({ - room: async ({ page, app, user, bot }, use) => { - const roomId = await app.client.createRoom({ name: "TestRoom", invite: [bot.credentials.userId] }); + room: async ({ page, app, user, homeserver, bot }, use) => { + charlie = new Bot(page, homeserver, { displayName: "Charlie" }); + await charlie.prepareClient(); + const roomId = await app.client.createRoom({ + name: "TestRoom", + invite: [bot.credentials.userId, charlie.credentials.userId], + }); await use({ roomId }); }, }); test("should be able to start a video call", async ({ page, user, room, app }) => { await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob joined the room")).toBeVisible(); + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await page.getByRole("button", { name: "Video call" }).click(); await page.getByRole("menuitem", { name: "Element Call" }).click(); @@ -126,9 +134,16 @@ test.describe("Element Call", () => { expect(hash.get("skipLobby")).toEqual(null); }); + test("should NOT be able to start a voice call", async ({ page, user, room, app }) => { + // Voice calls do not exist in group rooms + await app.viewRoomById(room.roomId); + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); + await expect(page.getByRole("button", { name: "Voice call" })).not.toBeVisible(); + }); + test("should be able to skip lobby by holding down shift", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); - await expect(page.getByText("Bob joined the room")).toBeVisible(); + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await page.getByRole("button", { name: "Video call" }).click(); await page.keyboard.down("Shift"); @@ -147,8 +162,8 @@ test.describe("Element Call", () => { test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); // Allow bob to create a call + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); - await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call await sendRTCState(bot, room.roomId); const button = page.getByTestId("join-call-button"); @@ -156,7 +171,6 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -168,29 +182,29 @@ test.describe("Element Call", () => { [true, false].forEach((skipLobbyToggle) => { test( - `should be able to join a call via incoming call toast (skipLobby=${skipLobbyToggle})`, + `should be able to join a call via incoming video call toast (skipLobby=${skipLobbyToggle})`, { tag: ["@screenshot"] }, async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); // Allow bob to create a call + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); - await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call - await sendRTCState(bot, room.roomId, "notification"); + await sendRTCState(bot, room.roomId, "notification", "video"); const toast = page.locator(".mx_Toast_toast"); const button = toast.getByRole("button", { name: "Join" }); + if (skipLobbyToggle) { await toast.getByRole("switch").check(); - await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-checked.png"); + await expect(toast).toMatchScreenshot(`incoming-call-group-video-toast-checked.png`); } else { await toast.getByRole("switch").uncheck(); - await expect(toast).toMatchScreenshot("incoming-call-group-video-toast-unchecked.png"); + await expect(toast).toMatchScreenshot(`incoming-call-group-video-toast-unchecked.png`); } // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -201,6 +215,34 @@ test.describe("Element Call", () => { }, ); }); + + test( + `should be able to join a call via incoming voice call toast`, + { tag: ["@screenshot"] }, + async ({ page, user, bot, room, app }) => { + await app.viewRoomById(room.roomId); + // Allow bob to create a call + await expect(page.getByText("Bob and one other were invited and joined")).toBeVisible(); + await app.client.setPowerLevel(room.roomId, bot.credentials.userId, 50); + // Fake a start of a call + await sendRTCState(bot, room.roomId, "notification", "audio"); + const toast = page.locator(".mx_Toast_toast"); + const button = toast.getByRole("button", { name: "Join" }); + + await expect(toast).toMatchScreenshot(`incoming-call-group-voice-toast.png`); + + // And test joining + await button.click(); + const frameUrlStr = await page.locator("iframe").getAttribute("src"); + await expect(frameUrlStr).toBeDefined(); + const url = new URL(frameUrlStr); + const hash = new URLSearchParams(url.hash.slice(1)); + assertCommonCallParameters(url.searchParams, hash, user, room); + + expect(hash.get("intent")).toEqual("join_existing"); + expect(hash.get("skipLobby")).toEqual("true"); + }, + ); }); test.describe("DMs", () => { @@ -253,7 +295,6 @@ test.describe("Element Call", () => { test("should be able to join a call in progress", async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); - // Allow bob to create a call await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call await sendRTCState(bot, room.roomId); @@ -262,7 +303,6 @@ test.describe("Element Call", () => { // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -278,24 +318,31 @@ test.describe("Element Call", () => { { tag: ["@screenshot"] }, async ({ page, user, bot, room, app }) => { await app.viewRoomById(room.roomId); - // Allow bob to create a call await expect(page.getByText("Bob joined the room")).toBeVisible(); // Fake a start of a call - await sendRTCState(bot, room.roomId, "ring"); + await sendRTCState(bot, room.roomId, "ring", "video"); const toast = page.locator(".mx_Toast_toast"); - const button = toast.getByRole("button", { name: "Join" }); + const button = toast.getByRole("button", { name: "Accept" }); if (skipLobbyToggle) { await toast.getByRole("switch").check(); - await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-checked.png"); } else { await toast.getByRole("switch").uncheck(); - await expect(toast).toMatchScreenshot("incoming-call-dm-video-toast-unchecked.png"); } + await expect(toast).toMatchScreenshot( + `incoming-call-dm-video-toast-${skipLobbyToggle ? "checked" : "unchecked"}.png`, + { + // Hide UserId + css: ` + .mx_IncomingCallToast_AvatarWithDetails span:nth-child(2) { + opacity: 0; + } + `, + }, + ); // And test joining await button.click(); const frameUrlStr = await page.locator("iframe").getAttribute("src"); - console.log(frameUrlStr); await expect(frameUrlStr).toBeDefined(); const url = new URL(frameUrlStr); const hash = new URLSearchParams(url.hash.slice(1)); @@ -306,6 +353,39 @@ test.describe("Element Call", () => { }, ); }); + + test( + `should be able to join a call via incoming voice call toast`, + { tag: ["@screenshot"] }, + async ({ page, user, bot, room, app }) => { + await app.viewRoomById(room.roomId); + await expect(page.getByText("Bob joined the room")).toBeVisible(); + // Fake a start of a call + await sendRTCState(bot, room.roomId, "ring", "audio"); + const toast = page.locator(".mx_Toast_toast"); + const button = toast.getByRole("button", { name: "Accept" }); + + await expect(toast).toMatchScreenshot(`incoming-call-dm-voice-toast.png`, { + // Hide UserId + css: ` + .mx_IncomingCallToast_AvatarWithDetails span:nth-child(2) { + opacity: 0; + } + `, + }); + + // And test joining + await button.click(); + const frameUrlStr = await page.locator("iframe").getAttribute("src"); + await expect(frameUrlStr).toBeDefined(); + const url = new URL(frameUrlStr); + const hash = new URLSearchParams(url.hash.slice(1)); + assertCommonCallParameters(url.searchParams, hash, user, room); + + expect(hash.get("intent")).toEqual("join_existing_dm_voice"); + expect(hash.get("skipLobby")).toEqual("true"); + }, + ); }); test.describe("Video Rooms", () => { diff --git a/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png b/playwright/snapshots/voip/element-call.spec.ts/incoming-call-dm-video-toast-checked-linux.png index f309c37f3ae93c5647035cc302d29e1df3aed11e..b0fd216c56ee4b53f9081ed02472c9be92695ce8 100644 GIT binary patch literal 12224 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 Ks}V5>_&)&g!8qCg literal 13389 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

$iYOpXt!}&8ohfZ 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 BQ*!_S literal 0 HcmV?d00001 diff --git a/res/css/views/rooms/_LiveContentSummary.pcss b/res/css/views/rooms/_LiveContentSummary.pcss index 0736f0dd45..a367e7e75d 100644 --- a/res/css/views/rooms/_LiveContentSummary.pcss +++ b/res/css/views/rooms/_LiveContentSummary.pcss @@ -25,6 +25,10 @@ Please see LICENSE files in the repository root for full details. mask-image: url("$(res)/img/element-icons/call/video-call.svg"); } + &.mx_LiveContentSummary_text_voice::before { + mask-image: url("$(res)/img/element-icons/call/voice-call.svg"); + } + &.mx_LiveContentSummary_text_active { color: $accent; diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx index 107d1a4c60..30576e2dc2 100644 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -7,6 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; import dispatcher from "../../../dispatcher/dispatcher"; import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; @@ -19,7 +20,7 @@ import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useEventEmitter, useEventEmitterState, useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { DefaultTagID } from "../../../stores/room-list/models"; import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; -import { type ConnectionState } from "../../../models/Call"; +import { CallEvent, type ConnectionState } from "../../../models/Call"; import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; import DMRoomMap from "../../../utils/DMRoomMap"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; @@ -67,6 +68,10 @@ export interface RoomListItemViewState { * Whether there are participants in the call. */ hasParticipantInCall: boolean; + /** + * Whether the call is a voice or video call. + */ + callType: CallType | undefined; /** * Pre-rendered and translated preview for the latest message in the room, or undefined * if no preview should be shown. @@ -123,10 +128,10 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { // EC video call or video room const call = useCall(room.roomId); const connectionState = useConnectionState(call); - const hasParticipantInCall = useParticipantCount(call) > 0; + const participantCount = useParticipantCount(call); const callConnectionState = call ? connectionState : null; - const showNotificationDecoration = hasVisibleNotification || hasParticipantInCall; + const showNotificationDecoration = hasVisibleNotification || participantCount > 0; // Actions @@ -138,6 +143,9 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { }); }, [room]); + const [callType, setCallType] = useState(CallType.Video); + useTypedEventEmitter(call ?? undefined, CallEvent.CallTypeChanged, setCallType); + return { name, notificationState, @@ -148,9 +156,10 @@ export function useRoomListItemViewModel(room: Room): RoomListItemViewState { isBold, isVideoRoom, callConnectionState, - hasParticipantInCall, + hasParticipantInCall: participantCount > 0, messagePreview, showNotificationDecoration, + callType: call ? callType : undefined, }; } diff --git a/src/components/views/rooms/LiveContentSummary.tsx b/src/components/views/rooms/LiveContentSummary.tsx index 1f5d36e8d0..ada3209f07 100644 --- a/src/components/views/rooms/LiveContentSummary.tsx +++ b/src/components/views/rooms/LiveContentSummary.tsx @@ -10,12 +10,10 @@ import React, { type FC } from "react"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; -import { type Call } from "../../../models/Call"; -import { useParticipantCount } from "../../../hooks/useCall"; export enum LiveContentType { Video, - // More coming soon + Voice, } interface Props { @@ -33,6 +31,7 @@ export const LiveContentSummary: FC = ({ type, text, active, participantC @@ -51,16 +50,3 @@ export const LiveContentSummary: FC = ({ type, text, active, participantC )} ); - -interface LiveContentSummaryWithCallProps { - call: Call; -} - -export const LiveContentSummaryWithCall: FC = ({ call }) => ( - -); diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index 5dda3b0453..321625adb1 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -12,6 +12,8 @@ import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/ic import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; +import VoiceCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/voice-call-solid"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { Flex } from "@element-hq/web-shared-components"; import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState"; @@ -24,9 +26,9 @@ interface NotificationDecorationProps extends HTMLProps { */ notificationState: RoomNotificationState; /** - * Whether the room has a video call. + * Whether the room has a voice or video call. */ - hasVideoCall: boolean; + callType?: CallType; } /** @@ -34,7 +36,7 @@ interface NotificationDecorationProps extends HTMLProps { */ export function NotificationDecoration({ notificationState, - hasVideoCall, + callType, ...props }: NotificationDecorationProps): JSX.Element | null { // Listen to the notification state and update the component when it changes @@ -58,7 +60,7 @@ export function NotificationDecoration({ muted: notificationState.muted, })); - if (!hasAnyNotificationOrActivity && !muted && !hasVideoCall) return null; + if (!hasAnyNotificationOrActivity && !muted && !callType) return null; return ( {isUnsentMessage && } - {hasVideoCall && } + {callType === CallType.Video && ( + + )} + {callType === CallType.Voice && ( + + )} {invited && } {isMention && } {(isMention || isNotification) && } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index 0061ac05ef..22f5a06c1b 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -132,7 +132,7 @@ export const RoomListItemView = memo(function RoomListItemView({ )} diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index b7af7124fb..99e6a7d0f1 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -121,7 +121,7 @@ export enum Action { UpdateSystemFont = "update_system_font", /** - * Changes room based on payload parameters. Should be used with JoinRoomPayload. + * Changes room based on payload parameters. Should be used with ViewRoomPayload. */ ViewRoom = "view_room", diff --git a/src/dispatcher/payloads/ViewRoomPayload.ts b/src/dispatcher/payloads/ViewRoomPayload.ts index 525dd50d62..c1dba33feb 100644 --- a/src/dispatcher/payloads/ViewRoomPayload.ts +++ b/src/dispatcher/payloads/ViewRoomPayload.ts @@ -39,6 +39,7 @@ interface BaseViewRoomPayload extends Pick { clear_search?: boolean; // Whether to clear the room list search view_call?: boolean; // Whether to view the call or call lobby for the room skipLobby?: boolean; // Whether to skip the call lobby when showing the call (only supported for element calls) + voiceOnly?: boolean; // Whether the call is voice only (only supported for element calls) opts?: JoinRoomPayload["opts"]; deferred_action?: ActionPayload; // Action to fire after MatrixChat handles this ViewRoom action diff --git a/src/hooks/room/useRoomCall.tsx b/src/hooks/room/useRoomCall.tsx index 9f72430492..97b30dea0d 100644 --- a/src/hooks/room/useRoomCall.tsx +++ b/src/hooks/room/useRoomCall.tsx @@ -142,11 +142,6 @@ export const useRoomCall = ( // If there are multiple options, the user will be prompted to choose. const callOptions = useMemo((): PlatformCallType[] => { const options: PlatformCallType[] = []; - if (memberCount <= 2) { - options.push(PlatformCallType.LegacyCall); - } else if (mayEditWidgets || hasJitsiWidget) { - options.push(PlatformCallType.JitsiCall); - } if (groupCallsEnabled) { if (hasGroupCall || mayCreateElementCalls) { options.push(PlatformCallType.ElementCall); @@ -155,6 +150,11 @@ export const useRoomCall = ( return [PlatformCallType.ElementCall]; } } + if (memberCount <= 2) { + options.push(PlatformCallType.LegacyCall); + } else if (mayEditWidgets || hasJitsiWidget) { + options.push(PlatformCallType.JitsiCall); + } if (hasGroupCall && WidgetType.CALL.matches(groupCall.widget.type)) { // only allow joining the ongoing Element call if there is one. return [PlatformCallType.ElementCall]; @@ -231,7 +231,7 @@ export const useRoomCall = ( if (widget && promptPinWidget) { WidgetLayoutStore.instance.moveToContainer(room, widget, Container.Top); } else { - placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined); + placeCall(room, CallType.Voice, callPlatformType, evt?.shiftKey || undefined, true); } }, [promptPinWidget, room, widget], @@ -244,7 +244,7 @@ export const useRoomCall = ( } else { // If we have pressed shift then always skip the lobby, otherwise `undefined` will defer // to the defaults of the call implementation. - placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined); + placeCall(room, CallType.Video, callPlatformType, evt?.shiftKey || undefined, false); } }, [widget, promptPinWidget, room], @@ -279,7 +279,13 @@ export const useRoomCall = ( const roomDoesNotExist = room instanceof LocalRoom && room.state !== LocalRoomState.CREATED; // We hide the voice call button if it'd have the same effect as the video call button - let hideVoiceCallButton = isManagedHybridWidgetEnabled(room) || !callOptions.includes(PlatformCallType.LegacyCall); + let hideVoiceCallButton = + isManagedHybridWidgetEnabled(room) || + // Disable voice calls if Legacy calls are disabled + (!callOptions.includes(PlatformCallType.LegacyCall) && + // Disable voice calls in ECall if the room is a group (we only present video calls for groups of users) + (!callOptions.includes(PlatformCallType.ElementCall) || memberCount > 2)); + let hideVideoCallButton = false; // We hide both buttons if: // - they require widgets but widgets are disabled diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86f9197e92..847c9b3f20 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -603,6 +603,7 @@ "video": "Video", "video_room": "Video room", "view_message": "View message", + "voice": "Voice", "warning": "Warning" }, "composer": { @@ -4096,9 +4097,11 @@ "user_busy_description": "The user you called is busy.", "user_is_presenting": "%(sharerName)s is presenting", "video_call": "Video call", + "video_call_incoming": "Incoming video call", "video_call_started": "Video call started", "video_call_using": "Video call using:", "voice_call": "Voice call", + "voice_call_incoming": "Incoming voice call", "you_are_presenting": "You are presenting" }, "web_default_device_name": "%(appName)s: %(browserName)s on %(osName)s", diff --git a/src/models/Call.ts b/src/models/Call.ts index a5fbc8d0bd..1882eec964 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -84,6 +84,7 @@ export enum CallEvent { Participants = "participants", Close = "close", Destroy = "destroy", + CallTypeChanged = "call_type_changed", } interface CallEventHandlerMap { @@ -94,6 +95,7 @@ interface CallEventHandlerMap { ) => void; [CallEvent.Close]: () => void; [CallEvent.Destroy]: () => void; + [CallEvent.CallTypeChanged]: (callType: CallType) => void; } /** @@ -103,6 +105,18 @@ export abstract class Call extends TypedEventEmitter params.append("font", font)); } this.appendAnalyticsParams(params, client); - this.appendRoomParams(params, client, roomId); + this.appendRoomParams(params, client, roomId, opts); const replacedUrl = params.toString().replace(/%24/g, "$"); url.hash = `#?${replacedUrl}`; @@ -751,11 +791,43 @@ export class ElementCall extends Call { ); } + /** + * Get the correct intent for a widget, so that Element Call presents the correct + * default config. + * @param client The matrix client. + * @param roomId + * @param voiceOnly Should the call be voice-only, or video (default). + */ + public static getWidgetIntent(client: MatrixClient, roomId: string, voiceOnly?: boolean): ElementCallIntent { + const room = client.getRoom(roomId); + if (room !== null && !isVideoRoom(room)) { + const isDM = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); + const oldestCallMember = client.matrixRTC.getRoomSession(room).getOldestMembership(); + const hasCallStarted = !!oldestCallMember && oldestCallMember.sender !== client.getSafeUserId(); + if (isDM) { + if (hasCallStarted) { + return voiceOnly ? ElementCallIntent.JoinExistingDMVoice : ElementCallIntent.JoinExistingDM; + } else { + return voiceOnly ? ElementCallIntent.StartCallDMVoice : ElementCallIntent.StartCallDM; + } + } else { + if (hasCallStarted) { + return ElementCallIntent.JoinExisting; + } else { + return ElementCallIntent.StartCall; + } + } + } + // If unknown, default to joining an existing call. + return ElementCallIntent.JoinExisting; + } + private static getWidgetData( client: MatrixClient, roomId: string, currentData: IWidgetData, overwriteData: IWidgetData, + voiceOnly?: boolean, ): IWidgetData { let perParticipantE2EE = false; if ( @@ -763,9 +835,13 @@ export class ElementCall extends Call { !SettingsStore.getValue("feature_disable_call_per_sender_encryption") ) perParticipantE2EE = true; + + const intent = ElementCall.getWidgetIntent(client, roomId, voiceOnly); + return { ...currentData, ...overwriteData, + intent, perParticipantE2EE, }; } @@ -791,7 +867,7 @@ export class ElementCall extends Call { this.updateParticipants(); } - public static get(room: Room): ElementCall | null { + public static get(room: Room, voiceOnly?: boolean): ElementCall | null { const apps = WidgetStore.instance.getApps(room.roomId); const hasEcWidget = apps.some((app) => WidgetType.CALL.matches(app.type)); const session = room.client.matrixRTC.getRoomSession(room); @@ -874,7 +950,10 @@ export class ElementCall extends Call { if (this.session.memberships.length === 0 && !this.presented && !this.room.isCallRoom()) this.destroy(); }; - private readonly onMembershipChanged = (): void => this.updateParticipants(); + private readonly onMembershipChanged = (): void => { + this.updateParticipants(); + this.callType = this.session.getConsensusCallIntent() === "audio" ? CallType.Voice : CallType.Video; + }; private updateParticipants(): void { const participants = new Map>(); diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index ee7c2ad9bc..017e1c4b66 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -365,7 +365,9 @@ export class RoomViewStore extends EventEmitter { call.presented = true; // Immediately start the call. This will connect to all required widget events // and allow the widget to show the lobby. - if (call.connectionState === ConnectionState.Disconnected) call.start({ skipLobby: payload.skipLobby }); + if (call.connectionState === ConnectionState.Disconnected) { + call.start({ skipLobby: payload.skipLobby, voiceOnly: payload.voiceOnly }); + } } // If we switch to a different room from the call, we are no longer presenting it const prevRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null; diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index f2de9aedc1..523788e8f5 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -14,6 +14,7 @@ import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check" import CrossIcon from "@vector-im/compound-design-tokens/assets/web/icons/close"; import { logger } from "matrix-js-sdk/src/logger"; import { type IRTCNotificationContent } from "matrix-js-sdk/src/matrixrtc"; +import { VoiceCallIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { AvatarWithDetails } from "@element-hq/web-shared-components"; import { _t } from "../languageHandler"; @@ -23,12 +24,8 @@ import defaultDispatcher from "../dispatcher/dispatcher"; import { type ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; import { Action } from "../dispatcher/actions"; import ToastStore from "../stores/ToastStore"; -import { - LiveContentSummary, - LiveContentSummaryWithCall, - LiveContentType, -} from "../components/views/rooms/LiveContentSummary"; -import { useCall, useJoinCallButtonDisabledTooltip } from "../hooks/useCall"; +import { LiveContentSummary, LiveContentType } from "../components/views/rooms/LiveContentSummary"; +import { useCall, useJoinCallButtonDisabledTooltip, useParticipantCount } from "../hooks/useCall"; import AccessibleButton, { type ButtonEvent } from "../components/views/elements/AccessibleButton"; import { useDispatcher } from "../hooks/useDispatcher"; import { type ActionPayload } from "../dispatcher/payloads"; @@ -36,6 +33,7 @@ import { type Call, CallEvent } from "../models/Call"; import LegacyCallHandler, { AudioID } from "../LegacyCallHandler"; import { useEventEmitter } from "../hooks/useEventEmitter"; import { CallStore, CallStoreEvent } from "../stores/CallStore"; +import DMRoomMap from "../utils/DMRoomMap"; /** * Get the key for the incoming call toast. A combination of the event ID and room ID. @@ -71,9 +69,15 @@ interface JoinCallButtonWithCallProps { onClick: (e: ButtonEvent) => void; call: Call | null; disabledTooltip: string | undefined; + isRinging: boolean; } -function JoinCallButtonWithCall({ onClick, call, disabledTooltip }: JoinCallButtonWithCallProps): JSX.Element { +function JoinCallButtonWithCall({ + onClick, + call, + disabledTooltip, + isRinging, +}: JoinCallButtonWithCallProps): JSX.Element { let disTooltip = disabledTooltip; const disabledBecauseFullTooltip = useJoinCallButtonDisabledTooltip(call); disTooltip = disabledTooltip ?? disabledBecauseFullTooltip ?? undefined; @@ -88,7 +92,7 @@ function JoinCallButtonWithCall({ onClick, call, disabledTooltip }: JoinCallButt Icon={CheckIcon} size="sm" > - {_t("action|join")} + {isRinging ? _t("action|accept") : _t("action|join")} ); @@ -152,7 +156,7 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { // This section can race, so we use a ref to keep track of whether we have started trying to play. // This is because `LegacyCallHandler.play` tries to load the sound and then play it asynchonously // and `LegacyCallHandler.isPlaying` will not be `true` until the sound starts playing. - const isRingToast = notificationContent.notification_type == "ring"; + const isRingToast = notificationContent.notification_type === "ring"; if (isRingToast && !soundHasStarted.current && !LegacyCallHandler.instance.isPlaying(AudioID.Ring)) { // Start ringing if not already. soundHasStarted.current = true; @@ -243,10 +247,11 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { room_id: room?.roomId, view_call: true, skipLobby: ("shiftKey" in e && e.shiftKey) || skipLobbyToggle, + voiceOnly: notificationContent["m.call.intent"] === "audio", metricsTrigger: undefined, }); }, - [room, skipLobbyToggle], + [room, skipLobbyToggle, notificationContent], ); // Dismiss on closing toast. @@ -262,34 +267,53 @@ export function IncomingCallToast({ notificationEvent }: Props): JSX.Element { useEventEmitter(CallStore.instance, CallStoreEvent.Call, onCall); useEventEmitter(call ?? undefined, CallEvent.Participants, onParticipantChange); useEventEmitter(room, RoomEvent.Timeline, onTimelineChange); + const isVoice = notificationContent["m.call.intent"] === "audio"; + const otherUserId = DMRoomMap.shared().getUserIdForRoomId(roomId); + const participantCount = useParticipantCount(call); + const detailsInformation = + notificationContent.notification_type === "ring" ? ( + {otherUserId} + ) : ( + + ); - const callLiveContentSummary = call ? ( - - ) : ( - - ); return ( <>

-
- {" "} - {_t("voip|video_call_started")} -
+ {isVoice ? ( +
+ {" "} + {_t("voip|voice_call_incoming")} +
+ ) : ( +
+ {" "} + {notificationContent.notification_type === "ring" + ? _t("voip|video_call_incoming") + : _t("voip|video_call_started")} +
+ )} } - details={callLiveContentSummary} + details={detailsInformation} title={room ? room.name : _t("voip|call_toast_unknown_room")} + className="mx_IncomingCallToast_AvatarWithDetails" /> -
- {_t("voip|skip_lobby_toggle_option")} - setSkipLobbyToggle(e.target.checked)} checked={skipLobbyToggle} /> -
+ {!isVoice && ( +
+ {_t("voip|skip_lobby_toggle_option")} + setSkipLobbyToggle(e.target.checked)} + checked={skipLobbyToggle} + /> +
+ )}
diff --git a/src/utils/room/placeCall.ts b/src/utils/room/placeCall.ts index 590ded7a80..6c046116ce 100644 --- a/src/utils/room/placeCall.ts +++ b/src/utils/room/placeCall.ts @@ -27,7 +27,8 @@ export const placeCall = async ( room: Room, callType: CallType, platformCallType: PlatformCallType, - skipLobby?: boolean, + skipLobby: boolean | undefined, + voiceOnly: boolean, ): Promise => { const { analyticsName } = getPlatformCallTypeProps(platformCallType); PosthogTrackers.trackInteraction(analyticsName); @@ -39,6 +40,7 @@ export const placeCall = async ( action: Action.ViewRoom, room_id: room.roomId, view_call: true, + voiceOnly, skipLobby, metricsTrigger: undefined, }); diff --git a/test/test-utils/call.ts b/test/test-utils/call.ts index c122d1d756..90ef5d1ea2 100644 --- a/test/test-utils/call.ts +++ b/test/test-utils/call.ts @@ -181,6 +181,7 @@ export function setUpClientRoomAndStores(): { const roomSession = new MockEventEmitter({ memberships: [], getOldestMembership: jest.fn().mockReturnValue(undefined), + getConsensusCallIntent: jest.fn().mockReturnValue(undefined), room, }) as Mocked; diff --git a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx index c54e16215e..deeab46bc5 100644 --- a/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx +++ b/test/unit-tests/components/views/rooms/NotificationDecoration-test.tsx @@ -7,6 +7,7 @@ import React from "react"; import { render, screen } from "jest-matrix-react"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState"; import { NotificationDecoration } from "../../../../../src/components/views/rooms/NotificationDecoration"; @@ -22,7 +23,7 @@ describe("", () => { it("should not render if RoomNotificationState.hasAnyNotificationOrActivity=true", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); - render(); + render(); expect(screen.queryByTestId("notification-decoration")).toBeNull(); }); @@ -30,7 +31,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "isUnsentMessage", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -39,7 +40,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "invited", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -49,7 +50,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isMention", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -59,7 +60,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(1); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -69,7 +70,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "isNotification", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "count", "get").mockReturnValue(0); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -78,7 +79,7 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "isActivityNotification", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); @@ -87,14 +88,21 @@ describe("", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(true); jest.spyOn(roomNotificationState, "muted", "get").mockReturnValue(true); const { asFragment } = render( - , + , ); expect(asFragment()).toMatchSnapshot(); }); - it("should render the video decoration", () => { + it("should render the video call decoration", () => { jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); const { asFragment } = render( - , + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + it("should render the audio call decoration", () => { + jest.spyOn(roomNotificationState, "hasAnyNotificationOrActivity", "get").mockReturnValue(false); + const { asFragment } = render( + , ); expect(asFragment()).toMatchSnapshot(); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index b5916402f3..499573f8a7 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -10,6 +10,7 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; import { render, screen, waitFor } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { mocked } from "jest-mock"; +import { CallType } from "matrix-js-sdk/src/webrtc/call"; import { mkRoom, stubClient, withClientContextRenderOptions } from "../../../../../test-utils"; import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView"; @@ -64,6 +65,7 @@ describe("", () => { isBold: false, isVideoRoom: false, callConnectionState: null, + callType: CallType.Video, hasParticipantInCall: false, name: room.name, showNotificationDecoration: false, diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap index da4909b9d2..6cbcbe3263 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -103,6 +103,17 @@ exports[` should display notification decoration 1`] = ` data-testid="notification-decoration" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1x); --mx-flex-wrap: nowrap;" > + + + diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap index e8b4c46320..4eff2799e4 100644 --- a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap @@ -16,6 +16,28 @@ exports[` should render the activity decoration 1`] = `; +exports[` should render the audio call decoration 1`] = ` + +
+ + + +
+
+`; + exports[` should render the invitation decoration 1`] = `
should render the unset message decoration 1 `; -exports[` should render the video decoration 1`] = ` +exports[` should render the video call decoration 1`] = `
{ stubClient(); client = mocked(MatrixClientPeg.safeGet()); + DMRoomMap.makeShared(client); room = new Room("!1:example.org", client, "@alice:example.org", { pendingEventOrdering: PendingEventOrdering.Detached, diff --git a/test/unit-tests/toasts/IncomingCallToast-test.tsx b/test/unit-tests/toasts/IncomingCallToast-test.tsx index 3ac1d89548..fd6508c228 100644 --- a/test/unit-tests/toasts/IncomingCallToast-test.tsx +++ b/test/unit-tests/toasts/IncomingCallToast-test.tsx @@ -188,6 +188,7 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: true, view_call: true, + voiceOnly: false, }), ); await waitFor(() => @@ -215,6 +216,7 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: false, view_call: true, + voiceOnly: false, }), ); await waitFor(() => @@ -239,6 +241,7 @@ describe("IncomingCallToast", () => { room_id: room.roomId, skipLobby: true, view_call: true, + voiceOnly: false, }), ); await waitFor(() =>