From 62bc420df948a7500484d671cbe8d716ad19ac7e Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 15 May 2026 16:53:49 +0200 Subject: [PATCH 1/3] WIP on audio route experiment --- src/UrlParams.ts | 3 + src/button/AudioRouteButton.tsx | 60 +++++++++++++++++++ src/components/CallFooter.tsx | 37 +++++++++--- src/controls.ts | 29 +++++++++ src/main.tsx | 2 +- src/room/InCallView.tsx | 2 + src/room/LobbyView.tsx | 43 +++++++++++++ src/state/CallViewModel/CallViewModel.ts | 41 +++++++++++-- .../CallViewModel/localMember/Publisher.ts | 31 +++++++--- src/state/IOSControlledAudioOutput.ts | 1 + src/state/IOSNativeControlledAudioOutput.ts | 57 ++++++++++++++++++ src/state/MediaDevices.ts | 7 ++- 12 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 src/button/AudioRouteButton.tsx create mode 100644 src/state/IOSNativeControlledAudioOutput.ts diff --git a/src/UrlParams.ts b/src/UrlParams.ts index f4ea840dee..1ed4ef7af1 100644 --- a/src/UrlParams.ts +++ b/src/UrlParams.ts @@ -196,6 +196,7 @@ export interface UrlConfiguration { * Element Call. */ controlledAudioDevices: boolean; + audioInputOutputSelection: boolean; /** * Setting this flag skips the lobby and brings you in the call directly. * In the widget this can be combined with preload to pass the device settings @@ -372,6 +373,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => { allowIceFallback: true, perParticipantE2EE: true, controlledAudioDevices: platform === "desktop" ? false : true, + audioInputOutputSelection: platform !== "ios", skipLobby: true, returnToLobby: false, sendNotificationType: "notification", @@ -427,6 +429,7 @@ export const computeUrlParams = (search = "", hash = ""): UrlParams => { allowIceFallback: false, perParticipantE2EE: false, controlledAudioDevices: false, + audioInputOutputSelection: true, skipLobby: false, returnToLobby: false, sendNotificationType: undefined, diff --git a/src/button/AudioRouteButton.tsx b/src/button/AudioRouteButton.tsx new file mode 100644 index 0000000000..13073353c0 --- /dev/null +++ b/src/button/AudioRouteButton.tsx @@ -0,0 +1,60 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { useTranslation } from "react-i18next"; +import { Button, Tooltip } from "@vector-im/compound-web"; +import { + EarpieceIcon, + HeadphonesSolidIcon, + VolumeOnSolidIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; + +import type { ComponentPropsWithoutRef, FC } from "react"; +import { RouteType } from "../controls.ts"; + +interface AudioRouteButtonProps extends ComponentPropsWithoutRef<"button"> { + size?: "md" | "lg"; + routeType: RouteType; +} + +export const AudioRouteButton: FC = ({ + routeType, + ...props +}) => { + const { t } = useTranslation(); + let label: string + let icon; + switch(routeType) { + case RouteType.speaker: + label = t("settings.devices.loudspeaker") + icon = VolumeOnSolidIcon; + break; + case RouteType.phone: + label = t("settings.devices.handset"); + icon = EarpieceIcon + break; + case RouteType.bluetooth: + label = "bluetooth headset"; + icon = HeadphonesSolidIcon; + break; + case RouteType.wired: + label = "headset"; + icon = HeadphonesSolidIcon; + break; + } + + return ( + +