3、媒体流获取:getUserMedia API详解、音视频约束配置、屏幕共享与录制
好,咱们今天聊点实在的。WebRTC 的第一步是什么?不是信令,不是 ICE,而是——拿到媒体流。说白了,你得先有摄像头和麦克风的数据,才能谈后面的传输。这个入口就是 getUserMedia。
我在项目中见过太多人一上来就调 API,结果要么黑屏,要么没声音,要么浏览器直接报错。嗯,这章咱们就把这个坎儿彻底踩平。
3.1 getUserMedia 基础调用
先看最基础的用法。你只需要传一个约束对象,它返回一个 Promise。
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
// 拿到 stream,可以赋值给 video 标签
videoElement.srcObject = stream;
})
.catch(err => {
console.error('获取媒体流失败:', err);
});
就这么简单?对,但坑也在这里。你想想看,如果用户拒绝授权,或者设备不存在,Promise 就会 reject。我曾经在一个项目中忘了处理 catch,结果用户点了「拒绝」之后整个页面卡死——嗯,那场面挺尴尬的。
getUserMedia必须在 HTTPS 或 localhost 下调用- 用户必须主动触发(比如点击按钮),不能页面加载就自动弹窗
- 返回的
MediaStream包含多个MediaStreamTrack
3.2 音视频约束配置详解
约束配置才是真正体现功力的地方。很多人只传 { video: true },但实际项目中你需要精确控制分辨率、帧率、码率,甚至指定使用前置还是后置摄像头。
3.2.1 视频约束
const constraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30, min: 15, max: 60 },
facingMode: 'user', // 'user' 前置, 'environment' 后置
aspectRatio: 16 / 9
},
audio: {
sampleRate: { ideal: 48000 },
channelCount: { ideal: 2 },
echoCancellation: true,
noiseSuppression: true
}
};
这里有个细节:ideal 是理想值,浏览器会尽量满足但不保证;min 和 max 是硬约束,不满足就直接 reject。我建议你只用 ideal,除非你有非常特殊的需求。
enumerateDevices() 获取设备列表,再根据用户选择的设备动态生成约束。这样用户体验会好很多。
3.2.2 音频约束
| 属性 | 说明 | 推荐值 |
|---|---|---|
| sampleRate | 采样率,单位 Hz | ideal: 48000 |
| channelCount | 声道数 | ideal: 2(立体声) |
| echoCancellation | 回声消除 | true |
| noiseSuppression | 降噪 | true |
| autoGainControl | 自动增益 | true |
为什么回声消除默认要开?因为不开的话,对方听到的全是回声。我在做视频会议项目时踩过这个坑,测试的时候自己跟自己说话没感觉,一联调才发现问题。
3.3 屏幕共享
屏幕共享用的是 getDisplayMedia,不是 getUserMedia。这个 API 会弹出一个系统级的窗口,让用户选择共享整个屏幕、某个窗口还是某个浏览器标签页。
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always' // 显示鼠标指针
},
audio: {
echoCancellation: true,
noiseSuppression: true
}
});
// 处理 stream
} catch (err) {
if (err.name === 'NotAllowedError') {
console.log('用户取消了屏幕共享');
}
}
}
3.4 媒体录制
拿到流之后,录制就顺理成章了。用 MediaRecorder 可以把流录制成视频文件。
const mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9'
});
let chunks = [];
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) {
chunks.push(event.data);
}
};
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
// 可以下载或预览
};
mediaRecorder.start(1000); // 每秒触发一次 ondataavailable
这里有个小技巧:start() 的参数控制数据回调的频率。设得越小,内存占用越大,但录制更实时。我一般设 1000ms,平衡一下。
- video/webm;codecs=vp8
- video/webm;codecs=vp9
- video/webm;codecs=h264(部分浏览器支持)
- audio/webm;codecs=opus
3.5 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,从设备获取到最终录制,每一步都有对应的 API 和约束。
3.6 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间。
- 在 iOS Safari 上调
facingMode: 'user'发现没效果——后来才知道 iOS 需要先调用enumerateDevices获取设备 ID,再用deviceId精确指定。 - 录制时没检查
mimeType是否支持,结果录出来的文件打不开。建议先调MediaRecorder.isTypeSupported()做兼容。 - 屏幕共享时忘了处理用户取消的情况,导致 Promise 一直 pending。记得加 catch,判断
NotAllowedError。
嗯,这一章的内容就到这里。媒体流获取是整个 WebRTC 的基石,把这块吃透了,后面的信令、传输、渲染都会顺畅很多。