如何在 Chrome 中为 WebRTC 调用者设置远程描述而不会出错?

Posted

技术标签:

【中文标题】如何在 Chrome 中为 WebRTC 调用者设置远程描述而不会出错?【英文标题】:How to set remote description for a WebRTC caller in Chrome without errors? 【发布时间】:2019-04-01 10:51:59 【问题描述】:

我希望逻辑没有缺陷。

第 1 步: 来电者创建报价

第 2 步: 调用者设置 localDescription

第 3 步: 调用者将描述发送给被调用者

//--------------------------------------------- ---------//

第四步:被调用者收到offer sets远程描述

第 5 步: 被调用者创建答案

第 6 步: 被调用者设置本地描述

第 7 步:被调用者将描述发送给调用者

//--------------------------------------------- ---------//

第 8 步: 调用者收到答案并设置远程描述

这是上面的代码

const socket = io();
const constraints = 
  audio: true,
  video: true
;
const configuration = 
  iceServers: [
    "url": "stun:23.21.150.121"
  , 
    "url": "stun:stun.l.google.com:19302"
  ]
;

const selfView = $('#selfView')[0];
const remoteView = $('#remoteView')[0];

var pc = new RTCPeerConnection(configuration);

pc.onicecandidate = (
  candidate
) => 
  socket.emit('message', 
    to: $('#remote').val(),
    candidate: candidate
  );
;

pc.onnegotiationneeded = async () => 
  try 
    await pc.setLocalDescription(await pc.createOffer());
    socket.emit('message', 
      to: $('#remote').val(),
      desc: pc.localDescription
    );
   catch (err) 
    console.error(err);
  
;

pc.ontrack = (event) => 
  // don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
;

socket.on('message', async (
  from,
  desc,
  candidate
) => 
  $('#remote').val(from);
  try 
    if (desc) 
      // if we get an offer, we need to reply with an answer
      if (desc.type === 'offer') 
        await pc.setRemoteDescription(desc);
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) => pc.addTrack(track, stream));
        selfView.srcObject = stream;
        await pc.setLocalDescription(await pc.createAnswer());
        console.log(pc.localDescription);
        socket.emit(
          to: from,
          desc: pc.localDescription
        );
       else if (desc.type === 'answer') 
        await pc.setRemoteDescription(desc).catch(err => console.log(err));
       else 
        console.log('Unsupported SDP type.');
      
     else if (candidate) 
      await pc.addIceCandidate(new RTCIceCandidate(candidate)).catch(err => console.log(err));
    
   catch (err) 
    console.error(err);
  
);


async function start() 
  try 
    // get local stream, show it in self-view and add it to be sent
    const stream = await requestUserMedia(constraints);
    stream.getTracks().forEach((track) => pc.addTrack(track, stream));
    attachMediaStream(selfView, stream);
   catch (err) 
    console.error(err);
  


socket.on('id', (data) => 
  $('#myid').text(data.id);
);


// this function is called once the caller hits connect after inserting the unique id of the callee
async function connect() 
  try 
    await pc.setLocalDescription(await pc.createOffer());
    socket.emit('message', 
      to: $('#remote').val(),
      desc: pc.localDescription
    );
   catch (err) 
    console.error(err);
  


socket.on('error', data => 
  console.log(data);
);

现在这段代码在执行第8步

时抛出了一个错误

DOMException: 无法执行“setRemoteDescription” 'RTCPeerConnection':无法设置远程报价 sdp:调用错误 状态:kHaveLocalOffer

DOMException: 无法执行“addIceCandidate” 'RTCPeerConnection': 处理 ICE 候选时出错

尝试调试但未发现逻辑或代码有任何缺陷。注意到pc 对象具有localDescriptioncurrentLocalDescription 的一件奇怪的事情,我认为创建答案的被调用者必须同时具有描述类型为answer 而是显示localDescription 为@987654329 @ 和 currentLocalDescription 类型是 answer

我不知道它是否应该像我初学者那样表现得那样。

提前致谢。

【问题讨论】:

【参考方案1】:

您的代码是正确的。这是一个长期存在的bug in Chrome 和negotiationneeded

我已经在a fiddle 中进行了检测(右键单击并在两个相邻的窗口中打开,然后单击调用其中一个)。

在 Firefox 中,它可以工作。报价方协商一次,因为您一次添加了两个音轨(视频/音频):

negotiating in stable
onmessage answer

并且,在回答者方面,您在'stable' 状态之外添加的曲目将添加到答案中:

onmessage offer
adding audio track
adding video track

但在 Chrome 中,它已经坏了,在提供者上触发 negotiationneeded 两次,每个添加的曲目一次:

negotiating in stable
negotiating in stable
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
  Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
  Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
  Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer

并在应答端触发negotiationneeded 两次,这甚至不在'stable' 状态:

onmessage offer
adding audio track
adding video track
negotiating in have-remote-offer
negotiating in have-remote-offer
onmessage offer
DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection':
  Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer

这些额外的事件会导致在此处看到的相互状态错误的严重破坏。

具体来说,Chrome 在这里违反了spec 的两个部分:

    “排队任务” 触发此事件。 “在一次对连接进行多项修改的常见情况下,排队可防止协商需要过早触发。”

    如果连接的信号状态不是"stable",请中止这些步骤[触发事件]。

解决方法

解决两个 Chrome 错误需要(使用async/await 为简洁起见):

let negotiating = false;
pc.onnegotiationneeded = async e => 
  try 
    if (negotiating || pc.signalingState != "stable") return;
    negotiating = true;
    /* Your async/await-using code goes here */
   finally 
    negotiating = false;
  

【讨论】:

如果你赞成这个答案,别忘了还★chrome bug。 在 Chrome 75 中修复。 我在 Chrome 81 上,即使在应用解决方案后也面临同样的问题。

以上是关于如何在 Chrome 中为 WebRTC 调用者设置远程描述而不会出错?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 的扩展方法中为调用者变量赋值

如何在被调用的方法中获取调用者的方法名?

SPA + API + OIDC:仅提供 ACCESS 令牌时如何验证 API 调用者?

如何在 Solidity 中为函数设置默认参数

WebRTC[51] - 如何获取Chrome浏览器底层的WebRTC日志

Android WebView 无法在 WebRTC 对等连接上显示两个视频标签