3、核心API解析:MediaStream、RTCPeerConnection、RTCDataChannel
好,咱们直接进入正题。WebRTC 移动端开发,说白了就是跟三个核心 API 打交道:MediaStream(媒体流)、RTCPeerConnection(对等连接)、RTCDataChannel(数据通道)。
这三个东西,我习惯把它们比作「水管、管道和快递」。你想想看,MediaStream 是水龙头出来的水(音视频),RTCPeerConnection 是连接两家的水管,RTCDataChannel 则是水管里单独跑的一条快递线(传文本、文件)。
下面我一个一个拆开讲,顺便聊聊我在项目里踩过的坑。
3.1 MediaStream:音视频的源头
MediaStream 代表一路媒体流。在移动端,最常见的就是摄像头和麦克风采集。
获取本地媒体流的代码,大家应该很熟悉了:
// 获取摄像头和麦克风
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,会导致黑屏或崩溃。
// 停止所有 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 穿透、编解码器协商等。
我个人习惯把连接过程拆成三步:
- 创建连接对象 —— 配置 STUN/TURN 服务器
- 交换 SDP —— 描述媒体能力
- 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% 以上。
接下来是 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 中的协作关系:
从图中可以看出:MediaStream 提供音视频数据,通过 addTrack() 喂给 RTCPeerConnection;RTCDataChannel 则独立于媒体流,直接在连接上跑数据。两者最终都通过 RTCPeerConnection 发送到远端。
3.5 移动端实战要点
| API | 常见问题 | 解决方案 |
|---|---|---|
| MediaStream | 摄像头权限拒绝 | 提前申请权限,捕获错误后提示用户 |
| MediaStream | 音频回声 | 开启 echoCancellation 和 noiseSuppression |
| RTCPeerConnection | NAT 穿透失败 | 配置 TURN 服务器,增加 ICE 超时时间 |
| RTCPeerConnection | 连接断开 | 监听 oniceconnectionstatechange,自动重连 |
| RTCDataChannel | 弱网丢包 | 根据场景选择可靠/不可靠模式 |
| RTCDataChannel | 大文件传输 | 分片发送,每片 16KB 以内 |
好了,三个核心 API 就讲到这里。记住一句话:MediaStream 是原料,RTCPeerConnection 是管道,RTCDataChannel 是快递。 理解了这个关系,后面写代码就顺了。
公众号:蓝海资料掘金营,微信deep3321