9

我希望逻辑没有缺陷。

第 1 步:来电者创建报价

第 2 步:调用者设置 localDescription

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

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

第四步:被调用者接收offer sets remote description

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

第六步:被叫设置本地描述

第 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:无法在“RTCPeerConnection”上执行“setRemoteDescription”:无法设置远程报价 sdp:在错误状态下调用:kHaveLocalOffer

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

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

在此处输入图像描述 我不知道它是否应该像我初学者那样表现得那样。

提前致谢。

4

1 回答 1

10

你的代码是正确的。这是Chrome中的一个长期存在的错误negotiationneeded

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

在 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 在这里违反了规范的两个部分:

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

  2. 如果连接的信号状态不是"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;
  }
}
于 2018-10-28T19:27:22.643 回答