3

I'm using AudioContext, programatically, with Typescript.

Here's my code:

/**
 * Checks for getUserMedia
 *
 * @params: none
 * @returns: any
 */
public hasGetUserMedia(): any {
  const mediaservices = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
  console.log('Media Services: ', navigator.mediaDevices);
  return mediaservices;
}

/**
 * A function to get the return value of hasGetUserMedia
 *
 * @params: none
 * @return: none
 */
public isUserMediaGood(micstatus: boolean) {
  const self = this;
  if (this.hasGetUserMedia()) {
    // Good to go!
    self.isUserMediaThere = true;
    console.log('We have User Media Houston!');

    // Now accessInputDevice
    this.accessInputDevice(micstatus);
  } else {
    // Oops!
    self.isUserMediaThere = false;
    console.log('WARNING: getUserMedia() is not supported by your browser');

  }
}

public accessInputDevice(micstatus: boolean) {
  window.AudioContext = window.AudioContext;

  const context = new AudioContext();
  const constraints = {
    audio: micstatus,
    video: false
  }

  // initialization
  if (localStorage.getItem('microphone') === null) {
    // just assume it is prompt
    localStorage.setItem('microphone', 'prompt');
  }

  // Then somewhere
  navigator.getUserMedia({audio: true}, function (e) {
    // http://stackoverflow.com/q/15993581/1008999
    //
    // In chrome, If your app is running from SSL (https://),
    // this permission will be persistent.
    // That is, users won't have to grant/deny access every time.
    localStorage.setItem("voice_access", "granted");

  }, function (err) {
    if (err.name === 'PermissionDismissedError') {
      localStorage.setItem('voice_access', 'prompt')
    }
    if (err.name === 'PermissionDeniedError') {
      localStorage.setItem('voice_access', 'denied');
    }
  })

  navigator.mediaDevices.getUserMedia(constraints)
    .then((stream) => {
      const microphone = context.createMediaStreamSource(stream);
      const filter = context.createBiquadFilter();

      // microphone -> filter -> destination
      console.log('Mic: ', microphone);

      microphone.connect(filter);
      filter.connect(context.destination);
    });
}

public gotDevices(deviceInfos: any) {

  for (let i = 0; i !== deviceInfos.length; ++i) {
    const deviceInfo = deviceInfos[i];
    const option = document.createElement('option');
    option.value = deviceInfo.deviceId;
    if (deviceInfo.kind === 'audioinput') {
      option.text = deviceInfo.label || 'microphone ';
      // this.microphone.appendChild(option);

      console.log('Found device: ', deviceInfo);

      //    } else if (deviceInfo.kind === 'videoinput') {
      //      option.text = deviceInfo.label || 'camera ' +
      //        (videoSelect.length + 1);
      //      videoSelect.appendChild(option);
    } else {
      console.log('Found another kind of device: ', deviceInfo);
    }
  }
}

public getStream() {
  const self = this;
  const constraints = {
    audio: {
      audio: false,
      deviceId: {exact: self.microphone.id}
    },
    //      video: {
    //        deviceId: {exact: videoSelect.value}
    //      }
  };

  navigator.mediaDevices.getUserMedia(constraints).
    then(self.gotStream).catch(self.handleError);
}

public gotStream(stream: any) {
  const self = this;
  window.AudioContext = stream; // make stream available to console
  self.microphone = stream;

}

public handleError(error: any) {


  // log to console first
  console.error('Error: ', error); /* handle the error */
  if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
    // required track is missing
  } else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
    // webcam or mic are already in use
  } else if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
    // constraints can not be satisfied by avb. devices
  } else if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
    // permission denied in browser
  } else if (error.name === 'TypeError' || error.name === 'TypeError') {
    // empty constraints object
  } else {
    // other errors
  }

}

My problem is I get the error: Uncaught (in promise): TypeError: Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio and video must be requested

TypeError: Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio and video must be requested.

The error occurs here:

navigator.mediaDevices.getUserMedia(constraints)
  .then((stream) => {
    const microphone = context.createMediaStreamSource(stream);
    const filter = context.createBiquadFilter();

    // microphone -> filter -> destination
    console.log('Mic: ', microphone);

    microphone.connect(filter);
    filter.connect(context.destination);
  });

I'd like to know why this is happening?

UPDATE:

So, I implemented this solution from here:

