WebRTC中的完美协商(二)

这是一种有助于操作的API!

令人惊讶的是,这在两端都有效!到目前为止,我们只是以一种方式发送媒体,但是另一端可以通过调用pc.addTrack(track,stream)以相同的方式发送媒体。这里的协商也是自动进行的。在这种情况下,提供者/应答者的角色只是颠倒了。

你可以继续对你的对等连接对象进行你所需的任何更改,API将在下一次JavaScript tick时根据需要重新协商。你再也不必担心协商问题了!

如果两端在同一时间进行更改,那就另当别论了。

那么,Glare问题呢?

“Glare”是指双方同时互相发送offer,破坏了两端的状态机。他们的线路交叉缠绕在一起。就像你和朋友同时开始说话,“你听说了……吗?啊,你先说吧!”计算机都会持续这样做且永远不会停止。除非这台计算机没有神经网络。

所有操作都无法进行了,只能回到手动协商! ——等等,让我们看看是否可以修复这个问题。

Rollback解决远程更改问题

Glare是一个应用问题,因为我们可以用多种方法解决它。例如:如果我们使用外数据通道,使所有更改始终仅来自一端,则可以完全避免Glare问题。但我们使用的这个API很难缠,但我们距离解决问题只有一步之遥了。因此,我们使用Rollback来节省时间,旨在达成完美的协商。

礼貌让行

简而言之,我们将使其中一端“礼让”一些,对另一端说“对不起,您先请!”也就是说,其中一端会拒绝收到的offer。我们很幸运,因为这是“rollback”的作用:

await pc.setLocalDescription({type: "rollback"});

无论我们处于什么状态,rollback都会带我们回到“稳定”的signaling State,因此我们可以让另一端先进入。

实施礼貌让行的方法如下:

io.onmessage = async ({data: {description, candidate}}) => {
  if (description) {
    if (description.type == "offer" && pc.signalingState != "stable") {
      if (!polite) return;
      await Promise.all([
        pc.setLocalDescription({type: "rollback"}),
        pc.setRemoteDescription(description)
      ]);
    } else {
      await pc.setRemoteDescription(description);
    }
    if (description.type == "offer") {
      await pc.setLocalDescription(await pc.createAnswer());
      io.send({description: pc.localDescription});
    }
  } else if (candidate) await pc.addIceCandidate(candidate);
}

这比以前的操作多了一些代码。 要解读这个操作,我们可以只介绍新增加的部分,即处理碰撞的部分:

if (description.type == "offer" && pc.signalingState != "stable") {
      if (!polite) return;
      await Promise.all([
        pc.setLocalDescription({type: "rollback"}),
        pc.setRemoteDescription(description);
      ]);
    } else {

这里我们使用了一个全局变量polite,对一个同位体为true,对另一个同位体为false。 下一行是对冲突段解决方法的实施。 它在这里忽略对等点:

if (!polite) return;

剩下的代码是礼貌让行的一端。它roll back到本地offer,然后设置成远程offer。

耐心取胜,因为rollback只会将我们的更改推迟到下一次协商,而下一次协商会自动进行。这样我们就避免了矛盾,这就皆大欢喜了。当我们回到“stable”状态时,negotiationneeded会被立即触发,因此我们可以确信一切都会对我们这一端有利。

为什么要选择Promise.all?

为什么我们选用Promise.all,实际上与ICE计时有关。我们必须避免与信令通道竞争,因为ICE候选可能正好置于刚接收的offer后。如果发生这种情况,我们这一端会再次被召唤,并且它不会等到我们完成基于承诺的async功能。因此,我们必须确保ICE代理准备好处理这些候选。为此,我们会在可能发生的任何addIceCandidate调用之前,以正确的顺序立即在对等连接对象上对两个方法调用进行排序。是的,没错:RTCPeerConnection完成了一个内部队列,这些异步操作中只有一个可以同时运行。这将使所有addIceCandidate调用排在队列的第三位,届时计时操作将进入预期的“have-remote-offer”状态,ICE代理做好准备运行候选。如果我们只是等待每种方法,我们会在方法之间留出一个时间窗口,以便addIceCandidate提早进入。

最后一步

另一端也有类似的问题。我们必须将我们的negotiationneeded代码从更早版本修复为:

pc.onnegotiationneeded = async () => {
  const offer = await pc.createOffer();
  if (pc.signalingState != "stable") return;
  await pc.setLocalDescription(offer);
  io.send({description: pc.localDescription});
}

“但是,”你可能会说,“仅从“stable”状态调用negotiationneeded,这是怎么回事?”实际上,createOffer是异步的,花费很多时间。因此远程offer可能导致两端竞争,有时会使我们脱离“stable”状态,形成了“ have-remote-offer”。这使我们对setLocalDescription的调用失败。 在这种情况下,无需rollback。因此我们只需返回即可。 一旦我们回到“stable”状态,negotiationneeded就会再次发生,因此在这里保证不会造成任何损失。

原文地址:https://blog.mozilla.org/webrtc/perfect-negotiation-in-webrtc/

文章作者:Jan-Ivar Bruaroey

填写常用邮箱,接收社区更新

WebRTC 中文社区由

运营