SET001
SET001

Reputation: 11728

Make video stream Optional in getUserMedia

In my application a user can make calls if he can produce an audio stream. So, I need to require access to the microphone (audio stream). Without it the application should throw an error. Video is optional. So, I call navigator.getUserMedia and put constraints like this:

{ audio: true, video: false }

And it throws an error when a microphone is not present, just like I need. But a side effect from this is that if the user also has access to a camera, video is not present in stream.

But if I set both audio and video to true I would have an error in cases when users have a microphone but do not have access to a camera (which is ok according to my application logic)

How I can make a video stream optional get getUserMedia?

Upvotes: 5

Views: 3286

Answers (3)

Malcolm Crum
Malcolm Crum

Reputation: 4879

The accepted answer doesn't handle the case in which they have rejected camera permission but allowed mic permission (or vice versa). If you assume you have both, and proceed to ask for a camera and mic in your getUserMedia call, you'll get permission denied.

const devices = await navigator.mediaDevices.enumerateDevices()
const mics = devices.filter(device => device.kind == "audioinput")
const cams = devices.filter(device => device.kind == "videoinput")
const allowedMicPermission = mics.some(device => device.label != '')
const allowedWebcamPermission = cams.some(device => device.label != '')
const hasMic = mics.length > 0
const hasCam = cams.length > 0
// if no permission, assume we haven't asked them yet.
const constraints = !allowedMicPermission && !allowedWebcamPermission
   ? { audio: hasMic, video: hasCam}
   : { audio: allowedMicPermission && hasMic, video: allowedWebcamPermission && hasCam }
return navigator.mediaDevices.getUserMedia(constraints);

This is all to try to infer what permissions the user has given us. A much easier way is to ask the browser what permissions the user has given us, but that only works in Chrome right now, and doesn't seem likely to change.

Upvotes: 2

jib
jib

Reputation: 42490

There now exists another way. You can now check directly whether the user has a camera and/or a microphone before calling getUserMedia:

navigator.mediaDevices.enumerateDevices()
  .then(devices => {
    const cams = devices.filter(device => device.kind == "videoinput");
    const mics = devices.filter(device => device.kind == "audioinput");

    const constraints = { video: cams.length > 0, audio: mics.length > 0 };
    return navigator.mediaDevices.getUserMedia(constraints);
  })
  .then(stream => video.srcObject = stream)
  .catch(failed);

The other answer works as well, but this may be a bit cleaner.

Note that this is using enumerateDevices, which returns a list of devices with information. The amount of information returned is limited for privacy reasons ahead of calling getUserMedia, but it will still reveal whether the user has at least one camera and/or at least one microphone, which is all we need here.

Upvotes: 12

Benjamin Trent
Benjamin Trent

Reputation: 7566

A solution I have found is to call getUserMedia with video and audio enabled, if the call fails(meaning that they either don't have a camera or a mic) then you call getUserMedia again from the failure callback that you provide requesting access to the microphone only.

var failedLocalAudioAndVideoStreamCallBack = function (error) {
      getUserMedia({ audio: true, video: false }, 
      gotLocalAudioStreamCallBack, failedLocalAudioStreamCallBack )});
    }

    getUserMedia({ audio: true, video: true },
    gotLocalAudioAndVideoStreamCallBack, failedLocalAudioAndVideoStreamCallBack); 

Of course, you can handle successes and failures however you like.

NOTE: if there is no camera, the pop-up requesting the initial camera feed(that will fail) never occurs. So, the user will only get one request for access(which makes this solution a tad bit more palatable).

Upvotes: 5

Related Questions