Is it possible to check if the user has a camera and microphone and if the permissions have been granted with Javascript?

82,702

Solution 1

Live Demo:

If user didn't allow webcam and/or microphone, then media-devices will be having "NULL" value for the "label" attribute. Above page will show this message: "Please invoke getUserMedia once."

PS. You can type "DetectRTC.MediaDevices" in the Chrome Console developers tool.

Note: It works only in Chrome. Firefox isn't supporting similar API yet. (Updated: Firefox supports as well)

Updated at Dec 16, 2015

Note: Following code snippet works both in Chrome and Firefox.

if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
    // Firefox 38+ seems having support of enumerateDevicesx
    navigator.enumerateDevices = function(callback) {
        navigator.mediaDevices.enumerateDevices().then(callback);
    };
}

var MediaDevices = [];
var isHTTPs = location.protocol === 'https:';
var canEnumerate = false;

if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) {
    canEnumerate = true;
} else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) {
    canEnumerate = true;
}

var hasMicrophone = false;
var hasSpeakers = false;
var hasWebcam = false;

var isMicrophoneAlreadyCaptured = false;
var isWebcamAlreadyCaptured = false;

function checkDeviceSupport(callback) {
    if (!canEnumerate) {
        return;
    }

    if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
        navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack);
    }

    if (!navigator.enumerateDevices && navigator.enumerateDevices) {
        navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator);
    }

    if (!navigator.enumerateDevices) {
        if (callback) {
            callback();
        }
        return;
    }

    MediaDevices = [];
    navigator.enumerateDevices(function(devices) {
        devices.forEach(function(_device) {
            var device = {};
            for (var d in _device) {
                device[d] = _device[d];
            }

            if (device.kind === 'audio') {
                device.kind = 'audioinput';
            }

            if (device.kind === 'video') {
                device.kind = 'videoinput';
            }

            var skip;
            MediaDevices.forEach(function(d) {
                if (d.id === device.id && d.kind === device.kind) {
                    skip = true;
                }
            });

            if (skip) {
                return;
            }

            if (!device.deviceId) {
                device.deviceId = device.id;
            }

            if (!device.id) {
                device.id = device.deviceId;
            }

            if (!device.label) {
                device.label = 'Please invoke getUserMedia once.';
                if (!isHTTPs) {
                    device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.';
                }
            } else {
                if (device.kind === 'videoinput' && !isWebcamAlreadyCaptured) {
                    isWebcamAlreadyCaptured = true;
                }

                if (device.kind === 'audioinput' && !isMicrophoneAlreadyCaptured) {
                    isMicrophoneAlreadyCaptured = true;
                }
            }

            if (device.kind === 'audioinput') {
                hasMicrophone = true;
            }

            if (device.kind === 'audiooutput') {
                hasSpeakers = true;
            }

            if (device.kind === 'videoinput') {
                hasWebcam = true;
            }

            // there is no 'videoouput' in the spec.

            MediaDevices.push(device);
        });

        if (callback) {
            callback();
        }
    });
}

// check for microphone/camera support!
checkDeviceSupport(function() {
    document.write('hasWebCam: ', hasWebcam, '<br>');
    document.write('hasMicrophone: ', hasMicrophone, '<br>');
    document.write('isMicrophoneAlreadyCaptured: ', isMicrophoneAlreadyCaptured, '<br>');
    document.write('isWebcamAlreadyCaptured: ', isWebcamAlreadyCaptured, '<br>');
});

Solution 2

Yes it is quite possible to detect whether a microphone and a camera is available after granting the permission.

Using the old API:

navigator.getUserMedia({ audio: true, video: true}, function (stream) {
     if (stream.getVideoTracks().length > 0 && stream.getAudioTracks().length > 0) {
         //code for when none of the devices are available                       
     } else {
        // code for when both devices are available
     }
}, function (error) { 
   // code for when there is an error
});

Using the newer, promise-based API:

