3、获取媒体流:getUserMedia API详解、摄像头与麦克风权限、媒体约束配置、屏幕共享基础
好,咱们今天来聊聊 WebRTC 里最基础、也最关键的一步——获取媒体流。说白了,就是怎么把用户的摄像头和麦克风给“请”到浏览器里来。你想想看,没有音视频数据,那还谈什么实时通信?
我个人习惯把 getUserMedia 看作是整个 WebRTC 应用的“水龙头”。你拧开它,数据就流进来了。但怎么拧、拧多大、拧哪个方向,这里头门道不少。我在项目中遇到过不少同学,一上来就调 API,结果要么是权限弹窗没处理好,要么是分辨率不对,画面糊成一团。嗯,咱们今天就把它彻底讲透。
3.1 getUserMedia 的前世今生
getUserMedia 这个 API,最早是 Chrome 和 Firefox 在 2013 年左右开始支持的。那时候还叫 navigator.getUserMedia,写法很别扭,还得加前缀。现在好了,标准化的 navigator.mediaDevices.getUserMedia 已经成了主流。
它的核心作用就一个:请求用户授权,然后返回一个 MediaStream 对象。这个对象里,可能包含视频轨道(摄像头画面)、音频轨道(麦克风声音),也可能两者都有。
3.2 基础用法:三步走
用起来其实很简单,就三步:
- 检查浏览器支持——别一上来就调,万一用户用的是老古董呢?
- 调用 getUserMedia——传入约束配置,拿到 Promise。
- 处理结果——成功就把流挂到 video 元素上,失败就提示用户。
来看代码:
// 第一步:检查支持
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error('浏览器不支持 getUserMedia');
return;
}
// 第二步:调用 API
async function startCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 第三步:挂到 video 标签上
const video = document.getElementById('localVideo');
video.srcObject = stream;
} catch (err) {
console.error('获取媒体流失败:', err);
}
}
你看,就这么几行。但这里有个坑——video: true 这种写法,其实是让浏览器自己选默认摄像头和默认分辨率。我曾经在一个项目里这么写,结果用户那边画面只有 320x240,糊得不行。所以,咱们得学会配置约束。
3.3 媒体约束配置:精细控制
媒体约束(MediaConstraints)是 getUserMedia 的灵魂。你可以指定分辨率、帧率、摄像头方向、音频采样率等等。说白了,就是告诉浏览器:“我要什么样的流,你给我按这个标准来。”
约束分两种:
- 强制约束(mandatory):用
min、max、exact关键字。如果达不到,Promise 直接 reject。 - 理想约束(ideal):用
ideal关键字。浏览器会尽量满足,但达不到也不会报错。
来看一个实际配置:
const constraints = {
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
frameRate: { ideal: 30, max: 60 },
facingMode: { ideal: 'user' } // 'user' 前置,'environment' 后置
},
audio: {
echoCancellation: { exact: true },
noiseSuppression: true,
sampleRate: { ideal: 48000 }
}
};
这里我解释一下:
width和height我设了范围,浏览器会选一个最接近 1280x720 的。如果设备只支持 640x480,那也没问题。facingMode用ideal而不是exact,这样如果设备没有前置摄像头,它会自动切到后置,不会报错。- 音频这边,
echoCancellation我用了exact,因为回声消除对通话质量太重要了。如果设备不支持,那这个通话体验会很差,不如直接报错让用户换设备。
facingMode 的 user 和 environment 是必配的。否则默认可能是后置摄像头,用户自拍时一脸懵。
3.4 摄像头与麦克风权限
权限这块,是很多新手翻车的地方。浏览器为了安全,要求 getUserMedia 必须在 HTTPS 或 localhost 下才能调用。你想想看,如果随便一个 HTTP 网站都能偷开你的摄像头,那还得了?
权限弹窗的时机:
- 第一次调用 getUserMedia 时,浏览器会弹出权限请求。
- 用户可以选择“允许”或“阻止”。
- 如果用户之前选过“记住”,那下次就不会再弹了。
我曾经遇到一个坑:用户点了“阻止”之后,后续所有 getUserMedia 调用都会直接 reject,而且不会再次弹窗。用户还以为网站坏了。所以,一定要做好错误处理:
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (err) {
if (err.name === 'NotAllowedError') {
// 用户拒绝了权限
showMessage('请允许摄像头和麦克风权限');
} else if (err.name === 'NotFoundError') {
// 没有找到摄像头或麦克风
showMessage('未检测到摄像头或麦克风');
} else if (err.name === 'NotReadableError') {
// 设备被其他应用占用
showMessage('摄像头或麦克风被其他应用占用');
}
}
3.5 屏幕共享基础
屏幕共享是 WebRTC 里一个很实用的功能。它用的不是 getUserMedia,而是 getDisplayMedia。这个 API 会弹出一个选择窗口,让用户选择共享整个屏幕、某个窗口还是某个浏览器标签页。
基本用法:
async function startScreenShare() {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always' // 显示鼠标指针
},
audio: {
echoCancellation: true
}
});
const video = document.getElementById('screenVideo');
video.srcObject = stream;
} catch (err) {
console.error('屏幕共享失败:', err);
}
}
这里有几个要点:
getDisplayMedia返回的也是 MediaStream,所以你可以直接把它传给 RTCPeerConnection。- 音频方面,
getDisplayMedia支持采集系统音频(比如播放的视频声音),但需要浏览器支持。Chrome 从 74 版本开始支持。 - 用户停止共享时,可以通过监听
oninactive事件来感知。
我在项目中遇到过一个问题:用户共享屏幕后,想切回摄像头。这时候不能直接调用 getUserMedia 覆盖,而是要用 replaceTrack 方法替换轨道。否则会多出一路流,造成资源浪费。
3.6 知识体系总览
为了让你更直观地理解这一章的内容,我画了一张图:
3.7 避坑指南
最后,我把自己踩过的坑总结一下:
- 我曾经在移动端用
exact约束指定了 4K 分辨率,结果大部分手机都不支持,直接报错。后来改成ideal就好了。 - 我曾经在屏幕共享时忘了处理
oninactive事件,用户停止共享后,远端还显示着最后一帧画面。嗯,这个 bug 被测试小姐姐追着改了三轮。 - 我建议在调用
getUserMedia之前,先用navigator.mediaDevices.enumerateDevices()获取设备列表,这样你可以知道用户有哪些摄像头和麦克风,然后动态生成约束。
getUserMedia。而是用 stream.getVideoTracks()[0].applyConstraints(newConstraints) 来动态调整。这样更高效,也不会重新触发权限弹窗。
好了,这一章的内容就到这里。getUserMedia 虽然基础,但它是整个 WebRTC 应用的起点。把这一块吃透了,后面的信令、P2P 连接、数据传输才能跑得顺畅。
公众号:蓝海资料掘金营,微信deep3321