5、媒体流获取:getUserMedia详解、摄像头与麦克风选择、分辨率与帧率设置、媒体约束(MediaConstraints)
好,咱们进入正题。这一章要聊的,是WebRTC里最基础、也最核心的一步——拿到用户的摄像头和麦克风。说白了,没有媒体流,后面的一切都是空谈。
我记得刚接触WebRTC那会儿,觉得getUserMedia就是个简单的API,调一下就能出画面。结果真到项目里,踩的坑一个接一个。用户拒绝权限怎么办?设备选错了怎么办?分辨率不对怎么办?嗯,今天咱们就把这些事彻底聊透。
5.1 getUserMedia:一切的开端
getUserMedia是浏览器提供的一个API,用来获取用户的音视频流。它的调用方式很简单,但背后的逻辑其实挺多的。
// 最基本的调用
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
.then(function(stream) {
// 拿到流了,可以塞给video标签
const video = document.getElementById('localVideo');
video.srcObject = stream;
})
.catch(function(err) {
console.error('获取媒体流失败:', err);
});
你看,就这么几行代码,就能把摄像头画面显示在页面上。但实际项目中,我从来不会这么写。为什么?因为video: true这种写法太粗糙了,浏览器会按默认参数来,画质、帧率、设备选择全交给浏览器决定——这往往不是我们想要的。
5.2 摄像头与麦克风的选择
你有没有遇到过这种情况:用户有前后两个摄像头,但你的应用只能调用默认的那个?我遇到过,而且不止一次。
要解决这个问题,得用到MediaDeviceInfo。浏览器会给你一个设备列表,每个设备都有唯一的deviceId。你可以在getUserMedia里指定用哪个设备。
// 先枚举所有设备
navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
// 过滤出摄像头
const videoDevices = devices.filter(d => d.kind === 'videoinput');
console.log('可用摄像头:', videoDevices);
// 假设我们想用第二个摄像头
if (videoDevices.length > 1) {
return navigator.mediaDevices.getUserMedia({
video: {
deviceId: { exact: videoDevices[1].deviceId }
},
audio: false
});
}
})
.then(function(stream) {
// 拿到指定摄像头的流
});
这里有个细节:deviceId的值每次页面刷新都可能变,所以不能硬编码。我一般会在用户首次进入时枚举一次,然后存到localStorage里,下次直接读取。
kind改成audioinput。另外,enumerateDevices需要在用户授权后才能拿到完整的设备列表,否则只能看到默认设备。
5.3 分辨率与帧率设置
分辨率这事,说起来简单,做起来坑多。你设个1920x1080,浏览器不一定给你这个分辨率。它会找一个最接近的。为什么?因为摄像头硬件支持的分辨率是有限的,不是你想要什么就有什么。
我个人习惯用width和height的约束来指定分辨率,但要注意写法:
// 推荐写法:指定理想值
navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
}
});
// 强制写法:必须精确匹配
navigator.mediaDevices.getUserMedia({
video: {
width: { exact: 1920 },
height: { exact: 1080 }
}
});
ideal是理想值,浏览器会尽量接近,但不保证完全一致。exact是强制值,如果设备不支持,直接报错。我在项目中一般用ideal,除非有特殊需求——比如做扫码应用,必须用特定分辨率。
帧率也是同理。30fps是标准,但有些低端设备只能跑到15fps。你设个ideal: 30,它跑不到也不会报错,只是画面会卡一点。
| 约束类型 | 说明 | 示例 |
|---|---|---|
| ideal | 理想值,浏览器尽量满足 | width: { ideal: 1280 } |
| exact | 精确值,不满足则报错 | width: { exact: 1920 } |
| min | 最小值,低于此值报错 | width: { min: 640 } |
| max | 最大值,高于此值报错 | width: { max: 1920 } |
5.4 媒体约束(MediaConstraints)详解
媒体约束是getUserMedia的灵魂。它不只是控制分辨率和帧率,还能控制很多细节。比如:
- facingMode:控制前置还是后置摄像头。移动端特别有用。
- echoCancellation:回声消除,语音通话必备。
- noiseSuppression:降噪,环境嘈杂时很有用。
- sampleRate:音频采样率,影响音质。
// 一个完整的约束示例
const constraints = {
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 48000
},
video: {
width: { ideal: 1280, max: 1920 },
height: { ideal: 720, max: 1080 },
frameRate: { ideal: 30, max: 60 },
facingMode: { ideal: 'user' } // 前置摄像头
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
// 成功
})
.catch(function(err) {
// 处理错误
});
facingMode的user是前置,environment是后置,别搞反了。
5.5 避坑指南
我曾经在一个视频会议项目里,遇到用户反馈说摄像头打不开。排查了半天,发现是用户同时开了另一个视频软件,摄像头被占用了。这种情况,getUserMedia会抛NotReadableError。解决方案是提示用户关闭其他占用摄像头的应用。
还有一次,用户说画面是黑的。我一看代码,发现他用了exact: 3840x2160,但他的摄像头最高只支持1080p。浏览器直接报错,连降级的机会都没有。从那以后,我基本只用ideal,除非有100%的把握。
另外,移动端有个常见问题:用户拒绝权限后,再次调用getUserMedia会直接失败,不会弹出权限请求。你需要引导用户去系统设置里手动开启权限。这个体验很糟糕,但没办法,浏览器的安全策略就是这样。
5.6 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以看到,从getUserMedia出发,到设备选择、分辨率帧率、约束设置,再到错误处理,每一步都有讲究。
嗯,这一章的内容就这些。getUserMedia看似简单,但用好它,需要你对设备、约束、错误处理都有清晰的认识。下次写代码的时候,记得多想想:用户用的是什么设备?会不会拒绝权限?分辨率设得合不合理?把这些想清楚了,你的应用才能在各种环境下稳定运行。