private async chooseInputIntrinsicDevice()

in src/devicecontroller/DefaultDeviceController.ts [1083:1193]


  private async chooseInputIntrinsicDevice(
    kind: 'audio' | 'video',
    device: Device | null
  ): Promise<void> {
    // N.B.,: the input device might already have augmented constraints supplied
    // by an `AudioTransformDevice`. `getMediaStreamConstraints` will respect
    // settings supplied by the device.
    const proposedConstraints = this.getMediaStreamConstraints(kind, device);

    // TODO: `matchesConstraints` should really return compatible/incompatible/exact --
    // `applyConstraints` can be used to reuse the active device while changing the
    // requested constraints.
    if (this.matchesDeviceSelection(kind, device, this.activeDevices[kind], proposedConstraints)) {
      this.logger.info(`reusing existing ${kind} input device`);
      return;
    }
    if (this.activeDevices[kind] && this.activeDevices[kind].stream) {
      this.stopTracksAndRemoveCallbacks(kind);
    }

    const startTimeMs = Date.now();
    const newDevice: DeviceSelection = new DeviceSelection();
    try {
      this.logger.info(
        `requesting new ${kind} device with constraint ${JSON.stringify(proposedConstraints)}`
      );
      const stream = this.intrinsicDeviceAsMediaStream(device);
      if (kind === 'audio' && device === null) {
        newDevice.stream = DefaultDeviceController.createEmptyAudioDevice() as MediaStream;
        newDevice.constraints = null;
      } else if (stream) {
        this.logger.info(`using media stream ${stream.id} for ${kind} device`);
        newDevice.stream = stream;
        newDevice.constraints = proposedConstraints;
      } else {
        newDevice.stream = await navigator.mediaDevices.getUserMedia(proposedConstraints);
        newDevice.constraints = proposedConstraints;
      }
      await this.handleNewInputDevice(kind, newDevice);
    } catch (error) {
      const errorMessage = this.getErrorMessage(error);

      if (kind === 'audio') {
        this.eventController?.publishEvent('audioInputFailed', {
          audioInputErrorMessage: errorMessage,
        });
      } else {
        this.eventController?.publishEvent('videoInputFailed', {
          videoInputErrorMessage: errorMessage,
        });
      }

      this.logger.error(
        `failed to get ${kind} device for constraints ${JSON.stringify(
          proposedConstraints
        )}: ${errorMessage}`
      );

      let hasError = true;
      // This is effectively `error instanceof OverconstrainedError` but works in Node.
      if (error && 'constraint' in error) {
        this.logger.error(`Over-constrained by constraint: ${error.constraint}`);
        // Try to reduce the constraints if over-constraints
        if (this.useMediaConstraintsFallback) {
          const fallbackConstraints = this.getMediaStreamConstraints(kind, device, true);
          const fallbackConstraintsJSON = JSON.stringify(fallbackConstraints);
          if (fallbackConstraintsJSON !== JSON.stringify(proposedConstraints)) {
            this.logger.info(
              `retry requesting new ${kind} device with minimal constraint ${fallbackConstraintsJSON}`
            );
            try {
              newDevice.stream = await navigator.mediaDevices.getUserMedia(fallbackConstraints);
              newDevice.constraints = fallbackConstraints;
              await this.handleNewInputDevice(kind, newDevice);
              hasError = false;
            } catch (e) {
              this.logger.error(
                `failed to get ${kind} device for constraints ${fallbackConstraintsJSON}: ${this.getErrorMessage(
                  e
                )}`
              );
            }
          }
        }
      }

      if (hasError) {
        /*
         * If there is any error while acquiring the audio device, we fall back to null device.
         * Reason: If device selection fails (e.g. NotReadableError), the peer connection is left hanging
         * with no active audio track since we release the previously attached track.
         * If no audio packet has yet been sent to the server, the server will not emit the joined event.
         */
        if (kind === 'audio') {
          this.logger.info(`choosing null ${kind} device instead`);
          try {
            newDevice.stream = DefaultDeviceController.createEmptyAudioDevice();
            newDevice.constraints = null;
            await this.handleNewInputDevice(kind, newDevice);
          } catch (error) {
            this.logger.error(
              `failed to choose null ${kind} device. ${error.name}: ${error.message}`
            );
          }
        }
        this.handleGetUserMediaError(error, Date.now() - startTimeMs);
      }
    } finally {
      this.watchForDeviceChangesIfNecessary();
    }
  }