From aff01d042358fb504c2e16ea5f4977e5bface277 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 2 Oct 2023 17:35:04 +0100 Subject: [PATCH 1/3] Show the device that's actually in use Feeds the current track (if any), and therefore device it's using into the device selection dropdown and implement a some somewhat complex logic to observe when the 'default' device has changed to a different actual devbice between when we opened it and now, and display the device we're actually using as selected instead. The comments should explain more, including the caveat that we can't do the same detective work if the device is one of several devices on the same bit of hardware. This is because is uses the same groupId comparison technique used by Livekit in `normalizeDeviceId` to do a smiliar thing. --- src/livekit/useLiveKit.ts | 6 +++ src/settings/SettingsModal.tsx | 88 ++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index d6d8394b3..5413738c2 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -159,6 +159,12 @@ export function useLiveKit( const syncDevice = (kind: MediaDeviceKind, device: MediaDevice) => { const id = device.selectedId; if (id !== undefined && room.getActiveDevice(kind) !== id) { + logger.info( + `Switching ${kind} device to ${ + device.available.find((d) => d.deviceId === device.selectedId) + ?.label + } (${id})` + ); room .switchActiveDevice(kind, id) .catch((e) => diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index 99716f883..c91292187 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -18,6 +18,9 @@ import { ChangeEvent, Key, useCallback, useState } from "react"; import { Item } from "@react-stately/collections"; import { Trans, useTranslation } from "react-i18next"; import { MatrixClient } from "matrix-js-sdk"; +import { useMaybeRoomContext } from "@livekit/components-react"; +import { LocalTrack } from "livekit-client"; +import { logger } from "matrix-js-sdk/src/logger"; import { Modal } from "../Modal"; import styles from "./SettingsModal.module.css"; @@ -66,26 +69,79 @@ export const SettingsModal = (props: Props) => { useShowConnectionStats(); const [enableE2EE, setEnableE2EE] = useEnableE2EE(); - // Generate a `SelectInput` with a list of devices for a given device kind. - const generateDeviceSelection = (devices: MediaDevice, caption: string) => { + const lkRoom = useMaybeRoomContext(); + + const getTrackInUse = (sourceName: string): LocalTrack | undefined => { + if (!lkRoom) return undefined; + + const t = Array.from(lkRoom.localParticipant.audioTracks.values()).find( + (d) => d.source === sourceName + ); + + return t?.track; + }; + + /** + * Generate a `SelectInput` with a list of devices for a given device kind. + * @param devices Info about the available & selected devices from MediaDevicesContext + * @param caption Device caption to be used if no labels available + * @param lkSource The livekit source name for this device type, eg. 'microphone': used to match up + * devices used by tracks in a current room (if any). + */ + const generateDeviceSelection = ( + devices: MediaDevice, + caption: string, + lkSource?: string + ) => { if (devices.available.length == 0) return null; + const trackUsedByRoom = lkSource ? getTrackInUse(lkSource) : undefined; + + let selectedKey = devices.selectedId; + // We may present a different device as the currently selected one if we have an active track + // from the default device, because the default device may have changed since we acquired the + // track, in which case we want to display the one we're actually using rather than what the + // default is now. + if ( + trackUsedByRoom && + (devices.selectedId === "" || + !devices.selectedId || + devices.selectedId === "default") + ) { + // we work out what the actual device is based on groupId, but this only works if + // there is only one such device with the same group ID, which there won't be if + // we're using hardware with multiple sub-devices (eg. a multitrack soundcard) + const usedGroupId = + trackUsedByRoom?.mediaStreamTrack.getSettings().groupId; + const devicesWithMatchingGroupId = devices.available.filter( + (d) => d.groupId === usedGroupId && d.groupId !== "default" + ); + + if (devicesWithMatchingGroupId.length === 1) { + logger.info( + `Current default device doesn't appear to match device in use: selecting ${devicesWithMatchingGroupId[0].label}` + ); + selectedKey = devicesWithMatchingGroupId[0].deviceId; + } + } + + const getLabel = (device: MediaDeviceInfo, index: number) => { + /*if (selected && trackUsedByRoom) { + return trackUsedByRoom.label.trim(); + }*/ + return !!device.label && device.label.trim().length > 0 + ? device.label + : `${caption} ${index + 1}`; + }; + return ( devices.select(id.toString())} > - {devices.available.map(({ deviceId, label }, index) => ( - - {!!label && label.trim().length > 0 - ? label - : `${caption} ${index + 1}`} - + {devices.available.map((d, index) => ( + {getLabel(d, index)} ))} ); @@ -124,7 +180,11 @@ export const SettingsModal = (props: Props) => { } > - {generateDeviceSelection(devices.audioInput, t("Microphone"))} + {generateDeviceSelection( + devices.audioInput, + t("Microphone"), + "microphone" + )} {!isFirefox() && generateDeviceSelection(devices.audioOutput, t("Speaker"))} From 88849828462a2e4de0f6cdd4487a01101a07918a Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 5 Oct 2023 18:04:02 +0100 Subject: [PATCH 2/3] Expand comment Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> --- src/settings/SettingsModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index c91292187..4cd101841 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -99,8 +99,8 @@ export const SettingsModal = (props: Props) => { let selectedKey = devices.selectedId; // We may present a different device as the currently selected one if we have an active track - // from the default device, because the default device may have changed since we acquired the - // track, in which case we want to display the one we're actually using rather than what the + // from the default device, because the default device of the OS may have changed since we acquired the + // track, but EC did not update the track to match the new default, in which case we want to display the one we're actually using rather than what the // default is now. if ( trackUsedByRoom && From 6d2d5641a51256478f3283d683e7b74a3b8df6f9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 6 Oct 2023 11:55:56 +0100 Subject: [PATCH 3/3] Fix logic a bit as per comments (and... add comments) --- src/settings/SettingsModal.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index c91292187..e688b504e 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -101,27 +101,38 @@ export const SettingsModal = (props: Props) => { // We may present a different device as the currently selected one if we have an active track // from the default device, because the default device may have changed since we acquired the // track, in which case we want to display the one we're actually using rather than what the - // default is now. + // default is now. We only do this if we've selected, and are using, the default device if ( trackUsedByRoom && (devices.selectedId === "" || !devices.selectedId || - devices.selectedId === "default") + devices.selectedId === "default") && + trackUsedByRoom.mediaStreamTrack.getSettings().deviceId === "default" ) { // we work out what the actual device is based on groupId, but this only works if // there is only one such device with the same group ID, which there won't be if // we're using hardware with multiple sub-devices (eg. a multitrack soundcard) const usedGroupId = trackUsedByRoom?.mediaStreamTrack.getSettings().groupId; - const devicesWithMatchingGroupId = devices.available.filter( - (d) => d.groupId === usedGroupId && d.groupId !== "default" - ); + const defaultGroupId = devices.available.find( + (d) => d.deviceId === "default" + )?.groupId; - if (devicesWithMatchingGroupId.length === 1) { - logger.info( - `Current default device doesn't appear to match device in use: selecting ${devicesWithMatchingGroupId[0].label}` + // If the device we're actually using doesn't match tne group ID of what the default is + // now, then display a different one. + if (usedGroupId !== defaultGroupId) { + const devicesWithMatchingGroupId = devices.available.filter( + (d) => d.groupId === usedGroupId && d.deviceId !== "default" ); - selectedKey = devicesWithMatchingGroupId[0].deviceId; + + // One final check: check that there is only one such device matching the group ID. + // If not, we simply can't match up the device correctly: we don't have enough info. + if (devicesWithMatchingGroupId.length === 1) { + logger.info( + `Current default device doesn't appear to match device in use: selecting ${devicesWithMatchingGroupId[0].label}` + ); + selectedKey = devicesWithMatchingGroupId[0].deviceId; + } } }