本文正在参加「金石计划 . 瓜分6万现金大奖」
主要介绍了
enumerateDevices()获取媒体设备列表的通用用法,包含麦克风、摄像头的设备分类;摄像头设备前后置的区分和处理;摄像头前后置的切换处理;避免广角摄像头的选择等等
获取媒体设备列表
通过 navigator.mediaDevices.enumerateDevices 来获取媒体设备列表数据,包括摄像头和麦克风。
注意:此处一个接口中的数据类型有两种,分别为 InputDeviceInfo 和 MediaDeviceInfo
// 获取设备列表
function getDevices() {
return new Promise((resolve, reject) => {
navigator.mediaDevices
.enumerateDevices()
.then((devices) => {
console.log("设备列表数据,整理前", devices);
// 临时展示模块 待todo替换
let mediaList = [];
devices.forEach((device) => {
mediaList.push({
id: device.deviceId,
label: device.label,
kind: device.kind,
});
});
console.log("设备列表数据,整理后", mediaList);
resolve({ flag: true, devices: mediaList });
})
.catch((err) => {
console.error(err.name + ": " + err.message);
reject({ flag: false, err });
});
});
}
媒体设备分类
实际应用中,麦克风、摄像头列表可以分开展示,因为麦克风和摄像头是可以同时存在的
如上图所示,可以通过kind属性来进行区分:视频设备类型为 videoinput;音频设备类型为 audioinput 和 audiooutput(外放设备)。
音频设备,想区分的更细致,可以用两个列表来展示,但是一般没有必要。
getDevices()方法中的“临时展示模块”,替换为如下方式:
// todo
let audioDevices = [];
let videoDevices = [];
devices.forEach((device) => {
// 没有授予硬件权限时,deviceId为空字符串
if (device.deviceId == "communications" || device.deviceId == "") {
return;
}
if (device.kind == "videoinput") {
// 视频设备
videoDevices.push(device);
} else {
// 音频设备
audioDevices.push(device);
}
});
if (videoDevices.length === 0) {
reject({ flag: false, err: "未检测到摄像头" });
return;
}
if (audioDevices.length === 0) {
reject({ flag: false, err: "未检测到麦克风" });
return;
}
// resolve({ flag: true, videoDevices, audioDevices });
前后置摄像头区分
对于 PC 端,只有前置摄像头,一般情况下,只有一个摄像头设备;当然也可以外接摄像头设备,到时候就可以选择打开的摄像头设备了
对于手机端,前后置摄像头是都有的。一般手机通过enumerateDevices()方法获取的前后置摄像头各只有一个;但是有些手机,如华为手机,或者某些手机的火狐浏览器,获取到的设备列表有多个。
区分前后置摄像头:
- 一般可以通过 label 属性区分,但是也有可能 label 值为空,就需要进行单独的处理了
- 同一个华为手机上,自带的浏览器可获取到 7 个摄像头,但是 QQ 浏览器只有 2 个(前后置)摄像头,浏览器底层解析返回的也是不一样的
- 对于华为这种多摄像头的情况,要注意选择正确的摄像头,避免选中广角的摄像头,导致看到的画面不清晰
// todo 如下代码接着上面分类继续
// 后置摄像头列表
let cameraBackList = videoDevices.filter(
(o) => o.label.indexOf("back") > -1 || o.label.indexOf("后") > -1
);
// 前置摄像头列表
let cameraPreList = videoDevices.filter(
(o) => !(o.label.indexOf("back") > -1 || o.label.indexOf("后") > -1)
);
// 没有后置时,取前置最后一个
if (cameraBackList.length === 0) {
cameraBackList.push(cameraPreList[cameraPreList.length - 1]);
}
if (cameraPreList.length === videoDevices.length) {
cameraPreList.length = 1;
}
console.log("前置摄像头列表", cameraPreList);
console.log("后置摄像头列表", cameraBackList);
// 前置正常取第二个;只有一个时,取第一个
let preCameraId = cameraPreList[cameraPreList.length > 1 ? 1 : 0].deviceId;
// 后置正常取最后一个;火狐后置取第一个;华为后置也是取第一个(解决华为火狐浏览器后置选中广角摄像头的问题)
let ua = navigator.userAgent;
let backCameraId =
cameraBackList[
ua.indexOf("Firefox") || ua.indexOf("HUAWEI")
? 0
: cameraBackList.length - 1
].deviceId;
// 麦克风默认取第一个 safari不支持扬声器,即没有audiooutput
let microphoneId = audioDevices[0].deviceId;
console.log("前置摄像头id", preCameraId);
console.log("后置摄像头id", backCameraId);
console.log("麦克风id", microphoneId);
resolve({ flag: true, preCameraId, backCameraId });
摄像头的切换
上面的通用方法:
- 对于 web 端,就是进行摄像头的切换
- 对于手机端,可以进行前后置摄像头的切换
- 获取前后置摄像头 ID ,方便后面进行切换操作
// 需要用到的参数
let localStream = null;
let preCameraId = null;
let backCameraId = null;
let isPre = true;
// 获取前后置摄像头ID
const res = await getDevices();
preCameraId = res.preCameraId;
backCameraId = res.backCameraId;
- 切换视频流的操作,具体方法可看上一篇文章WebRTC 之媒体权限申请 getUserMedia
传参时,可以通过设备ID(deviceId)指定打开某个摄像头,具体实现deviceId: isPre ? backCameraId : preCameraId
// 切换前需要先关闭流
if (localStream) {
localStream.getTracks().forEach((stream: MediaStreamTrack) => {
stream.stop();
});
}
// 预览视频流方法
getUserMedia(
{
video: { deviceId: isPre ? backCameraId : preCameraId },
audio: true,
},
(stream) => {
localStream = stream;
localVideo.current.srcObject = stream;
isPre = !isPre;
}
);