前言

又好久没写博客了,以前觉得写博客是一个非常耗费精力和时间的事情,因为担心自己的专业知识储备不够,写出来的文章漏洞百出;另一个是因为自己文笔不够好,写的不够通俗易懂,让人没有看下去的欲望。所以每次打算写博客的时候想到这些就又放弃了。

但现在我转变想法了,由于AI的出现,估计大部分博客论坛的日活都比以前下降了不少,因为AI就是一个私人老师和最大的知识库,以往遇到一些问题不知道如何解决,都是先通过搜索引擎找到相对应的文章,但是现在直接问AI就能得到想要的答案,没有必要去博客或论坛里找答案了。

所以以后写博客就当是为了记录自己在工作或生活中遇到问题时如何解决,总结经验,加深自己的印象,写得是否通俗易懂都不重要。

概念

WebRTC(Web Real-Time Communication)是一种支持浏览器和移动应用进行实时通信的技术。它允许音频、视频以及数据的点对点传输,无需依赖中间服务器。

WebRTC实现音视频通话主要依赖以下两个核心 API:

  1. RTCPeerConnection:用于建立点对点连接,直白点就是在两台通信设备之间建立连接
  2. MediaStream API:用于捕获和处理音视频流,然后发送给另一端的通信方

实现步骤

实现WebRTC通信需要以下几个步骤:

  1. 获取本地媒体流。
  2. 创建RTCPeerConnection并添加媒体流。
  3. 通过信令服务器交换SDP和ICE候选信息。
  4. 建立点对点连接并传输音视频或数据。

获取本地媒体流

在创建点对点连接之前,一定要先获取本地媒体流,否则可能无法推流成功。

navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    // 将本地视频流显示在页面上
    const localVideo = document.getElementById('localVideo');
    localVideo.srcObject = stream;

    localStream = stream
  })
  .catch(error => {
    console.error('获取本地媒体流失败:', error);
  });

创建RTCPeerConnection

获取本地媒体流且对方接受通话请求后就可以开始创建点对点连接。

点对点连接需要提供iceServers的STUN和TURN服务器列表,这个iceServer是为了将本机设备的局域网IP转换成外部可以访问到的公网IP,这样两台设备才可以进行通信,这里可以使用公共的服务器,也可以自己搭建一个。

const peerConnection = new RTCPeerConnection({
    iceServers: [
      { urls: 'stun:stun.l.google.com:19302' } // 使用 Google 的 STUN 服务器
    ]
});
// 将本地媒体流添加到 RTCPeerConnection
localStream.getTracks().forEach((track) => {
  peerConnection.addTrack(track, localStream.value!);
});

// 处理 ICE 候选信息
peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    // 将候选信息发送到信令服务器,再由信令服务器转发给另一端
    sendToSignalingServer({ candidate: event.candidate });
  }
};

// 监听另一端推送媒体流
peerConnection.ontrack = (event) => {
  // 将另一端的媒体流显示在页面上
  const remoteVideo = document.getElementById('remoteVideo');
  remoteVideo.srcObject = event.streams[0] || event.stream;
  remoteVideo.onloadedmetadata = () => {
    remoteVideo.play();
  };
};

信令服务器交换信息

信令服务器用于在两个客户端之间交换 SDP 和 ICE 候选信息。

SDP可以简单理解为是当前设备的配置信息以及支持的视频格式等信息,两台设备需要通过协商确认后才能进行webRTC通信。

ICE候选信息即上面所说的公网IP和端口信息。

信令服务器需要自己实现,一般是搭建一个webSocket服务器即可

// 通信发起方需要主动创建offer并发送给另一端
if(isCaller) {
  peerConnection.createOffer()
  .then(offer => {
    // 设置为本地描述
    peerConnection.setLocalDescription(offer);
    // 还需要将offer发送到信令服务器
    sendToSignalingServer(offer);
  });
}

// 监听信令服务器的消息
onReceiveFromSignalingServer(data => {
  if (data.sdp) {
    // 收到远端发送的offer或answer,设置为远端描述
    peerConnection.setRemoteDescription(new RTCSessionDescription(data));
    // 如果不是通信发起方,收到是offer,则需要创建answer传回给发起方
    if(!isCaller) {
      peerConnection.createAnswer().then(async (answer) => {
        // 
        peerConnection.setLocalDescription(answer);
        sendToSignalingServer(answer);
      });
    }
  } else if (data.candidate) {
    peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
  }
});

这里的offer和answer就是SDP信息,candidate就是ICE候选信息。

建立点对点连接并传输音视频或数据

以上步骤都顺利完成后,就能成功建立点对点连接并自动推流至远端,实现音视频通话。

总结

之前在工作中主要遇到了两个问题,一是无法建立连接,二是建立连接后无法成功推流。然后排查发现有两个原因,一个是创建点对点连接之前,没有先获取本地媒体流;二是交换SDP和ICE时信息顺序错乱,发起方还没有等到另一端的peerConnection创建好,就已经将candidate或sdp信息发送过来,导致无法建立起有效的点对点连接。