使用 WebRTC 时,调用 mediaDevices 相关接口必须要先使用 getUserMedia 获取摄像头/麦克风权限
版本一:直接获取权限
// 释放 mediaStream
const stopStreamTracks = stream => {
if (!stream || !stream.getTracks) {
return;
}
try {
const tracks = stream.getTracks();
tracks.forEach(it => {
try {
it.stop();
} catch (errMsg) {
// debugger;
}
});
} catch (errMsg) {
// debugger;
}
};
// 请求 摄像头/麦克风 权限
const requestPermission = async () => {
try {
const stream = await global.navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stopStreamTracks(stream);
if (stream) {
return true;
}
} catch (errMsg) {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
}
return true;
};
正常情况下可以工作,但是当电脑没有摄像头或者麦克风是,并不会弹出权限允许的弹窗,此时 getUserMedia 直接 rejected,返回了 device not found 异常,基于这样的问题,开发了版本二。
版本二:过滤设备列表,动态请求设备权限
// 获取设备列表
const enumerateDevices = async () => {
try {
const devices = await global.navigator.mediaDevices.enumerateDevices();
return devices;
} catch (errMsg) {
return [];
}
};
// 请求 摄像头/麦克风 权限
const requestPermission = async () => {
const devices = await enumerateDevices();
const constraints = devices.reduce(
(info, device) => {
if (!device) {
return info;
}
if ('videoinput' === device.kind) {
return { video: true, audio: info.audio };
}
if ('audioinput' === device.kind) {
return { audio: true, video: info.video };
}
return info;
},
{ video: false, audio: false },
);
try {
const stream = await global.navigator.mediaDevices.getUserMedia(constraints);
stopStreamTracks(stream);
} catch (errMsg) {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
}
return true;
};
此版本也正常工作了一段时间,直到碰到了在 Linux 下使用 PCIE 采集卡的客户,因为在 getUserMedia 之前调用了 enumerateDevices,在 linux 下的 chrome 中使用 4 路 PCIE 采集卡,设备列表返回的设备 deviceId 位空且只有一个设备,与预期不符,基于这样的问题,开发了版本三。
版本三:逐步回退,多次检查
- 第一步:尝试同时获取摄像头和麦克风权限,如果是 NotAllowedError 异常的话说明用户拒绝提供权限,否则进入下一步
- 第二步:单独获取麦克风权限(业务选择麦克风比摄像头重要),如果异常检查原因,进入下一步
- 第三步:单独获取摄像头权限,检查异常原因
- 第四步:如果三次异常都不是 NotAllowedError导致的,那么可以认为是设备异常导致的,不要提示用户权限不够
// 请求 摄像头/麦克风 权限
const requestPermission = async () => {
// 第一步:尝试同时获取摄像头和麦克风权限
try {
const stream = await global.navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stopStreamTracks(stream);
if (stream) {
return true;
}
} catch (errMsg) {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
}
try {
const stream = await global.navigator.mediaDevices.getUserMedia({ video: false, audio: true });
stopStreamTracks(stream);
if (stream) {
return true;
}
} catch (errMsg) {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
}
try {
const stream = await global.navigator.mediaDevices.getUserMedia({ video: true, audio: false });
stopStreamTracks(stream);
if (stream) {
return true;
}
} catch (errMsg) {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
}
return true;
};
版本三暂时没有发现问题,后续有问题继续更新。
稍微优化了一下代码
function getDevicePermission(constraints) {
return global.navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
if (stream) {
stopStreamTracks(stream);
return true;
}
return Promise.reject(new Error('EmptyStreamError'));
})
.catch(errMsg => {
if (errMsg && 'NotAllowedError' === errMsg.name) {
return false;
}
return Promise.reject(errMsg);
});
}
function requestPermission() {
return getDevicePermission({ video: true, audio: true })
.catch(() => getDevicePermission({ video: false, audio: true }))
.catch(() => getDevicePermission({ video: true, audio: false }))
.catch(() => true);
}