WebRTC 端对端连接的基本流程解析。

A:客户端 A,发起端
B:客户端 B,被呼叫端
Signal:信令服务器
sturn/turn:STUN/TURN 服务器

传输步骤:媒体协商、链路连接、媒体数据传输。

  1. 首先 A、B 需要和信令服务器建立连接。

  2. 发送端 A 创建一个 RTCPeerConnection。

    1
    pc1 = new RTCPeerConnection();
  3. 发送端 A 拿到本地的流 Add Stream 到连接中去。

    1
    2
    3
    localStream.getTracks().forEach((track) => {
    pc1.addTrack(track, localStream);
    });
  4. 发送端 A 创建一个 Offer,并设置到 setLocalDescription 中,setLocalDescription 调用后会向 sturn/turn 服务器发送一个 bind request,这个时候就开始收集所有能和对方连接的候选者了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var offerOptions = {
    offerToReceiveVideo: 1,
    offerToReceiveAudio: 0,
    };
    pc1.createOffer(offerOptions)
    .then(getOffer)
    .catch(handelCreateOfferError);

    function getOffer(desc) {
    pc1.setLocalDescription(desc);
    // 发送 SDP Offer 给信令服务器:send desc.sdp;
    // 信令服务器会把这个 SDP Offer 发给能连接的接收端 B
    }
  5. 接收端 B 收到信令服务器发来的 A 的 SDP Offer 后,创建一个 PeerConnection,并 setRemoteDescription 将收到的 sdp 设置进去。

    1
    2
    pc2 = new RTCPeerConnection();
    pc2.setRemoteDescription(desc);
  6. 然后接收端 B 需要给一个应答,创建一个 Answer 并设置到 B 的 LocalDescription,这个时候会向 sturn/turn 服务发送一个 bind request,然后接收端 B 再将自己的 SDP Answer 发送给 Signal 服务器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    pc2.setLocalDescription(desc);
    pc2.createAnswer()
    .then(getAnswer)
    .catch(handleAnsError);

    function getAnswer(desc) {
    pc2.setLocalDescription(desc);
    // 发送 pc2 的 sdp answer 给 signal 服务器
    }
  7. Signal 服务器收到接收端 B 的 SDP Answer 后会将这个 answer 发给端 A,端 A 收到 B 的 answer 后续设置 RemoteDescription。至此,A 和 B 的媒体协商算是完成了。

    1
    2
    // recieve SDP Answer from Signal Server
    pc1.setRemoteDescription(desc);
  8. 发送端 A 会收到一个 sturn/turn 服务器发来的 Candidate,这个 Candidate 就是服务器收集的能和对方建立连接的候选者了。然后 A 将这个 Candidate 发送给 Signal 服务器,通过信令服务器转给 B 端,B 收到之后续将这个 Candidate 添加进去。

    同样的道理,当 B 端收到 sturn/turn 服务器发来的 Candidate,会将这个 Candidate 通过 Signal 转发给 A,A 将收到的 B 的候选者列表 Add 进去。

    1
    2
    3
    4
    5
    6
    7
    pc1.onicecandidate = (e) => {
    pc2.addIceCandidate(e.candidate);
    };

    pc2.onicecandidate = (e) => {
    pc1.addIceCandidate(e.candidate);
    };

    此时就 A、B 双方都已经收到了 Candidate,底层开始 pair,做排序和连接检测。

    当找到最优的一个线路后,A、B 开始通信。

    A 将数据流发送给 B,B 收到后将数据 AddStream,才能把音视频数据向上抛出进行渲染。

    1
    2
    3
    4
    5
    pc2.ontrack = getRemoteStream;

    function getRemoteStream(e) {
    remoteVideo.srcObject = e.streams[0];
    }

EOF