因业务需求,做了一段事件webRTC直播相关项目的开发。为避免遗忘,整理一下JS获取设备信息及常见错误处理方法,以便后期查阅搬砖。
由于受浏览器的限制,navigator.mediaDevices.getUserMedia在https协议下是可以正常使用的,而在http协议下只允许localhost/127.0.0.1这两个域名访问,因此在开发时应做好容灾处理,上线时则需要确认生产环境是否处于https协议下。
1 2 3 4 5
| let mediaDevices = navigator.mediaDevices || null if ( mediaDevices === null ) { console.warn(`请确定是否处于https协议环境下`) return }
|
获取权限的通用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| getUserMedia(constraints = { video: true, audio: true }) { return new Promise((resolve, reject) => { navigator.mediaDevices.getUserMedia(constraints).then(stream => { resolve(stream) }).catch(err => { //log to console first console.log(err); /* handle the error */ let toastText = '未知错误' let errName = err.name if (errName == "NotFoundError" || errName == "DevicesNotFoundError") { //required track is missing toastText = '未发现设备' } else if (errName == "NotReadableError" || errName == "TrackStartError") { //webcam or mic are already in use toastText = '设备被占用' } else if (errName == "OverconstrainedError" || errName == "ConstraintNotSatisfiedError") { //constraints can not be satisfied by avb. devices toastText = '约束无法满足(分辨率)' } else if (errName == "NotAllowedError" || errName == "PermissionDeniedError") { //permission denied in browser toastText = '请求被拒绝' } else if (errName == "TypeError" || errName == "TypeError") { //empty constraints object toastText = '必须至少请求一个音频和视频' } else { //other errors } reject({ toastText, errName }) }); }) }
|
获取设备(摄像头、麦克风、扬声器)列表
我在项目开发中需要用到的硬件参数主要有两种:品牌,分辨率。获取摄像头的品牌名称相对来说比较简单,可直接通过mediaDevices.enumerateDevices()获取电脑上可使用的外设列表,通过kind字段过滤出摄像头。
提示:必须先获取权限才能拿到设备列表
1 2 3 4
| if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { console.error(location.href + ":不支持 enumerateDevices() .") return }
|
完整获取设备列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| // 获取系统设备 async getDevices() { if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) { console.error(location.href + ":不支持 enumerateDevices() .") return } // 初始化设备 const devices = await navigator.mediaDevices.enumerateDevices() console.log(devices) var camList = [],micList = [],spkList = []; for (var i = 0; i !== devices.length; ++i) { var device = devices[i]; var option = {} option.value = device.deviceId; if (device.kind === 'audioinput') { option.text = device.label || 'microphone ' + (micList.length + 1); micList.push(option); } else if (device.kind === 'videoinput') { option.text = device.label || 'camera ' + (camList.length + 1); camList.push(option); } else if (device.kind === 'audiooutput') { option.text = device.label || 'speaker ' + (spkList.length + 1); spkList.push(option); }else{ console.log('Some other kind of source/device: ', device); } } return { camList, micList, spkList, camSelected: this.getDefaultDevice(camList), micSelected: this.getDefaultDevice(micList), spkSelected: this.getDefaultDevice(spkList), } }
|
设备变更
在摄像头拔出的一瞬间,active会从true变更为false,同时触发oninactive钩子,有了状态监听之后事情就简单了许多。代码经过测试后发现,对用户变更摄像头权限也有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // 判断摄像头是否在线 let cameraIsOnline = false const loadWebCamera = () => { ... mediaDevices.getUserMedia({ audio: false, video: true }).then(async (stream) => { const video = document.getElementById('videoPlayer'); if (window.URL) { try { video.src = window.URL.createObjectURL(stream); } catch (err) { video.srcObject = stream; } } else { video.src = stream; } video.autoplay = true; // 兼容性处理 if( stream.oninactive === null ) { // 监听流中断,流中断后将重新进行调用自身进行状态监测 stream.oninactive = () => loadWebCamera() } await video.play() cameraIsOnline = true }).catch((error) => { cameraIsOnline = false ... }) }
|