用Javascript检查用户是否有摄像头和麦克风,并且权限已经被授予,这是否可能?

44

我想了解用户设备是否有连接相机和麦克风,如果有,则使用Javascript获取音视频流之前是否已经授予了权限。我希望至少在Chrome和Firefox上进行此检查。有一个一致的API可以实现这个吗?

7个回答

51

现场演示:

如果用户没有允许使用网络摄像头和/或麦克风,则媒体设备的“label”属性将具有"NULL"值。上述页面将显示此消息:"请调用getUserMedia一次。"

PS.您可以在Chrome控制台开发者工具中键入"DetectRTC.MediaDevices"

注意:它仅适用于Chrome。 Firefox尚未支持类似的API。(更新:Firefox也支持)

更新于2015年12月16日

注意:以下代码片段在Chrome和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>');
});

2
火狐浏览器目前还不支持enumerateDevices、getMediaDevices或MediaStreamTrack.getSources API,这意味着我们无法检测到火狐浏览器是否有访问麦克风/摄像头的权限,除非手动进行getUserMedia请求。每当我们在火狐浏览器中进行getUserMedia请求时,它都会显示权限弹出窗口/下拉菜单,这对于实际应用场景并不好。 - Muaz Khan
3
Firefox 38+提供了一个用于枚举设备的API,其工作方式有所不同,但在未授予权限时仍会返回设备标签的空值。请参见此处的示例,该示例使用我的辅助库来包装不同的API:https://www.xdumaine.com/enumerateDevices/test/ - xdumaine
1
只是为了扩展一下 - 你不必使用DetectRTC或我的库enumerateDevices,你可以直接使用MediaStreamTrack.getSources(仅限Chrome)来列出设备,并检查结果是否具有标签。 - xdumaine
1
@xdumaine +1 知道 Firefox 38+ 支持 enumerateDevices API。我会进一步调查并尽快实现。顺便说一句,对于新手来说,学习和使用棘手的API更加困难。他们更喜欢像你的库或javascript-shims这样的解决方案。 - Muaz Khan
2
@MuazKhan 为什么要检查 !navigator.enumerateDevices && navigator.enumerateDevices?它不是每次都会返回 false 吗? - sertsedat
显示剩余5条评论

19
现在您也可以使用 navigator.permissions 来检查权限是否已经存在。
navigator.permissions.query({ name: "camera" }).then(res => {
    if(res.state == "granted"){
        // has permission
    }
});

更多信息请参见 MDN 文档。

但需要注意的是,截至 2021 年 1 月,支持度不够完整:

  • Chrome 43+ 支持 navigator.permissions.query,并且至少从 Chrome 87+ 开始支持查询 cameramicrophone 权限。
  • Firefox 46+ 支持 navigator.permissions.query,但是截至 Firefox 84,不支持查询 cameramicrophone 权限。
  • Safari 甚至不支持 navigator.permissions.query

3
很遗憾,这在Safari上也不起作用。 - Darren G

14

在授权后,检测麦克风和摄像头是否可用是完全可能的。

使用旧的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
});

使用更新的基于 Promise 的 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
   });

我看到这个错误:“Uncaught TypeError: Failed to execute 'getUserMedia' on 'Navigator': 3 arguments required, but only 2 present.”。问题出在哪里? - Miron
2
替换为 navigator.mediaDevices.getUserMedia - Baart

8

1)您应该使用Media Recorder并了解promise

2)检查浏览器是否支持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.

如果您阅读 DOMException,您将看到可以访问DOMException.name,这是您应该进行比较的内容,因此:

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: 关于跨浏览器兼容性,截至今天2018年09月06日,MediaRecorder 仅支持 Chrome 和 Firefox 浏览器,而 IE 和 IOS 则不支持。https://caniuse.com/#search=MediaRecorder


他为什么要使用Media​Stream Recording API呢? - octavn
因为他想录制音频和视频,而使用getUserMedia是一种录制的方法。 - John Balvin Arias
1
请记住,除非您关闭它,否则这将保持相机/麦克风流运行。 - Shahid Kamal

3

该函数检查用户是否具有音频和视频访问权限:

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');
        }
    ));
}

1
只是一条注释:如果device.label仅在当前正在流传或已授予权限的情况下才有值,则此代码可能会错误地返回false。 - Alejandro B.

1
请尝试我的简单跨浏览器代码。 注意!使用https协议打开我的代码所在的网页!请前往演示

<!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>

我已经在以下浏览器中测试过它:
Windows 10
- Chrome 52 - Edge 25 - Firefox 47 - IE11 不兼容的浏览器! - Opera 39 - Safari 5 不兼容的浏览器!
Android
- Chrome 52 - Firefox 48 - Opera 37

如果你想在开发测试中也在本地主机上使用,从技术上讲,你也可以这样做,如果你想检查一下,请进行以下更改:if (location.protocol === 'https:' || location.hostname === 'localhost') { - gimp3695

1
您可以使用表示媒体流的MediaStreamTrack,然后按照此处所述使用其getSources方法:html5rocks 如果没有获取到任何媒体源,则表示您的客户端没有摄像头。在Firefox中不支持。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接