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 知识体系图

下面这张图,把本章的核心逻辑串起来了。你可以保存下来,以后忘了就看看。

获取本地音视频流核心流程 navigator.mediaDevices.getUserMedia 约束参数 (constraints) 视频:分辨率、帧率、码率 音频:采样率、回声消除、降噪 兼容性处理 MediaStream → video.srcObject

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