From a7fd5b9643200408033e1df8d22800d2c226b4ac Mon Sep 17 00:00:00 2001 From: Scott Willeke Date: Mon, 15 Sep 2025 22:12:20 +0000 Subject: [PATCH 1/2] feat(react): allow using multiple device kinds with MediaDeviceMenu This is an update to the MediaDeviceMenu react component to allow specifying device kinds as an array (e.g. microphone + speaker). - The menu already rendered a multi-kind menu when no kind was specified, there was just no way to specify >1 kind to render when specifying a kind. This PR leverages that same rendering. - This should be API-compatible. The one type-change was that the existing `MediaDeviceMenuProps` interface which is now a type alias for the prior props interface (now renamed `MediaDeviceMenuPropsSingleKind`) and the new `MediaDeviceMenuPropsMultiKind` props interface. - If you want any changes, feel free to ask, just wanted to see if this was directionally aligned with something you might accept. Signed-off-by: Scott Willeke --- .../react/src/prefabs/MediaDeviceMenu.tsx | 90 ++++++++++++------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/packages/react/src/prefabs/MediaDeviceMenu.tsx b/packages/react/src/prefabs/MediaDeviceMenu.tsx index 208e47af3..8e4ed42c2 100644 --- a/packages/react/src/prefabs/MediaDeviceMenu.tsx +++ b/packages/react/src/prefabs/MediaDeviceMenu.tsx @@ -3,8 +3,13 @@ import * as React from 'react'; import { MediaDeviceSelect } from '../components/controls/MediaDeviceSelect'; import type { LocalAudioTrack, LocalVideoTrack } from 'livekit-client'; -/** @public */ -export interface MediaDeviceMenuProps extends React.ButtonHTMLAttributes { +interface KindWithInitialSelection { + kind: MediaDeviceKind; + initialSelection?: string; +} + +interface MediaDeviceMenuPropsSingleKind + extends React.ButtonHTMLAttributes { kind?: MediaDeviceKind; initialSelection?: string; onActiveDeviceChange?: (kind: MediaDeviceKind, deviceId: string) => void; @@ -21,6 +26,27 @@ export interface MediaDeviceMenuProps extends React.ButtonHTMLAttributes { + kind?: KindWithInitialSelection[]; + initialSelection?: undefined; + onActiveDeviceChange?: (kind: MediaDeviceKind, deviceId: string) => void; + tracks?: Partial>; + /** + * this will call getUserMedia if the permissions are not yet given to enumerate the devices with device labels. + * in some browsers multiple calls to getUserMedia result in multiple permission prompts. + * It's generally advised only flip this to true, once a (preview) track has been acquired successfully with the + * appropriate permissions. + * + * @see {@link PreJoin} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices | MDN enumerateDevices} + */ + requestPermissions?: boolean; +} + +/** @public */ +export type MediaDeviceMenuProps = MediaDeviceMenuPropsSingleKind | MediaDeviceMenuPropsMultiKind; + /** * The `MediaDeviceMenu` component is a button that opens a menu that lists * all media devices and allows the user to select them. @@ -101,6 +127,26 @@ export function MediaDeviceMenu({ }; }, [handleClickOutside]); + // Normalize props to a consistent internal format + const kindsWithInitialSelection: KindWithInitialSelection[] = (() => { + if (kind === undefined) { + // Default to audio and video inputs when no kind is specified + return [{ kind: 'audioinput' as MediaDeviceKind }, { kind: 'videoinput' as MediaDeviceKind }]; + } else if (Array.isArray(kind)) { + // multi-kind case: kind is KindWithInitialSelection[] + return kind; + } else { + // single kind case: kind is MediaDeviceKind, initialSelection is string | undefined + return [{ kind, initialSelection }]; + } + })(); + + const kindLabels: Record = { + audioinput: 'Audio inputs', + videoinput: 'Video inputs', + audiooutput: 'Audio outputs', + }; + return ( <>