第一章 媒体流获取实战:用 getUserMedia 搞定音视频
各位同学,咱们直接进入正题。
WebRTC 的第一步是什么?说白了,就是拿到用户的摄像头和麦克风。没有媒体流,后面的一切都是空谈。这一章,我就带你亲手把音视频流“抓”到浏览器里来。
1.1 核心 API:navigator.mediaDevices.getUserMedia
这个 API 是 WebRTC 的入口,也是你第一个要记住的方法。它的作用很简单——请求用户授权,然后返回一个 MediaStream 对象。
我个人习惯把 getUserMedia 叫做“权限敲门砖”。你想想看,浏览器为了保护隐私,不可能让网页随便打开摄像头。所以这个方法会弹出一个授权对话框,用户点了“允许”,你才能拿到流。
基础语法:
const stream = await navigator.mediaDevices.getUserMedia(constraints);
constraints 是一个对象,用来指定你要获取什么类型的媒体。
1.2 只拿视频流:摄像头画面
先来个最简单的——只获取摄像头视频。
async function getVideoOnly() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
const videoElement = document.getElementById('localVideo');
videoElement.srcObject = stream;
} catch (err) {
console.error('获取视频流失败:', err);
}
}
这里有个细节:video: true 表示使用默认摄像头。如果你有多个摄像头,可以指定设备 ID。我在项目中遇到过用户插了外接摄像头,结果默认选的是笔记本内置摄像头,画质很差。后来我加了设备选择逻辑,才解决这个问题。
小技巧: 可以用 video: { width: 1280, height: 720 } 来指定分辨率。但注意,不是所有摄像头都支持你指定的分辨率,浏览器会选最接近的。
1.3 只拿音频流:麦克风录音
有时候你只需要声音,比如做语音通话或录音功能。
async function getAudioOnly() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: false,
audio: true
});
// 可以把音频流传给 Web Audio API 或者 RTCPeerConnection
console.log('获取到音频流,轨道数:', stream.getAudioTracks().length);
} catch (err) {
console.error('获取音频流失败:', err);
}
}
嗯,这里要注意:audio: true 会使用默认麦克风。如果你想用耳麦或者外接麦克风,同样需要设备选择。我曾经在会议室调试时,发现麦克风采集到的全是回声,后来加了回声消除约束才搞定。
避坑指南: 我曾经在 Chrome 上遇到一个坑——如果用户之前拒绝了权限,再次调用 getUserMedia 不会弹窗,而是直接抛出一个 NotAllowedError。所以一定要做好错误处理,提示用户去浏览器设置里开启权限。
1.4 同时获取音视频流
这才是最常见的场景——视频通话、直播推流,都需要同时拿到音视频。
async function getBoth() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 48000
}
});
// 此时 stream 包含视频轨道和音频轨道
console.log('视频轨道:', stream.getVideoTracks().length);
console.log('音频轨道:', stream.getAudioTracks().length);
} catch (err) {
console.error('获取音视频流失败:', err);
}
}
看到没?这里我加了更精细的约束。视频方面指定了理想分辨率和帧率,音频方面开启了回声消除和降噪。这些在实际项目中非常有用。
核心知识点: MediaStream 对象包含多个 MediaStreamTrack。你可以通过 stream.getVideoTracks() 和 stream.getAudioTracks() 分别操作它们。比如关闭某个轨道:track.stop()。
1.5 处理设备权限问题
权限问题是我见过最多人踩坑的地方。咱们系统梳理一下。
| 错误类型 | 错误名称 | 常见原因 |
|---|---|---|
| 用户拒绝 | NotAllowedError | 用户点击了“阻止” |
| 设备不存在 | NotFoundError | 没有摄像头或麦克风 |
| 设备忙碌 | NotReadableError | 摄像头被其他应用占用 |
| 安全限制 | SecurityError | 非 HTTPS 或本地文件协议 |
为什么会这样?因为浏览器对媒体权限非常敏感。我建议你在调用 getUserMedia 之前,先检查一下环境:
// 检查浏览器是否支持
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
alert('您的浏览器不支持媒体设备 API');
return;
}
// 检查是否 HTTPS(本地 localhost 除外)
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
alert('请在 HTTPS 环境下使用');
return;
}
我的经验: 在移动端 Safari 上,getUserMedia 必须由用户手势触发(比如点击按钮),否则会静默失败。所以记得把获取流的逻辑放在按钮点击事件里,不要放在页面加载时自动执行。
1.6 本章知识体系
下面这张图帮你理清 getUserMedia 的核心流程:
1.7 完整示例代码
最后,我给你一个可以直接运行的完整示例。把下面这段代码放到 HTML 文件中,点击按钮就能看到效果。
<!DOCTYPE html>
<html>
<body>
<video id="localVideo" autoplay playsinline></video>
<button id="startBtn">开启摄像头</button>
<button id="stopBtn">关闭摄像头</button>
<script>
let stream = null;
document.getElementById('startBtn').onclick = async () => {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480 },
audio: { echoCancellation: true }
});
document.getElementById('localVideo').srcObject = stream;
} catch (err) {
if (err.name === 'NotAllowedError') {
alert('请允许摄像头权限');
} else if (err.name === 'NotFoundError') {
alert('未检测到摄像头设备');
} else {
alert('获取媒体流失败:' + err.message);
}
}
};
document.getElementById('stopBtn').onclick = () => {
if (stream) {
stream.getTracks().forEach(track => track.stop());
document.getElementById('localVideo').srcObject = null;
}
};
</script>
</body>
</html>
重要提醒: 记得在 HTTPS 或 localhost 下运行。直接用 file:// 协议打开会报 SecurityError。我刚开始学的时候就被这个坑过,折腾了半天才发现是协议问题。
好了,这一章的内容就到这里。getUserMedia 是 WebRTC 的基石,你把它玩熟了,后面学音视频传输、录制、截图都会轻松很多。下一章咱们聊聊如何枚举设备,让你能自由选择摄像头和麦克风。
公众号:蓝海资料掘金营,微信deep3321