@@ -32,6 +32,7 @@ import {
32
32
availableOutputDevices$ as controlledAvailableOutputDevices$ ,
33
33
} from "../controls" ;
34
34
import { getUrlParams } from "../UrlParams" ;
35
+ import { switchWhen } from "../utils/observable" ;
35
36
36
37
// This hardcoded id is used in EX ios! It can only be changed in coordination with
37
38
// the ios swift team.
@@ -94,17 +95,29 @@ export const iosDeviceMenu$ = navigator.userAgent.includes("iPhone")
94
95
95
96
function availableRawDevices$ (
96
97
kind : MediaDeviceKind ,
97
- updateAvailableDeviceRequests $ : Observable < boolean > ,
98
+ usingNames $ : Observable < boolean > ,
98
99
scope : ObservableScope ,
99
100
) : Observable < MediaDeviceInfo [ ] > {
100
- return updateAvailableDeviceRequests$ . pipe (
101
- startWith ( false ) ,
102
- switchMap ( ( withPermissions ) =>
103
- createMediaDeviceObserver (
104
- kind ,
105
- ( e ) => logger . error ( "Error creating MediaDeviceObserver" , e ) ,
106
- withPermissions ,
107
- ) ,
101
+ const logError = ( e : Error ) : void =>
102
+ logger . error ( "Error creating MediaDeviceObserver" , e ) ;
103
+ const devices$ = createMediaDeviceObserver ( kind , logError , false ) ;
104
+ const devicesWithNames$ = createMediaDeviceObserver ( kind , logError , true ) ;
105
+
106
+ return usingNames$ . pipe (
107
+ switchMap ( ( withNames ) =>
108
+ withNames
109
+ ? // It might be that there is already a media stream running somewhere,
110
+ // and so we can do without requesting a second one. Only switch to the
111
+ // device observer that explicitly requests the names if we see that
112
+ // names are in fact missing from the initial device enumeration.
113
+ devices$ . pipe (
114
+ switchWhen ( ( devices , i ) => {
115
+ const c = i === 0 && devices . every ( ( d ) => ! d . label ) ;
116
+ if ( c ) debugger ;
117
+ return c ;
118
+ } , devicesWithNames$ ) ,
119
+ )
120
+ : devices$ ,
108
121
) ,
109
122
startWith ( [ ] ) ,
110
123
scope . state ( ) ,
@@ -147,11 +160,7 @@ function selectDevice$<Label>(
147
160
148
161
class AudioInput implements MediaDevice < DeviceLabel , SelectedAudioInputDevice > {
149
162
private readonly availableRaw$ : Observable < MediaDeviceInfo [ ] > =
150
- availableRawDevices$ (
151
- "audioinput" ,
152
- this . updateAvailableDeviceRequests$ ,
153
- this . scope ,
154
- ) ;
163
+ availableRawDevices$ ( "audioinput" , this . usingNames$ , this . scope ) ;
155
164
156
165
public readonly available$ = this . availableRaw$ . pipe (
157
166
map ( buildDeviceMap ) ,
@@ -185,7 +194,7 @@ class AudioInput implements MediaDevice<DeviceLabel, SelectedAudioInputDevice> {
185
194
}
186
195
187
196
public constructor (
188
- private readonly updateAvailableDeviceRequests $ : Observable < boolean > ,
197
+ private readonly usingNames $ : Observable < boolean > ,
189
198
private readonly scope : ObservableScope ,
190
199
) {
191
200
this . available$ . subscribe ( ( available ) => {
@@ -199,7 +208,7 @@ class AudioOutput
199
208
{
200
209
public readonly available$ = availableRawDevices$ (
201
210
"audiooutput" ,
202
- this . updateAvailableDeviceRequests $,
211
+ this . usingNames $,
203
212
this . scope ,
204
213
) . pipe (
205
214
map ( ( availableRaw ) => {
@@ -240,7 +249,7 @@ class AudioOutput
240
249
}
241
250
242
251
public constructor (
243
- private readonly updateAvailableDeviceRequests $ : Observable < boolean > ,
252
+ private readonly usingNames $ : Observable < boolean > ,
244
253
private readonly scope : ObservableScope ,
245
254
) {
246
255
this . available$ . subscribe ( ( available ) => {
@@ -318,7 +327,7 @@ class ControlledAudioOutput
318
327
class VideoInput implements MediaDevice < DeviceLabel , SelectedDevice > {
319
328
public readonly available$ = availableRawDevices$ (
320
329
"videoinput" ,
321
- this . updateAvailableDeviceRequests $,
330
+ this . usingNames $,
322
331
this . scope ,
323
332
) . pipe ( map ( buildDeviceMap ) ) ;
324
333
@@ -335,7 +344,7 @@ class VideoInput implements MediaDevice<DeviceLabel, SelectedDevice> {
335
344
}
336
345
337
346
public constructor (
338
- private readonly updateAvailableDeviceRequests $ : Observable < boolean > ,
347
+ private readonly usingNames $ : Observable < boolean > ,
339
348
private readonly scope : ObservableScope ,
340
349
) {
341
350
// This also has the purpose of subscribing to the available devices
@@ -346,48 +355,43 @@ class VideoInput implements MediaDevice<DeviceLabel, SelectedDevice> {
346
355
}
347
356
348
357
export class MediaDevices {
349
- private readonly updateAvailableDeviceRequests $ = new Subject < boolean > ( ) ;
358
+ private readonly deviceNamesRequest $ = new Subject < void > ( ) ;
350
359
/**
351
360
* Requests that the media devices be populated with the names of each
352
361
* available device, rather than numbered identifiers. This may invoke a
353
362
* permissions pop-up, so it should only be called when there is a clear user
354
363
* intent to view the device list.
355
- *
356
- * This always updates the `available$` devices for each media type with the current value
357
- * of `enumerateDevices`.
358
364
*/
359
365
public requestDeviceNames ( ) : void {
360
- void navigator . mediaDevices . enumerateDevices ( ) . then ( ( result ) => {
361
- // we only actually update the requests$ subject if there are no
362
- // devices with a label, because otherwise we already have the permission
363
- // to access the devices.
364
- this . updateAvailableDeviceRequests$ . next (
365
- ! result . some ( ( device ) => device . label ) ,
366
- ) ;
367
- } ) ;
366
+ this . deviceNamesRequest$ . next ( ) ;
368
367
}
369
368
369
+ // Start using device names as soon as requested. This will cause LiveKit to
370
+ // briefly request device permissions and acquire media streams for each
371
+ // device type while calling `enumerateDevices`, which is what browsers want
372
+ // you to do to receive device names in lieu of a more explicit permissions
373
+ // API. This flag never resets to false, because once permissions are granted
374
+ // the first time, the user won't be prompted again until reload of the page.
375
+ private readonly usingNames$ = this . deviceNamesRequest$ . pipe (
376
+ map ( ( ) => true ) ,
377
+ startWith ( false ) ,
378
+ this . scope . state ( ) ,
379
+ ) ;
380
+
370
381
public readonly audioInput : MediaDevice <
371
382
DeviceLabel ,
372
383
SelectedAudioInputDevice
373
- > = new AudioInput ( this . updateAvailableDeviceRequests $, this . scope ) ;
384
+ > = new AudioInput ( this . usingNames $, this . scope ) ;
374
385
375
386
public readonly audioOutput : MediaDevice <
376
387
AudioOutputDeviceLabel ,
377
388
SelectedAudioOutputDevice
378
389
> = getUrlParams ( ) . controlledAudioDevices
379
390
? new ControlledAudioOutput ( this . scope )
380
- : new AudioOutput ( this . updateAvailableDeviceRequests $, this . scope ) ;
391
+ : new AudioOutput ( this . usingNames $, this . scope ) ;
381
392
382
393
public readonly videoInput : MediaDevice < DeviceLabel , SelectedDevice > =
383
- new VideoInput ( this . updateAvailableDeviceRequests $, this . scope ) ;
394
+ new VideoInput ( this . usingNames $, this . scope ) ;
384
395
385
- public constructor ( private readonly scope : ObservableScope ) {
386
- this . updateAvailableDeviceRequests$ . subscribe ( ( recompute ) => {
387
- logger . info (
388
- "[MediaDevices] updateAvailableDeviceRequests$ changed:" ,
389
- recompute ,
390
- ) ;
391
- } ) ;
392
- }
396
+ public constructor ( private readonly scope : ObservableScope ) { }
393
397
}
0 commit comments