3、获取本地音视频流:navigator.mediaDevices.getUserMedia API详解
好,咱们直接进入正题。这一章要聊的,是 WebRTC 最基础、也最关键的一步——拿到本地摄像头和麦克风的数据。说白了,就是让浏览器开口说话、睁眼看世界。
我刚开始做 WebRTC 的时候,以为这步就是调个 API 完事。结果呢?踩了一堆坑。有的浏览器不支持,有的用户摄像头权限没开,有的分辨率设了跟没设一样……嗯,今天咱们就把这些坑一个个填平。
核心一句话:navigator.mediaDevices.getUserMedia 是浏览器获取音视频设备的统一入口。它返回一个 Promise,resolve 后给你一个 MediaStream 对象。
3.1 基础用法:最简单的 Demo
先看一个最简版本。你打开控制台,直接贴进去就能跑。
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
.then(function(stream) {
// 拿到流了,可以塞给 video 标签
let video = document.getElementById('localVideo');
video.srcObject = stream;
})
.catch(function(err) {
console.log('出错了:', err);
});
这段代码干了三件事:
- 请求摄像头和麦克风权限
- 拿到媒体流
- 把流绑定到 video 元素上
注意,video.srcObject 是直接赋 stream,不是赋 URL。我见过有人写成 video.src = URL.createObjectURL(stream),那是老古董写法了,现在不推荐。
小技巧:如果你只想测试摄像头,把 audio 设成 false 就行。反过来,只想测麦克风,video 设 false。
3.2 音视频约束参数:别让摄像头瞎拍
不加约束的 getUserMedia,浏览器会按默认参数来。默认分辨率可能是 640x480,也可能是 320x240,看设备心情。这显然不行。
我个人习惯,一定会传一个 constraints 对象,把分辨率、帧率、码率都写清楚。
3.2.1 视频约束
let constraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 },
bitrate: { ideal: 2000000 } // 2 Mbps
},
audio: true
};
这里有几个关键点:
- width / height:可以用 exact、ideal、min、max。exact 是强制要求,不满足就报错。ideal 是首选,浏览器会尽量满足。
- frameRate:帧率。30fps 是主流,60fps 对带宽要求高。
- bitrate:码率。注意,这个不是所有浏览器都支持。Chrome 支持,Safari 可能忽略。
注意:exact 慎用。我曾经在项目里设了 exact: 1920x1080,结果用户笔记本摄像头只支持 1280x720,直接报错。后来改成 ideal,问题解决。
3.2.2 音频约束
音频约束相对简单,但有几个参数值得关注:
let audioConstraints = {
audio: {
sampleRate: { ideal: 48000 },
channelCount: { ideal: 2 },
echoCancellation: true,
noiseSuppression: true
}
};
- sampleRate:采样率。48000 Hz 是 CD 音质,16000 Hz 够用。
- channelCount:声道数。1 是单声道,2 是立体声。
- echoCancellation:回声消除。做视频通话一定要开,不然对方能听到自己的回声。
- noiseSuppression:降噪。开。
我记得有一次做远程医疗项目,用户说声音有回音。排查半天,发现是 echoCancellation 没设。加上之后,世界清净了。
3.3 处理浏览器兼容性
getUserMedia 这个 API 经历过一次大迁移。老版本是 navigator.getUserMedia,新版本是 navigator.mediaDevices.getUserMedia。虽然现在主流浏览器都支持新版,但保不齐有用户用旧版浏览器。
我的做法是写一个兼容函数:
function getMedia(constraints) {
// 优先用新版 API
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
// 回退到老版 API
let getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('浏览器不支持 getUserMedia'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
这段代码做了三件事:
- 检测新版 API 是否存在
- 不存在则尝试各浏览器前缀的老版 API
- 都不支持则返回 rejected Promise
避坑指南:我曾经在 iOS 微信内置浏览器上遇到过问题。那个浏览器不支持 navigator.mediaDevices,但支持 webkitGetUserMedia。所以兼容代码一定要写全。
3.4 显示本地视频流
拿到 MediaStream 之后,怎么显示?很简单,绑定到 video 标签就行。
<video id="localVideo" autoplay playsinline muted></video>
注意几个属性:
- autoplay:自动播放。不加的话,视频不会动。
- playsinline:在 iOS Safari 上,不加这个,视频会全屏播放。
- muted:静音。本地预览如果不静音,会听到自己的声音,产生啸叫。
JavaScript 绑定:
let video = document.getElementById('localVideo');
video.srcObject = stream;
就这么简单。但有个细节:srcObject 赋值后,要等 video 的 loadedmetadata 事件触发,才能拿到视频的宽高信息。
video.onloadedmetadata = function() {
console.log('视频宽度:', video.videoWidth);
console.log('视频高度:', video.videoHeight);
};
3.5 知识体系图
下面这张图,把本章的核心逻辑串起来了。你可以保存下来,以后忘了就看看。
3.6 完整示例代码
最后,给一个完整的、可直接运行的示例。包含了约束、兼容性处理、错误捕获。
<!DOCTYPE html>
<html>
<head>
<title>获取本地音视频流</title>
</head>
<body>
<video id="localVideo" autoplay playsinline muted width="640" height="480"></video>
<script>
function getMedia(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
let getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;
if (!getUserMedia) {
return Promise.reject(new Error('浏览器不支持'));
}
return new Promise(function(resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
let constraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true,
noiseSuppression: true
}
};
getMedia(constraints)
.then(function(stream) {
let video = document.getElementById('localVideo');
video.srcObject = stream;
})
.catch(function(err) {
console.error('获取媒体流失败:', err);
alert('无法获取摄像头/麦克风权限,请检查设置');
});
</script>
</body>
</html>
重要提醒:getUserMedia 必须在 HTTPS 或 localhost 下才能使用。HTTP 环境下会直接报错。这是浏览器安全策略,没得商量。
好了,这一章的内容就到这里。你只要把上面这些搞明白,获取本地音视频流这块基本就稳了。下一章咱们聊聊怎么把流传给对方——也就是信令和连接建立。
公众号:蓝海资料掘金营,微信deep3321