44

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
  • 6,477
  • 14
  • 53
  • 76

7 Answers7

51

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>');
});
Muaz Khan
  • 7,138
  • 9
  • 41
  • 77
  • This looks interesting! How about browser compatibility though? – Amal Antony May 05 '15 at 08:20
  • 2
    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 May 05 '15 at 08:25
  • 1
    Above demo works both in desktop and Andriod ---- using Chrome. It even works in Opera as well. – Muaz Khan May 05 '15 at 08:26
  • 3
    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: https://www.xdumaine.com/enumerateDevices/test/ – xdumaine May 05 '15 at 14:18
  • 1
    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. – xdumaine May 05 '15 at 14:25
  • 1
    @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. – Muaz Khan May 05 '15 at 14:35
  • Can you please add the relevant code to your answer itself, instead of [only linking that demo page](http://meta.stackexchange.com/q/8231/183280)? And maybe additionally provide links to the documentation of the methods you used? – Bergi Dec 15 '15 at 11:43
  • 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 – Vitaliy Terziev Aug 08 '16 at 15:44
  • 2
    @MuazKhan why checking `!navigator.enumerateDevices && navigator.enumerateDevices` at all? Doesn't it give false every time? – sertsedat Sep 03 '18 at 12:04
  • 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. – Hitesh Jangid Jun 28 '21 at 08:50
19

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.
jameshfisher
  • 34,029
  • 31
  • 121
  • 167
Abhay Sehgal
  • 1,603
  • 2
  • 15
  • 21
14

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
   });
Kevin Doyon
  • 3,464
  • 2
  • 33
  • 38
Imal Hasaranga Perera
  • 9,683
  • 3
  • 51
  • 41
  • I see this error ``Uncaught TypeError: Failed to execute 'getUserMedia' on 'Navigator': 3 arguments required, but only 2 present.``. What's the problem? – Miron Jun 12 '18 at 08:33
  • 2
    replace by navigator.mediaDevices.getUserMedia – Baart Feb 10 '20 at 10:13
8

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

John Balvin Arias
  • 2,632
  • 3
  • 26
  • 41
3

This function checks if the user have audio and video access:

checkMediaAccess = async() => {
    navigator.mediaDevices.enumerateDevices().then( devices => 
        devices.forEach( device => {
            if(device.kind == 'audioinput' && device.label) console.log('Has Audio Access');
            if(device.kind == 'videoinput' && device.label) console.log('Has Video Access');
        }
    ));
}
Anish Sapkota
  • 774
  • 9
  • 19
  • 1
    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 – Alejandro B. Sep 13 '21 at 00:11
1

You can use the MediaStreamTrack which represent a media stream, then you can use its getSources method as explained here: html5rocks

If you don't get any media sources then your client hasn't a webcam. It's not supported by firefox.

1

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
Andrej
  • 679
  • 5
  • 14
  • 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') {` – gimp3695 Dec 29 '20 at 00:17