简体   繁体   English

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

[英]How to set remote description for a WebRTC caller in Chrome without errors?

I hope there is no flaw in the logic. 我希望逻辑上没有缺陷。

Step 1: caller creates offer 步骤1:呼叫者创建报价

Step 2: caller sets localDescription 步骤2:呼叫者设置localDescription

Step 3: caller sends the description to the callee 步骤3:呼叫者将描述发送给被呼叫者

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

Step 4: callee receives the offer sets remote description 步骤4:被叫方收到要约集的远程描述

Step 5: callee creates answer 步骤5:被叫方创建答案

Step 6: callee sets local description 步骤6:被叫方设置本地描述

Step 7: callee send the description to caller 步骤7:被呼叫者将说明发送给呼叫者

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

Step 8: caller receives the answer and sets remote description 步骤8:呼叫者收到答案并设置远程描述

And here is the code for the above 这是上面的代码

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);
});

Now this code throws an error while executing Step 8 现在,此代码在执行步骤8时引发错误

DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer DOMException:无法在'RTCPeerConnection'上执行'setRemoteDescription':无法设置远程商品sdp:处于错误状态:kHaveLocalOffer

DOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Error processing ICE candidate DOMException:无法在“ RTCPeerConnection”上执行“ addIceCandidate”:处理ICE候选者时出错

Tried to debug but didn't find any flaw in the logic or code. 尝试调试,但未发现逻辑或代码中的任何缺陷。 Noticed one weird thing that the the pc object has localDescription and currentLocalDescription and i think the callee who creates the answer must have both the description type to be answer but instead shows the localDescription to be offer and currentLocalDescription type is answer . 注意到一个奇怪的事情是,在pc对象有localDescriptioncurrentLocalDescription ,我认为谁创造了答案被叫方必须同时说明类型为answer ,而是给出了localDescriptionoffercurrentLocalDescription类型answer

在此处输入图片说明 I have no idea if it is supposed to behave like that or not as I am begginer. 我不知道它是否应该像我开始时那样表现。

Thanks in advance. 提前致谢。

Your code is correct. 您的代码是正确的。 This is a long-standing bug in Chrome with negotiationneeded . 这是Chrome中长期存在的错误,需要经过negotiationneeded

I've instrumented it in a fiddle (right-click and open in TWO adjacent windows, then click call in one). 我已经在一个小提琴中对其进行了检测 (右键单击并在两个相邻的窗口中打开,然后单击“其中一个”)。

In Firefox, it works. 在Firefox中,它可以工作。 The offerer negotiates once because you add two tracks (video/audio) at once: 要约人协商一次,因为您一次添加了两个轨道(视频/音频):

negotiating in stable
onmessage answer

and, on the answerer side, the tracks you add outside of 'stable' state are added to the answer: 并且在答题器端,您在'stable'状态之外添加的曲目将添加到答案中:

onmessage offer
adding audio track
adding video track

But in Chrome, it's broken, firing negotiationneeded twice on the offerer, once per track added: 但是在Chrome浏览器中,它很坏,需要在要约人上negotiationneeded两次negotiationneeded ,每添加一条轨道, 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

and firing negotiationneeded twice on the answerer side, which isn't even in 'stable' state: 并且在答答器端需要negotiationneeded两次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

These extra events cause the havoc of reciprocal state errors seen on both ends here. 这些额外的事件导致在此处两端都看到的相互状态错误的破坏。

To be specific, Chrome violates two parts of the spec here: 具体来说,Chrome违反了规范的两个部分:

  1. "Queue a task" to fire this event. “排队任务”以触​​发此事件。 "queueing prevents negotiationneeded from firing prematurely, in the common situation where multiple modifications to connection are being made at once." “在通常要同时对连接进行多次修改的常见情况下,排队可以防止协商过早触发。”

  2. If connection's signaling state is not "stable" , abort these steps [to fire the event]. 如果连接的信令状态不是"stable" ,请中止这些步骤[以触发事件]。

Workaround 解决方法

Working around both Chrome bugs requires (using async / await for brevity): 解决这两个 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;
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM