第一章 媒体流获取实战:用 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 的核心流程:

getUserMedia 核心流程 调用 getUserMedia 浏览器弹出权限请求 ✅ 用户允许 → 返回 MediaStream ❌ 用户拒绝 → 抛出错误 将 stream 绑定到 video 元素 捕获错误,提示用户处理

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