109

How do we go about requesting camera/microphone access with getUserMedia() after being denied once?

I'm working with getUserMedia to access the user's camera and pipe the data to a canvas. That bit all works fine.

In testing, I hit deny once. At this point in Chrome and Firefox, any subsequent requests with getUserMedia() default to the denied state.

We obviously don't want to annoy the hell out of our users by requesting permissions for camera/microphone on every page load after being denied. That's already annoying enough with the geolocation api.

However, there has to be a way to request it again. Simply because a user hit deny once doesn't mean they want to deny webcam access for all time.

I've been reading about the spec and googling around for a while but I'm not finding anything explicitly about this problem.

Edit: Further research, it appears that hitting Deny in Chrome adds the current site to a block list. This can be manually accessed via chrome://settings/content. Scroll to Media. Manage Exceptions, remove the blocked site(s).

Linking to chrome://settings/content doesn't work (in the case where we want to add a helpful link to let people re-enable permissions).

The whole UX for dealing with permissions around getUserMedia stinks. =(

Geuis
  • 41,122
  • 56
  • 157
  • 219
  • 1
    Thanks for this. I couldn't see the Media section when going directly through Settings > Show advanced settings, but through chrome://settings/content – Teknotica Jul 10 '13 at 15:31
  • Denying subsequent requests after hitting deny once, is Chrome behavior, not Firefox. Only if you select "Always deny" in the dropdown on an https site does that happen in Firefox. – jib Oct 20 '15 at 01:17
  • 3
    In Chrome, users can click on the camera icon in the url bar to undo a previous block or manage the block list. No need to mess with chrome:// links – jib Oct 20 '15 at 01:22

6 Answers6

35

jeffreyveon's answer will help reduce the chance that your user will choose deny, since she will only have to choose once.

In case she does click deny, you can provide a message that explains why you need the permission and how to update her choice. For example:

navigator.getUserMedia (
   // constraints
   {
      video: true,
      audio: true
   },

   // successCallback
   function(localMediaStream) {
      var video = document.querySelector('video');
      video.src = window.URL.createObjectURL(localMediaStream);
      video.onloadedmetadata = function(e) {
         // Do something with the video here.
      };
   },

   // errorCallback
   function(err) {
    if(err === PERMISSION_DENIED) {
      // Explain why you need permission and how to update the permission setting
    }
   }
);
jrullmann
  • 2,912
  • 1
  • 25
  • 27
  • 1
    Can I avoid clicking allow on camera access when page loads. Can it be controlled using JavaScript – Vivek Ranjan May 21 '15 at 16:10
  • 7
    No you cannot, and fortunately!!! It would allow websites, apps and whatever to access media devices of people without their consent, which would be a privacy rape... – Flo Schild Oct 25 '16 at 07:55
  • Where does `PERMISSION_DENIED` come from? – xehpuk Dec 26 '22 at 21:36
  • note that the `navigator.getUserMedia` is deprecated and `navigator.mediaDevices .getUserMedia` should be used instead. [doc here](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) – LuckyLikey Jun 27 '23 at 13:27
34

Chrome implements the Permissions API in navigator.permissions, and that applies to both camera and microphone permissions too.

So as of now, before calling getUserMedia(), you could use this API to query the permission state for your camera and microphone :

 navigator.permissions.query({name: 'microphone'})
 .then((permissionObj) => {
  console.log(permissionObj.state);
 })
 .catch((error) => {
  console.log('Got error :', error);
 })

 navigator.permissions.query({name: 'camera'})
 .then((permissionObj) => {
  console.log(permissionObj.state);
 })
 .catch((error) => {
  console.log('Got error :', error);
 })

On success, permissionObj.state would return denied, granted or prompt.

Useful SF question/answer here

For a cross browser solution, one simple approach can be to monitor the time difference between when the getUserMedia() Promise is called, and when it is rejected or resolved, like so :

// In the Promise handlers, if Date.now() - now < 500 then we can assume this is a persisted user setting
var now = Date.now();
navigator.mediaDevices.getUserMedia({audio: true, video: false})
.then(function(stream) {
  console.log('Got stream, time diff :', Date.now() - now);
})
.catch(function(err) {
  console.log('GUM failed with error, time diff: ', Date.now() - now);
});

This Medium article gives more details.

Hope this helps!

Philippe Sultan
  • 2,111
  • 17
  • 23
  • according to https://developer.mozilla.org/en-US/docs/Web/API/Navigator/permissions permissions isn't supported on Internet Explorer, Safari, Safari IOS, and Android web view. Do you know any other method that can be used here.I have also asked a question regarding this https://stackoverflow.com/questions/64679982/how-to-check-camera-permission-which-would-be-supported-across-all-browsers – anup Nov 04 '20 at 12:50
  • 1
    at the moment [Firefox does not support the `camera` permission name](https://stackoverflow.com/questions/53147944/firefox-permission-name-member-of-permissiondescriptor-camera-is-not-a-vali) – ffigari Nov 11 '21 at 16:15
22

Use HTTPS. When the user gives permission once, it's remembered and Chrome does not ask for permission for that page again and you get access to the media immediately. This does not provide you a way to force the permission bar on the user again, but atleast makes sure you don't have to keep asking for it once the user grants the permission once.

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.

See: http://www.html5rocks.com/en/tutorials/getusermedia/intro/

jeffreyveon
  • 13,400
  • 18
  • 79
  • 129
  • 3
    HTTPs on Firefox won't persist this permission. – Rui Marques Jan 29 '14 at 17:41
  • 1
    This now works in firefox, but the "Always Share" option is a bit hidden under the dropdown arrow. – xdumaine Apr 06 '15 at 15:26
  • "Always share" is a bit hidden in Firefox the same way "Always deny" is. OP's problem does not happen in Firefox. – jib Oct 20 '15 at 01:29
  • 2
    to make it work in firefox: `about:config` -> set `media.navigator.permission.disabled` flag as true, [original answer](http://stackoverflow.com/a/18230492/3074768) – mido Jan 20 '16 at 02:33
  • Thank you mido: that was damned annoying - works now (access local USB webcam in localhost file:/// webpage served in Firefox 50 after initially denying that webcam). Cripes. :-/ – Victoria Stuart Nov 17 '16 at 04:09
9

Please be aware of below points.

1. Localhost: In Localhost Chrome Browser asking permission only one time and Firefox every pageload.

2. HTTPS: Both Browsers Chrome and Firefox asking permission only one time.

Mr doubt
  • 51
  • 1
  • 10
  • 42
6

Ideally, you should be able to use the Permissions API before calling getUserMedia() to find out if the user has already granted or denied access to the camera and microphone, but this isn't yet available on Safari which accounts for a significant portion of users. Calling getUserMedia() will have an error in any one of the following cases:

  • user clicks "Block" and does not allow camera/microphone access
  • the browser itself does not have system permission to access the camera or microphone (common on macOS)
  • another app like Zoom is using the video stream already (common on Windows)
  • ...and more

You would want to prompt for permissions again in any of these cases after the user has resolved the issue.

Philippe Sultan's answer for using getUserMedia() is a great solution for prompting camera and microphone permissions across all browsers. Unforunately, the errors from getUserMedia() are wildly inconsistent across browsers and OS's. Chrome will have "NotReadableError" and "NotAllowedError" while Firefox is presenting a "NotFoundError" or "NotAllowedError". And Firefox is the only browser with any kind of error documentation.

You can use the mic-check package to request permissions for the camera and microphone. It will check the browser/OS to group all of the errors into more user-actionable error categories, like UserPermissionDenied, SystemPermissionDenied, and CouldNotStartVideoSource.

Install it with npm i mic-check or yarn add mic-check.

Use it with the code below:

import {
  MediaPermissionsError
  MediaPermissionsErrorType,
  requestMediaPermissions
} from 'mic-check';

requestMediaPermissions()
    .then(() => {
        // can successfully access camera and microphone streams
        // DO SOMETHING HERE
    })
    .catch((err: MediaPermissionsError) => {
        const { type, name, message } = err;
        if (type === MediaPermissionsErrorType.SystemPermissionDenied) {
            // browser does not have permission to access camera or microphone
        } else if (type === MediaPermissionsErrorType.UserPermissionDenied) {
            // user didn't allow app to access camera or microphone
        } else if (type === MediaPermissionsErrorType.CouldNotStartVideoSource) {
            // camera is in use by another application (Zoom, Skype) or browser tab (Google Meet, Messenger Video)
            // (mostly Windows specific problem)
        } else {
            // not all error types are handled by this library
        }
    });

Hope this helps! You can also learn more about the problem here.

Brian Li
  • 2,260
  • 1
  • 15
  • 18
3

The updated answer to this is that Chrome (currently testing on 73) no longer continuously prompts for camera access when the request is over HTTP.

Firefox however, does.

Adrian Lynch
  • 8,237
  • 2
  • 32
  • 40
  • 1
    How do I force it to pop up? It's acting like I have no permissions over HTTP to my local device. – Jonathan Sep 04 '19 at 18:04