navigator.mediaDevices.getUserMedia({ audio: true, video: true})
   .then(function (stream) {
         if (stream.getVideoTracks().length > 0 && stream.getAudioTracks().length > 0){
             //code for when none of the devices are available
         } else {
            // code for when both devices are available
         }
   })
  .catch(function (error) { 
       // code for when there is an error
   });

Solution 3

Now you can use navigator.permissions also to check permissions already exist

navigator.permissions.query({ name: "camera" }).then(res => {
    if(res.state == "granted"){
        // has permission
    }
});

See MDN for more info.

But note that support is patchy as of Jan 2021:

  • Chrome supports navigator.permissions.query as of Chrome 43+, and supports querying camera and microphone permissions, at least as of Chrome 87+.
  • Firefox supports navigator.permissions.query as of Firefox 46+, but does not support querying camera or microphone permissions as of Firefox 84.
  • Safari does not even support navigator.permissions.query.

Solution 4

1)You should be using Media Recorder and understand promise

2)Check if browser support the API enumerateDevices

if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
  console.log("This browser does not support the API yet");
}
3) Check if user has conected audio and camera, the only values are "videoinput", "audioinput" or "audiooutput" DeviceInfo.kind

let checking=["audioinput","videoinput"];
let onlyHas=[];
navigator.mediaDevices.enumerateDevices()
.then((devices)=> {
  let haveAllDevices=true;
  devices.forEach((device)=>{
    onlyHas.push(device.kind);
    if(!(device.kind==checking[0] || device.kind==checking[1])){
    haveAllDevices=false;
    }
   });
   //do something about ...
  
  
  
})
.catch(function(err) {
  console.log(err.name + ": " + err.message);
});
4)Permissions are reused,it means that if user already has denied permission then when you call getUserMedia the bowser won't prompt anything and will reject the promise promise throwing an error of type DOMException, otherwise it will resolve the promise. When the promise rejects it can be many reasons read, one of then is when user has denied access, when this happens it will throw an DOMException of type NotAllowedError, so for now we are only interested in this error.

If you read DOMException you can see you can acces DOMException.name, this is the one that you should be compared, so:

let constraints={audio:true,video:true};
navigator.mediaDevices.getUserMedia(constraints)
  .then((stream)=>{.....})
  .catch((err)=>
    {if(err.name=="NotAllowedError"){console.log("User has denied accessed")}
    });

PS: About cross browser compatibility MediaRecorder as for today 09/06/2018 it is only supported in chrome and firefox, and the brothers IE and IOS don't https://caniuse.com/#search=MediaRecorder

Solution 5

Please try my simple cross browser code.

Attention!!! Use https protocol for open web page with my code! Please go to demo

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <h1>Web camera</h1>
    <video autoplay></video>

    <script>
        function errorMessage(message, e) {
            console.error(message, typeof e == 'undefined' ? '' : e);
            //alert(message);
        }

        if (location.protocol === 'https:') {
            navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
            if (navigator.getUserMedia) {
                navigator.getUserMedia({ audio: true, video: true }, function (stream) {
                    document.querySelector('video').src = window.URL.createObjectURL(stream);
                    var mediaStreamTrack = stream.getVideoTracks()[0];
                    if (typeof mediaStreamTrack != "undefined") {
                        mediaStreamTrack.onended = function () {//for Chrome.
                            errorMessage('Your webcam is busy!')
                        }
                    } else errorMessage('Permission denied!');
                }, function (e) {
                    var message;
                    switch (e.name) {
                        case 'NotFoundError':
                        case 'DevicesNotFoundError':
                            message = 'Please setup your webcam first.';
                            break;
                        case 'SourceUnavailableError':
                            message = 'Your webcam is busy';
                            break;
                        case 'PermissionDeniedError':
                        case 'SecurityError':
                            message = 'Permission denied!';
                            break;
                        default: errorMessage('Reeeejected!', e);
                            return;
                    }
                    errorMessage(message);
                });
            } else errorMessage('Uncompatible browser!');
        } else errorMessage('Use https protocol for open this page.')
  </script>
</body>
</html>

I have tested it into follow browsers:

