message: fix: try all user media before failing
  this helps if the user owns e.g. multiple webcams
diff: @@ -2,6 +2,7 @@ import Log from './util/Log';
  import Translation from './util/Translation';
  import Overlay from './ui/Overlay';
  import * as getScreenMedia from 'getscreenmedia';
  +import Client from './Client';
  export default class UserMedia {
  public static async request(um = ['video', 'audio']): Promise<MediaStream> {
  @@ -48,14 +49,10 @@ export default class UserMedia {
  }
  private static async filterUserMedia(userMedia: string[]): Promise<string[]> {
  - let devices = await navigator.mediaDevices.enumerateDevices();
  - let availableDevices = devices.map(function (device) {
  - //@REVIEW MediaDeviceKind === string?
  - return <string>device.kind;
  - });
  + let devices = await UserMedia.getMediaDevices();
  userMedia = userMedia.filter(function (el) {
  - return availableDevices.indexOf(el) !== -1 || availableDevices.indexOf(el + 'input') !== -1;
  + return devices[el + 'input']?.length > 0;
  });
  if (userMedia.length === 0) {
  @@ -65,6 +62,23 @@ export default class UserMedia {
  return userMedia;
  }
  + private static async getMediaDevices(): Promise<{ audioinput: string[]; videoinput: string[] }> {
  + let devices = await navigator.mediaDevices.enumerateDevices();
  + let mediaDevices = devices.reduce(
  + (mediaDevices, device) => {
  + mediaDevices[device.kind]?.push(device.deviceId);
  +
  + return mediaDevices;
  + },
  + {
  + audioinput: [] as string[],
  + videoinput: [] as string[],
  + }
  + );
  +
  + return mediaDevices;
  + }
  +
  private static getScreenMedia(): Promise<MediaStream> {
  return new Promise((resolve, reject) => {
  getScreenMedia((error, stream) => {
  @@ -77,24 +91,62 @@ export default class UserMedia {
  });
  }
  - private static getUserMedia(um) {
  - let constraints: any = {};
  + private static async getUserMedia(um) {
  + const devices = await UserMedia.getMediaDevices();
  +
  + let videoIndex = 0;
  + let audioIndex = 0;
  +
  + while (
  + (videoIndex < devices.videoinput.length || !um.includes('video')) &&
  + (audioIndex < devices.audioinput.length || !um.includes('audio'))
  + ) {
  + const constraints: MediaStreamConstraints = {};
  +
  + if (um.includes('video')) {
  + constraints.video = {
  + deviceId: {
  + exact: devices.videoinput[videoIndex],
  + },
  + };
  - if (um.indexOf('video') > -1) {
  - constraints.video = true;
  + if (Client.isDebugMode()) {
  + constraints.video = {
  + ...constraints.video,
  + width: { ideal: 320 },
  + height: { ideal: 180 },
  + frameRate: { ideal: 2 },
  + };
  + }
  }
  - if (um.indexOf('audio') > -1) {
  - constraints.audio = true;
  + if (um.includes('audio')) {
  + constraints.audio = {
  + deviceId: {
  + exact: devices.audioinput[audioIndex],
  + },
  + };
  }
  try {
  - return navigator.mediaDevices.getUserMedia(constraints);
  - } catch (e) {
  - Log.error('GUM failed: ', e);
  + const userMedia = await navigator.mediaDevices.getUserMedia(constraints);
  - return Promise.reject('GUM failed');
  + return userMedia;
  + } catch (err) {
  + Log.error('GUM failed: ', err);
  +
  + if (err.toString().includes('video')) {
  + videoIndex++;
  + } else if (err.toString().includes('audio')) {
  + audioIndex++;
  + } else {
  + videoIndex++;
  + audioIndex++;
  + }
  + }
  }
  +
  + return Promise.reject('GUM failed');
  }
  private static onMediaFailure(err: Error) {
  