3、核心API解析:MediaStream、RTCPeerConnection、RTCDataChannel

好,咱们直接进入正题。WebRTC 移动端开发,说白了就是跟三个核心 API 打交道:MediaStream(媒体流)、RTCPeerConnection(对等连接)、RTCDataChannel(数据通道)。

这三个东西,我习惯把它们比作「水管、管道和快递」。你想想看,MediaStream 是水龙头出来的水(音视频),RTCPeerConnection 是连接两家的水管,RTCDataChannel 则是水管里单独跑的一条快递线(传文本、文件)。

下面我一个一个拆开讲,顺便聊聊我在项目里踩过的坑。

3.1 MediaStream:音视频的源头

MediaStream 代表一路媒体流。在移动端,最常见的就是摄像头和麦克风采集。

核心概念: MediaStream 包含多个 MediaStreamTrack,每个 Track 对应一种媒体类型(视频或音频)。

获取本地媒体流的代码,大家应该很熟悉了:

// 获取摄像头和麦克风
const constraints = {
  video: {
    facingMode: 'user', // 前置摄像头
    width: { ideal: 1280 },
    height: { ideal: 720 }
  },
  audio: true
};

navigator.mediaDevices.getUserMedia(constraints)
  .then(stream => {
    // stream 就是 MediaStream 对象
    // 可以赋值给 video 标签的 srcObject
    localVideo.srcObject = stream;
  })
  .catch(err => {
    console.error('获取媒体流失败:', err);
  });

嗯,这里要注意一点。我在项目中遇到过一个问题:Android 部分机型在切换摄像头时,如果不先停止当前 Track,会导致黑屏或崩溃。

避坑指南: 我曾经在切换前后摄像头时,直接重新调用 getUserMedia,结果旧流没释放,导致内存泄漏。正确做法是先停止所有 Track,再重新获取。
// 停止所有 Track
stream.getTracks().forEach(track => track.stop());

// 然后重新获取
navigator.mediaDevices.getUserMedia(newConstraints);

另外,移动端还有个常见问题:音频回声。我建议在采集时加上回声消除约束:

const constraints = {
  audio: {
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true
  }
};

这三个参数,说白了就是给音频做「降噪、去回声、自动增益」。在嘈杂环境中效果很明显。

3.2 RTCPeerConnection:核心管道

RTCPeerConnection 是 WebRTC 最复杂的 API。它负责建立和维护两个端之间的连接,处理 ICE 协商、NAT 穿透、编解码器协商等。

我个人习惯把连接过程拆成三步:

  1. 创建连接对象 —— 配置 STUN/TURN 服务器
  2. 交换 SDP —— 描述媒体能力
  3. ICE 候选者交换 —— 找到最优路径

先看创建连接:

const config = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:your-turn-server.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ]
};

const pc = new RTCPeerConnection(config);

这里有个坑:移动端网络环境复杂,纯 STUN 经常失败,必须配 TURN 服务器做中继。 我在做一款视频通话 App 时,发现 4G 网络下 STUN 穿透成功率只有 60% 左右,加上 TURN 后提升到了 95% 以上。

经验之谈: 移动端建议至少配两个 STUN 服务器和一个 TURN 服务器。TURN 服务器推荐用 coturn 自建,或者用云服务商提供的。

接下来是 SDP 交换。发起方创建 Offer,接收方创建 Answer:

// 发起方
pc.createOffer()
  .then(offer => pc.setLocalDescription(offer))
  .then(() => {
    // 通过信令服务器发送 offer 给远端
    signalingServer.send(JSON.stringify({
      type: 'offer',
      sdp: pc.localDescription
    }));
  });

// 接收方收到 offer 后
pc.setRemoteDescription(new RTCSessionDescription(offer))
  .then(() => pc.createAnswer())
  .then(answer => pc.setLocalDescription(answer))
  .then(() => {
    signalingServer.send(JSON.stringify({
      type: 'answer',
      sdp: pc.localDescription
    }));
  });

ICE 候选者交换同样重要。当发现新的候选者时,会触发 onicecandidate 事件:

pc.onicecandidate = (event) => {
  if (event.candidate) {
    // 发送给远端
    signalingServer.send(JSON.stringify({
      type: 'candidate',
      candidate: event.candidate
    }));
  }
};

为什么会这样?因为两端需要知道对方的 IP 和端口才能建立连接。ICE 就是干这个活的。

3.3 RTCDataChannel:轻量级数据传输

RTCDataChannel 允许你在两个端之间直接传输任意数据,不经过服务器。它基于 SCTP 协议,支持可靠和不可靠两种模式。

创建数据通道很简单:

// 在 RTCPeerConnection 上创建
const dataChannel = pc.createDataChannel('chat', {
  ordered: true,       // 保证顺序
  maxRetransmits: 3    // 最大重传次数
});

// 监听消息
dataChannel.onmessage = (event) => {
  console.log('收到消息:', event.data);
};

// 发送消息
dataChannel.send('Hello from mobile!');

我在项目中用 RTCDataChannel 做过实时白板同步。说白了,就是传鼠标坐标和绘图指令。延迟比 WebSocket 低很多,因为不需要经过服务器中转。

适用场景:
  • 实时聊天(文本、表情)
  • 文件传输(小文件,大文件建议分片)
  • 游戏状态同步
  • 屏幕共享时的控制指令

注意:移动端数据通道在弱网下容易丢包。 我建议对重要数据使用可靠模式(ordered: true),对实时性要求高的数据(如游戏位置)使用不可靠模式(maxRetransmits: 0)。

3.4 三者关系图

下面这张图展示了三个 API 在 WebRTC 中的协作关系:

WebRTC 三大核心 API 协作关系 MediaStream 摄像头 / 麦克风采集 getUserMedia() RTCPeerConnection P2P 连接管理 ICE / SDP / NAT 穿透 RTCDataChannel 文本 / 文件 / 指令 createDataChannel() addTrack() createDataChannel() 远端 Peer 接收媒体流 / 数据 onaddstream / onmessage 信令 + ICE 候选者 信令服务器 WebSocket / HTTP 交换 SDP 媒体流 连接管理 数据通道 远端通信

从图中可以看出:MediaStream 提供音视频数据,通过 addTrack() 喂给 RTCPeerConnection;RTCDataChannel 则独立于媒体流,直接在连接上跑数据。两者最终都通过 RTCPeerConnection 发送到远端。

3.5 移动端实战要点

API 常见问题 解决方案
MediaStream 摄像头权限拒绝 提前申请权限,捕获错误后提示用户
MediaStream 音频回声 开启 echoCancellation 和 noiseSuppression
RTCPeerConnection NAT 穿透失败 配置 TURN 服务器,增加 ICE 超时时间
RTCPeerConnection 连接断开 监听 oniceconnectionstatechange,自动重连
RTCDataChannel 弱网丢包 根据场景选择可靠/不可靠模式
RTCDataChannel 大文件传输 分片发送,每片 16KB 以内
我的建议: 移动端开发时,一定要在真机上测试。模拟器里网络环境太理想,很多问题暴露不出来。我吃过这个亏,在模拟器上跑得好好的,一上真机就各种 ICE 失败。

好了,三个核心 API 就讲到这里。记住一句话:MediaStream 是原料,RTCPeerConnection 是管道,RTCDataChannel 是快递。 理解了这个关系,后面写代码就顺了。


公众号:蓝海资料掘金营,微信deep3321