Windows 10

  • Chrome 52
  • Edge 25
  • Firefox 47
  • IE11 Uncompatible browser!
  • Opera 39
  • Safari 5 Uncompatible browser!

Android

  • Chrome 52
  • Firefox 48
  • Opera 37
Share:
82,702
Amal Antony
Author by

Amal Antony

Updated on November 09, 2021

Comments

  • Amal Antony
    Amal Antony over 2 years

    I would like to find out if the user's device has an attached camera and microphone, and if so, has permissions been granted to get the audio and video stream using Javascript. I want to make this check to be made across Chrome and Firefox at the very least. What's a consistent API for this?

  • Amal Antony
    Amal Antony about 9 years
    This looks interesting! How about browser compatibility though?
  • Muaz Khan
    Muaz Khan about 9 years
    Firefox isn't supporting enumerateDevices or getMediaDevices or MediaStreamTrack.getSources API yet; which means that we can't detect if Firefox is having access to microphone/webcam without manually making getUserMedia requests. Whenever we'll make getUserMedia request in Firefox, it'll show permission popup/dropdown which isn't good for real life usecases.
  • Muaz Khan
    Muaz Khan about 9 years
    Above demo works both in desktop and Andriod ---- using Chrome. It even works in Opera as well.
  • xdumaine
    xdumaine about 9 years
    Firefox 38+ has an API for enumerating devices that works different, but still returns null values for device labels when permission aren't granted. See here for an example that uses my helper library to wrap up the different apis: xdumaine.com/enumerateDevices/test
  • xdumaine
    xdumaine about 9 years
    Just to expand on this - you don't have to use DetectRTC or my library enumerateDevices, you can just use MediaStreamTrack.getSources (chrome only) for listing devices and just checking to see if the results have labels or not.
  • Muaz Khan
    Muaz Khan about 9 years
    @xdumaine +1 to know that Firefox 38+ is supporting enumerateDevices API. I'll investigate further & implement soon. BTW, its harder for newcomers to learn and use tricky APIs. They prefer solution like your library or javascript-shims.
  • Bergi
    Bergi over 8 years
    Can you please add the relevant code to your answer itself, instead of only linking that demo page? And maybe additionally provide links to the documentation of the methods you used?
  • Vitaliy Terziev
    Vitaliy Terziev almost 8 years
    This appears to be working Ffox too, at least on the latest version. I assume that there is still no "industry standard" way for checking this
  • Miron
    Miron almost 6 years
    I see this error Uncaught TypeError: Failed to execute 'getUserMedia' on 'Navigator': 3 arguments required, but only 2 present.. What's the problem?
  • sertsedat
    sertsedat over 5 years
    @MuazKhan why checking !navigator.enumerateDevices && navigator.enumerateDevices at all? Doesn't it give false every time?
  • octavn
    octavn about 5 years
    Why exactly should he be using the Media​Stream Recording API ?
  • John Balvin Arias
    John Balvin Arias about 5 years
    because he wants to record audio and video, and one way to record its to use getUserMedia
  • Shahid Kamal
    Shahid Kamal over 4 years
    Keep in mind that this will keep the camera/microphone stream running unless you close it.
  • Baart
    Baart about 4 years
    replace by navigator.mediaDevices.getUserMedia
  • Darren G
    Darren G over 3 years
    Unfortunately this doesn't work on safari either
  • gimp3695
    gimp3695 over 3 years
    Technically you can use this also on localhost in developer testing if you want to check for that make this change if (location.protocol === 'https:' || location.hostname === 'localhost') {
  • Hitesh Jangid
    Hitesh Jangid almost 3 years
    call your media permission in DetectRTC.load(callback) function, where callback will be navigator.mediaDevices.getUserMedia & you can pass video: DetectRTC.hasWebcam & audio:DetectRTC.hasMicrophone to avoid catch issue in case one of video or audio device is not available.
  • Alejandro B.
    Alejandro B. over 2 years
    just a comment: device.label will only have a value if it is currently streaming or has persistent permission granted, thus this code may incorrectly return false