https://medium.com/@barzik/the-new-html5-video-audio-api-has-privacy-issues-on-desktop-chrome-5832c99c7659

Then I follow this link:

https://blog.addpipe.com/getusermedia-video-constraints/

Then found that the "TYPE ERROR:" is thrown when both the AUDIO: false and VIDEO: false.

I need to KEEP the video: FALSE. Why? I'm not interested in turning on the video and "terrify" the user thinking that our software is spying. This is a PRIVACY concern. https://blog.addpipe.com/common-getusermedia-errors/

This is the code I implemented:

NOTE: micstatus is being passed when the user CLICKS on an icon of a MICROPHONE. When the user CLICKS, it passes "OFF" or "FALSE". When the user clicks again, it passes "ON" or "TRUE". But, if the VIDEO stays "FALSE", then the TYPE ERROR fires. That's my problem. CHROME does not allow FALSE, FALSE being passed into the getUserMedia() method. The last error is FIRED. That's what I need to get around: KEEP VIDEO OFF and turn the AUDIO ON or OFF thus NOT throwing the TYPE ERROR.

var constraints = {
  video: false,
  audio: micstatus
}

navigator.mediaDevices.getUserMedia(constraints).then(function success(stream) {
  /* do stuff */
  this.success(success,stream);
}).catch(function (err) {
  // log to console first 
  console.log(err); /* handle the error */
  if (err.name == "NotFoundError" || err.name == "DevicesNotFoundError") {
    // required track is missing
    console.log('Required track is missing');
  } else if (err.name == "NotReadableError" || err.name == "TrackStartError") {
    // webcam or mic are already in use
    console.log('Webcam or mic are already in use');
  } else if (err.name == "OverconstrainedError" || err.name == "ConstraintNotSatisfiedError") {
    // constraints can not be satisfied by avb. devices
    console.log('Constraints can not be satisfied by available devices');
  } else if (err.name == "NotAllowedError" || err.name == "PermissionDeniedError") {
    // permission denied in browser
    console.log('Permission Denied.');
  } else if (err.name == "TypeError" || err.name == "TypeError") {
    // empty constraints object
    console.log('Both audio and video are FALSE');
  } else {
    // other errors
    console.log('Sorry! Another error occurred.');
  }
});


// SUCCESS FUNCTION
public success(status: any, stream: any): void {
   // Success!!!
   console.log('Success with the Audio Context', status);
   console.log('Audio Context', stream);
}
Peter The Angular Dude
  • 1,112
  • 5
  • 26
  • 53
  • you should just be able to create a constraints object with audio:true and a deviceid property. example: .getUserMedia( { audio: true, deviceId: { exact: $deviceId.value } } ) – Chris Love Oct 23 '20 at 01:00

2 Answers2

0

This question was backed 5 years back and I just stumbled upon on when I was searching for the same issue that happened to me.

I don't know why, but I noticed that if I try to use getUserMedia() after creating a audio context, then that error comes up.

So if you have like this..

let ctx = new AudioContext();

navigator.mediaDevices.getUserMedia({audio: true});

this will produce that error.

Now if you remove the new AudioContext() line, it will be all fine and you get a stream.

This may help someone. I am still searching for a reason of that behavior.

Rakesh Mehta
  • 519
  • 2
  • 9
-3

You need to check for permissions on camera and micro using async method navigator.permissions.query({ name: 'camera'/*and 'microphone'*/ })

This one returns the object with information about your site's permissions.

For example:

const checkForVideoAudioAccess = async () => {
        try {
          const cameraResult = await navigator.permissions.query({ name: 'camera' });
          // The state property may be 'denied', 'prompt' and 'granted'
          this.isCameraAccessGranted = cameraResult.state !== 'denied';

          const microphoneResult = await navigator.permissions.query({ name: 'microphone' });
          this.isMicrophoneAccessGranted = microphoneResult.state !== 'denied';
        } catch(e) {
          console.error('An error occurred while checking the site permissions', e);
        }

        return true;
      }

Read more about permissions: https://alligator.io/js/permissions-api/

After, you can use the information about permissions when using getUserMedia()

Example again:

navigator.mediaDevices.getUserMedia({
          video: !this.isMicrophoneAccessGranted,
          audio: !this.isCameraAccessGranted,
        })
        .then(() => {
          this.initStream();
        });