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..5b6cd5502 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,91 @@ 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 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. We only do this if we've selected, and + // are using, the default device + if ( + trackUsedByRoom && + (devices.selectedId === "" || + !devices.selectedId || + 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 defaultGroupId = devices.available.find( + (d) => d.deviceId === "default" + )?.groupId; + + // 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" + ); + + // 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; + } + } + } + + 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 +192,11 @@ export const SettingsModal = (props: Props) => { } > - {generateDeviceSelection(devices.audioInput, t("Microphone"))} + {generateDeviceSelection( + devices.audioInput, + t("Microphone"), + "microphone" + )} {!isFirefox() && generateDeviceSelection(devices.audioOutput, t("Speaker"))}