Skip to content

Commit 7b6e1fc

Browse files
committed
Only rerequest permissoins if we do not yet get labels when enumerating
1 parent 06b2e8b commit 7b6e1fc

File tree

2 files changed

+66
-23
lines changed

2 files changed

+66
-23
lines changed

src/state/MediaDevices.ts

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
88
import {
99
combineLatest,
1010
filter,
11+
identity,
1112
map,
1213
merge,
1314
of,
@@ -18,7 +19,7 @@ import {
1819
type Observable,
1920
} from "rxjs";
2021
import { createMediaDeviceObserver } from "@livekit/components-core";
21-
import { logger } from "matrix-js-sdk/lib/logger";
22+
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
2223

2324
import {
2425
audioInput as audioInputSetting,
@@ -36,6 +37,7 @@ import { getUrlParams } from "../UrlParams";
3637
// This hardcoded id is used in EX ios! It can only be changed in coordination with
3738
// the ios swift team.
3839
const EARPIECE_CONFIG_ID = "earpiece-id";
40+
const logger = rootLogger.getChild("[MediaDevices]");
3941

4042
export type DeviceLabel =
4143
| { type: "name"; name: string }
@@ -93,15 +95,15 @@ export const iosDeviceMenu$ = navigator.userAgent.includes("iPhone")
9395

9496
function availableRawDevices$(
9597
kind: MediaDeviceKind,
96-
usingNames$: Observable<boolean>,
98+
recomputeDevicesWithPermissions$: Observable<boolean>,
9799
scope: ObservableScope,
98100
): Observable<MediaDeviceInfo[]> {
99-
return usingNames$.pipe(
100-
switchMap((usingNames) =>
101+
return recomputeDevicesWithPermissions$.pipe(
102+
switchMap((recomputeDevicesWithPermissions) =>
101103
createMediaDeviceObserver(
102104
kind,
103105
(e) => logger.error("Error creating MediaDeviceObserver", e),
104-
usingNames,
106+
recomputeDevicesWithPermissions,
105107
),
106108
),
107109
startWith([]),
@@ -145,7 +147,11 @@ function selectDevice$<Label>(
145147

146148
class AudioInput implements MediaDevice<DeviceLabel, SelectedAudioInputDevice> {
147149
private readonly availableRaw$: Observable<MediaDeviceInfo[]> =
148-
availableRawDevices$("audioinput", this.usingNames$, this.scope);
150+
availableRawDevices$(
151+
"audioinput",
152+
this.recomputeDevicesWithPermissions$,
153+
this.scope,
154+
);
149155

150156
public readonly available$ = this.availableRaw$.pipe(
151157
map(buildDeviceMap),
@@ -179,17 +185,21 @@ class AudioInput implements MediaDevice<DeviceLabel, SelectedAudioInputDevice> {
179185
}
180186

181187
public constructor(
182-
private readonly usingNames$: Observable<boolean>,
188+
private readonly recomputeDevicesWithPermissions$: Observable<boolean>,
183189
private readonly scope: ObservableScope,
184-
) {}
190+
) {
191+
this.available$.subscribe((available) => {
192+
logger.info("[audio-input] available devices:", available);
193+
});
194+
}
185195
}
186196

187197
class AudioOutput
188198
implements MediaDevice<AudioOutputDeviceLabel, SelectedAudioOutputDevice>
189199
{
190200
public readonly available$ = availableRawDevices$(
191201
"audiooutput",
192-
this.usingNames$,
202+
this.recomputeDevicesWithPermissions$,
193203
this.scope,
194204
).pipe(
195205
map((availableRaw) => {
@@ -230,9 +240,13 @@ class AudioOutput
230240
}
231241

232242
public constructor(
233-
private readonly usingNames$: Observable<boolean>,
243+
private readonly recomputeDevicesWithPermissions$: Observable<boolean>,
234244
private readonly scope: ObservableScope,
235-
) {}
245+
) {
246+
this.available$.subscribe((available) => {
247+
logger.info("[audio-output] available devices:", available);
248+
});
249+
}
236250
}
237251

238252
class ControlledAudioOutput
@@ -295,13 +309,16 @@ class ControlledAudioOutput
295309
window.controls.onOutputDeviceSelect?.(device.id);
296310
}
297311
});
312+
this.available$.subscribe((available) => {
313+
logger.info("[controlled-output] available devices:", available);
314+
});
298315
}
299316
}
300317

301318
class VideoInput implements MediaDevice<DeviceLabel, SelectedDevice> {
302319
public readonly available$ = availableRawDevices$(
303320
"videoinput",
304-
this.usingNames$,
321+
this.recomputeDevicesWithPermissions$,
305322
this.scope,
306323
).pipe(map(buildDeviceMap));
307324

@@ -318,21 +335,28 @@ class VideoInput implements MediaDevice<DeviceLabel, SelectedDevice> {
318335
}
319336

320337
public constructor(
321-
private readonly usingNames$: Observable<boolean>,
338+
private readonly recomputeDevicesWithPermissions$: Observable<boolean>,
322339
private readonly scope: ObservableScope,
323-
) {}
340+
) {
341+
// This also has the purpose of subscribing to the available devices
342+
this.available$.subscribe((available) => {
343+
logger.info("[video-input] available devices:", available);
344+
});
345+
}
324346
}
325347

326348
export class MediaDevices {
327-
private readonly deviceNamesRequest$ = new Subject<void>();
349+
private readonly requests$ = new Subject<boolean>();
328350
/**
329351
* Requests that the media devices be populated with the names of each
330352
* available device, rather than numbered identifiers. This may invoke a
331353
* permissions pop-up, so it should only be called when there is a clear user
332354
* intent to view the device list.
333355
*/
334356
public requestDeviceNames(): void {
335-
this.deviceNamesRequest$.next();
357+
void navigator.mediaDevices.enumerateDevices().then((result) => {
358+
this.requests$.next(!result.some((device) => device.label));
359+
});
336360
}
337361

338362
// Start using device names as soon as requested. This will cause LiveKit to
@@ -341,26 +365,33 @@ export class MediaDevices {
341365
// you to do to receive device names in lieu of a more explicit permissions
342366
// API. This flag never resets to false, because once permissions are granted
343367
// the first time, the user won't be prompted again until reload of the page.
344-
private readonly usingNames$ = this.deviceNamesRequest$.pipe(
345-
map(() => true),
368+
private readonly recomputeDevicesWithPermissions$ = this.requests$.pipe(
346369
startWith(false),
347-
this.scope.state(),
370+
identity,
371+
this.scope.stateNonDistinct(),
348372
);
349373

350374
public readonly audioInput: MediaDevice<
351375
DeviceLabel,
352376
SelectedAudioInputDevice
353-
> = new AudioInput(this.usingNames$, this.scope);
377+
> = new AudioInput(this.recomputeDevicesWithPermissions$, this.scope);
354378

355379
public readonly audioOutput: MediaDevice<
356380
AudioOutputDeviceLabel,
357381
SelectedAudioOutputDevice
358382
> = getUrlParams().controlledAudioDevices
359383
? new ControlledAudioOutput(this.scope)
360-
: new AudioOutput(this.usingNames$, this.scope);
384+
: new AudioOutput(this.recomputeDevicesWithPermissions$, this.scope);
361385

362386
public readonly videoInput: MediaDevice<DeviceLabel, SelectedDevice> =
363-
new VideoInput(this.usingNames$, this.scope);
387+
new VideoInput(this.recomputeDevicesWithPermissions$, this.scope);
364388

365-
public constructor(private readonly scope: ObservableScope) {}
389+
public constructor(private readonly scope: ObservableScope) {
390+
this.recomputeDevicesWithPermissions$.subscribe((recompute) => {
391+
logger.info(
392+
"[MediaDevices] recomputeDevicesWithPermissions$ changed:",
393+
recompute,
394+
);
395+
});
396+
}
366397
}

src/state/ObservableScope.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export class ObservableScope {
3838
shareReplay({ bufferSize: 1, refCount: false }),
3939
);
4040

41+
private readonly stateNonDistinctImpl: MonoTypeOperator = (o$) =>
42+
o$.pipe(this.bind(), shareReplay({ bufferSize: 1, refCount: false }));
43+
4144
/**
4245
* Transforms an Observable into a hot state Observable which replays its
4346
* latest value upon subscription, skips updates with identical values, and
@@ -47,6 +50,15 @@ export class ObservableScope {
4750
return this.stateImpl;
4851
}
4952

53+
/**
54+
* Transforms an Observable into a hot state Observable which replays its
55+
* latest value upon subscription, skips updates with identical values, and
56+
* is bound to this scope.
57+
*/
58+
public stateNonDistinct(): MonoTypeOperator {
59+
return this.stateNonDistinctImpl;
60+
}
61+
5062
/**
5163
* Ends the scope, causing any bound Observables to complete.
5264
*/

0 commit comments

Comments
 (0)