WebRTC 完美协商

本文将讨论如何通过在两个对等方(peers)之间建立连接时消除竞争条件来实现完美协商,假设我们已经建立了一个信令服务器。

协商是一种非对称操作,一方作为 “调用者”,另一方作为 “被调用者”。通过实施这种模式,我们无需关心连接的哪一端,无论是 “调用方 ”还是 “被调用方”,都能正常工作。

首先,我们给对等方分配一个 “礼貌 ”或 “不礼貌 ”的角色。那么,什么是有礼貌的对等方和不礼貌的对等方呢?

礼貌的对等方:礼貌的对等方可能会发送“SDP Offer”,但是因为他很有礼貌,所以当他收到“SDP Offer”时,他会以“SDP answer”进行回应。

不礼貌对等方:不礼貌对等体发送“SDP Offer”,如果收到“SDP Offer”,则拒绝该提议。只要发生冲突,不礼貌对等方就会获胜。

为什么我们要分配礼貌和不礼貌的角色?通过分配这些角色,当发生碰撞时,同伴们知道应该做什么。

但是我们如何决定哪个对等点应该是礼貌的还是不礼貌的呢?这可以用任何简单的逻辑来完成,假设第一个连接到服务器的对等方将是礼貌的还是不礼貌的,或者我们生成一个随机数,并为最小数字分配礼貌角色,为最大数字分配不礼貌角色。任何简单的逻辑都可以。

我们不介意谁是“呼叫者 ”或“被呼叫者”。假设有礼貌的对等方是呼叫者,无礼的对等方是被呼叫者。如果发生碰撞,有礼貌的对等方会放弃自己的提议,并通过回复无礼同行者的提议来切换到被叫者,而无礼同行者也会切换到呼叫者。

代码实现:

在此,我们假设设置了一个信令服务器。

// 根据某些逻辑将此值重新初始化为 true 或 false。
let courtesy = true ; 

// STUN 服务器。免费的请搜索 Google STUN 服务器。
const config = { 
 iceServers : [{ urls : "stun:stun.mystunserver.tld" }], 
}; 
const signaler = new  SignalingChannel (); 
const pc = new  RTCPeerConnection (config);

我们使用配置参数初始化 RTCPeerConnection 构造函数。RTCPeerConnection 对象代表对等连接。

连接到远程对等方:

const constraints = { audio: true, video: true };
const selfVideo = document.querySelector("video.selfview");
const remoteVideo = document.querySelector("video.remoteview");

async function start() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);

    for (const track of stream.getTracks()) {
      pc.addTrack(track, stream);
    }
    selfVideo.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

在上述代码中,我们通过调用 getUserMedia(constraints) 方法访问用户媒体音频和视频,并将这些音频和视频轨道添加到 RTCPeerConnection。然后,我们将访问的流设置为 srcObject。如果用户拒绝访问媒体,流可能为空。

处理传入的轨道:

pc.ontrack = ({ track, streams }) => {
  track.onunmute = () => {
    if (remoteVideo.srcObject) {
      return;
    }
    remoteVideo.srcObject = streams[0];
  };
};

在这里,当远程描述被设置时,就会触发 ontrack 事件。我们对轨道和流进行解构。当轨道开始接收数据包时,就会触发 unmute 事件。

处理 negotiationneeded 事件:

let makingOffer = false;

pc.onnegotiationneeded = async () => {
  try {
    makingOffer = true;
    await pc.setLocalDescription();
    signaler.send({ description: pc.localDescription });
  } catch (err) {
    console.error(err);
  } finally {
    makingOffer = false;
  }
};

当需要通过信令服务器就连接进行协商时,onnegotiationneeded 事件就会被触发。在不带任何参数的情况下调用 setLocalDescription() 会根据连接状态创建合适的要约或应答。由于 onnegotiationneeded 事件会在稳定状态下触发,因此上述代码始终会生成一个要约。

处理传入的 ICE 候选者:

pc.onicecandidate = ({ candidate }) => signaler.send({ candidate });

当新的 ICE 候选被 ICE 框架发现时,它们会通过信令服务器发送到远程对等方。

处理信令通道上的传入消息:

let ignoreOffer = false;

signaler.onmessage = async ({ data: { description, candidate } }) => {
  try {
    if (description) {
      const offerCollision =
        description.type === "offer" &&
        (makingOffer || pc.signalingState !== "stable");

      ignoreOffer = !polite && offerCollision;
      if (ignoreOffer) {
        return;
      }

      await pc.setRemoteDescription(description);
      if (description.type === "offer") {
        await pc.setLocalDescription();
        signaler.send({ description: pc.localDescription });
      }
    } else if (candidate) {
      try {
        await pc.addIceCandidate(candidate);
      } catch (err) {
        if (!ignoreOffer) {
          throw err;
        }
      }
    }
  } catch (err) {
    console.error(err);
  }
};

当我们从远程对等方收到数据时,signaler.onmessage(自行实现 signaler)事件就会被触发。在此,我们会重组从远程对等程序收到的描述和候选信息。然后检查描述中是否有可用数据,如果描述中有可用数据,则检查是否有 offer 碰撞。(makingOffer || pc.signalingState !== “sable”);如果其中任何一个条件为真,就意味着我们正在准备 offer,而且如果我们从远端对等设备收到的描述类型是 “offer”,那么我们就有一个offer碰撞。从这里开始,我们必须决定是忽略还是接受offer,我们要根据最初分配给我们的角色 “礼貌 ”或 “不礼貌 ”来决定。

ignoreOffer = !polite && offerCollision; 此行表示,如果发生offer冲突,而我又不是礼貌的对等方,则通过将 ignoreOffer 设置为 true 或 false 来忽略该offer。如果我们忽略该offer,则只需返回,否则继续该过程。

如果我们接受该提议,那么我们将远程描述设置为从远程对等方收到的描述,并通过信令服务器将创建的“answer”发送给另一个对等方。

如果收到的描述为空但有一个 ICE 候选,那么我们将该 ICE 候选添加到本地 ICE 层。

WebRTC 完美协商
WebRTC 完美协商

作者:Rakeshnoothi
译自medium.

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/52977.html

(0)

相关推荐

发表回复

登录后才能评论