@@ -3,8 +3,13 @@ import * as React from 'react';
3
3
import { MediaDeviceSelect } from '../components/controls/MediaDeviceSelect' ;
4
4
import type { LocalAudioTrack , LocalVideoTrack } from 'livekit-client' ;
5
5
6
- /** @public */
7
- export interface MediaDeviceMenuProps extends React . ButtonHTMLAttributes < HTMLButtonElement > {
6
+ interface KindWithInitialSelection {
7
+ kind : MediaDeviceKind ;
8
+ initialSelection ?: string ;
9
+ }
10
+
11
+ interface MediaDeviceMenuPropsSingleKind
12
+ extends React . ButtonHTMLAttributes < HTMLButtonElement > {
8
13
kind ?: MediaDeviceKind ;
9
14
initialSelection ?: string ;
10
15
onActiveDeviceChange ?: ( kind : MediaDeviceKind , deviceId : string ) => void ;
@@ -21,6 +26,27 @@ export interface MediaDeviceMenuProps extends React.ButtonHTMLAttributes<HTMLBut
21
26
requestPermissions ?: boolean ;
22
27
}
23
28
29
+ interface MediaDeviceMenuPropsMultiKind
30
+ extends React . ButtonHTMLAttributes < HTMLButtonElement > {
31
+ kind ?: KindWithInitialSelection [ ] ;
32
+ initialSelection ?: undefined ;
33
+ onActiveDeviceChange ?: ( kind : MediaDeviceKind , deviceId : string ) => void ;
34
+ tracks ?: Partial < Record < MediaDeviceKind , LocalAudioTrack | LocalVideoTrack | undefined > > ;
35
+ /**
36
+ * this will call getUserMedia if the permissions are not yet given to enumerate the devices with device labels.
37
+ * in some browsers multiple calls to getUserMedia result in multiple permission prompts.
38
+ * It's generally advised only flip this to true, once a (preview) track has been acquired successfully with the
39
+ * appropriate permissions.
40
+ *
41
+ * @see {@link PreJoin }
42
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices | MDN enumerateDevices }
43
+ */
44
+ requestPermissions ?: boolean ;
45
+ }
46
+
47
+ /** @public */
48
+ export type MediaDeviceMenuProps = MediaDeviceMenuPropsSingleKind | MediaDeviceMenuPropsMultiKind ;
49
+
24
50
/**
25
51
* The `MediaDeviceMenu` component is a button that opens a menu that lists
26
52
* all media devices and allows the user to select them.
@@ -101,6 +127,26 @@ export function MediaDeviceMenu({
101
127
} ;
102
128
} , [ handleClickOutside ] ) ;
103
129
130
+ // Normalize props to a consistent internal format
131
+ const kindsWithInitialSelection : KindWithInitialSelection [ ] = ( ( ) => {
132
+ if ( kind === undefined ) {
133
+ // Default to audio and video inputs when no kind is specified
134
+ return [ { kind : 'audioinput' as MediaDeviceKind } , { kind : 'videoinput' as MediaDeviceKind } ] ;
135
+ } else if ( Array . isArray ( kind ) ) {
136
+ // multi-kind case: kind is KindWithInitialSelection[]
137
+ return kind ;
138
+ } else {
139
+ // single kind case: kind is MediaDeviceKind, initialSelection is string | undefined
140
+ return [ { kind, initialSelection } ] ;
141
+ }
142
+ } ) ( ) ;
143
+
144
+ const kindLabels : Record < MediaDeviceKind , string > = {
145
+ audioinput : 'Audio inputs' ,
146
+ videoinput : 'Video inputs' ,
147
+ audiooutput : 'Audio outputs' ,
148
+ } ;
149
+
104
150
return (
105
151
< >
106
152
< button
@@ -119,39 +165,21 @@ export function MediaDeviceMenu({
119
165
ref = { tooltip }
120
166
style = { { visibility : isOpen ? 'visible' : 'hidden' } }
121
167
>
122
- { kind ? (
123
- < MediaDeviceSelect
124
- initialSelection = { initialSelection }
125
- onActiveDeviceChange = { ( deviceId ) => handleActiveDeviceChange ( kind , deviceId ) }
126
- onDeviceListChange = { setDevices }
127
- kind = { kind }
128
- track = { tracks ?. [ kind ] }
129
- requestPermissions = { needPermissions }
130
- />
131
- ) : (
132
- < >
133
- < div className = "lk-device-menu-heading" > Audio inputs</ div >
134
- < MediaDeviceSelect
135
- kind = "audioinput"
136
- onActiveDeviceChange = { ( deviceId ) =>
137
- handleActiveDeviceChange ( 'audioinput' , deviceId )
138
- }
139
- onDeviceListChange = { setDevices }
140
- track = { tracks ?. audioinput }
141
- requestPermissions = { needPermissions }
142
- />
143
- < div className = "lk-device-menu-heading" > Video inputs</ div >
168
+ { kindsWithInitialSelection . map ( ( kindInfo , idx , arr ) => (
169
+ < React . Fragment key = { `device-group-${ kindInfo . kind } ` } >
170
+ { arr . length > 1 && idx < arr . length && (
171
+ < div className = "lk-device-menu-heading" > { kindLabels [ kindInfo . kind ] } </ div >
172
+ ) }
144
173
< MediaDeviceSelect
145
- kind = "videoinput"
146
- onActiveDeviceChange = { ( deviceId ) =>
147
- handleActiveDeviceChange ( 'videoinput' , deviceId )
148
- }
174
+ kind = { kindInfo . kind }
175
+ initialSelection = { kindInfo . initialSelection }
176
+ onActiveDeviceChange = { ( deviceId ) => handleActiveDeviceChange ( kindInfo . kind , deviceId ) }
149
177
onDeviceListChange = { setDevices }
150
- track = { tracks ?. videoinput }
178
+ track = { tracks ?. [ kindInfo . kind ] }
151
179
requestPermissions = { needPermissions }
152
180
/>
153
- </ >
154
- ) }
181
+ </ React . Fragment >
182
+ ) ) }
155
183
</ div >
156
184
) }
157
185
</ >
0 commit comments