WebRTC 无法在控制台上的 RTCPeerConnection 错误上执行“addIceCandidate”,但仍可以显示远程和本地视频

Posted

技术标签:

【中文标题】WebRTC 无法在控制台上的 RTCPeerConnection 错误上执行“addIceCandidate”,但仍可以显示远程和本地视频【英文标题】:WebRTC getting Failed to execute 'addIceCandidate' on RTCPeerConnection error on console but can still display remote and local videos 【发布时间】:2020-03-13 10:33:41 【问题描述】:

我正在尝试使用 webRTC 连接两个对等点。我能够正确显示本地和远程视频,但是一旦远程视频出现,候选对象就会变为null,并在控制台上记录以下错误消息。

TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Candidate missing values for both sdpMid and sdpMLineIndex

我正在使用两台单独的笔记本电脑来测试连接,并且由于远程和本地视频都显示,我认为我已经成功连接了两个对等方,但由于错误消息我不确定。

知道为什么会发生这种情况吗?我是否成功连接了两个对等点?

下面是代码。

谢谢!

前端

import React,  Component  from 'react';
import io from 'socket.io-client';
class App extends Component 
  constructor(props) 
    super(props);
    this.room = 'test-room';
    this.socket = io.connect('http://localhost:5000');
    this.localPeerConnection = new RTCPeerConnection(
      iceServers: [
        
          urls: 'stun:stun.l.google.com:19302'
        
      ]
    );
    this.remotePeerConnection = new RTCPeerConnection(
      iceServers: [
        
          urls: 'stun:stun.l.google.com:19302'
        
      ]
    );
  ;

  componentDidMount() 
    this.socket.on('connect', () => 
      this.socket.emit('join', this.room, err => 
        if (err) 
          console.error(err);
         else 
          this.socket.on('offer', offer => 
            console.log('OFFER RECEIVED: ', offer);
            this.createAnswer(offer);
          );

          this.socket.on('candidate', candidate => 
            console.log('CANDIDATE RECEIVED', candidate);
            this.localPeerConnection.addIceCandidate(candidate).catch(error => console.error(error));
            this.remotePeerConnection.addIceCandidate(candidate).catch(error => console.error(error));
          );

          this.socket.on('answer', answer => 
            console.log('ANSWER RECEIVED:', answer);
            this.localPeerConnection.setRemoteDescription(answer);
          );
        
      );
    );
  

  startCall = async () => 
    this.localPeerConnection.onicecandidate = e => 
      const iceCandidate = e.candidate;
      this.socket.emit('candidate',  room: this.room, candidate: iceCandidate );
      console.log('candidate generated', e.candidate);
    ;

    this.localPeerConnection.ontrack = e => 
      this.remoteVideo.srcObject = e.streams[0];
      console.log('REMOTE STREAM?: ', e.streams[0]);
    ;

    try 
      const stream = await navigator.mediaDevices.getUserMedia( video:  width: 150, height: 150 , audio: false );
      for (const track of stream.getTracks()) 
        this.localPeerConnection.addTrack(track, stream);
      

      this.localVideo.srcObject = stream;
      console.log('LOCAL STREAMS: ', this.localPeerConnection.getLocalStreams())

      return this.createOffer();
     catch (error) 
      console.error(error);
    
  

  createOffer = async () => 
    try 
      const offer = await this.localPeerConnection.createOffer();
      await this.localPeerConnection.setLocalDescription(offer);
      await this.remotePeerConnection.setRemoteDescription(offer);

      this.socket.emit('offer',  room: this.room, offer );
      console.log('SENDING OFFER: ', offer);
     catch (error) 
      console.error(error);
    
  

  createAnswer = async description => 
    this.remotePeerConnection.onicecandidate = e => 
      const iceCandidate = e.candidate;
      this.socket.emit('candidate',  room: this.room, candidate: iceCandidate );
      console.log('candidate generated', e.candidate);
    ;

    this.remotePeerConnection.ontrack = e => 
      this.remoteVideo.srcObject = e.streams[0];
    ;

    this.remotePeerConnection.setRemoteDescription(description)
      .then(() => navigator.mediaDevices.getUserMedia( video:  width: 150, height: 150 , audio: false ))
      .then(stream => 
        for (const track of stream.getTracks()) 
          this.remotePeerConnection.addTrack(track, stream);
        

        this.localVideo.srcObject = stream;

        return this.remotePeerConnection.createAnswer();
      )
      .then(answer => 
        this.remotePeerConnection.setLocalDescription(answer);
        return answer;
      )
      .then(answer => 
        this.socket.emit('answer',  room: this.room, answer );
        console.log('SENDING ANSWER: ', answer);
      )
      .catch(error => console.error(error))
  

  render() 
    return (
      <div>
        <h1>Webrtc</h1>
        <div>
          <button onClick=this.startCall>CALL</button>
        </div>
        <div style= display: 'flex' >
          <div>
            <video id='localVideo' autoPlay muted playsInline ref=ref => this.localVideo = ref />
            <p>LOCAL VIDEO</p>
          </div>
          <div>
            <video id='remoteVideo' autoPlay muted playsInline ref=ref => this.remoteVideo = ref />
            <p>REMOTE VIDEO</p>
          </div>
        </div>
      </div>
    );
  


export default App;

服务器

const express = require('express');

const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const PORT = process.env.PORT || 5000;

const connections = [];
const clients = [];

io.set('origins', '*:*');
io.on('connection', socket => 
  connections.push(socket);
  clients.push( socket_id: socket.id );
  console.log('Connected: %s sockets connected ', connections.length);

  socket.on('join', (room, callback) => 
    const clients = io.sockets.adapter.rooms[room];
    const numClients = (typeof clients !== 'undefined') ? clients.length : 0;
    console.log('joined room', room);
    if (numClients > 1) 
      return callback('already_full');
    
    else if (numClients === 1) 
      socket.join(room);
      io.in(room).emit('ready');
    
    else 
      socket.join(room);
    

    callback();
  );

  socket.on('offer', (data) => 
    const  room, offer  = data;
    console.log('offer from: ', offer);
    socket.to(room).emit('offer', offer);
  );

  socket.on('answer', (data) => 
    const  room, answer  = data;
    console.log('answer from: ', answer);
    socket.to(room).emit('answer', answer);
  );

  socket.on('candidate', (data) => 
    const  room, candidate  = data;
    console.log('candidate: ', candidate);
    socket.to(room).emit('candidate', candidate);
  );

  socket.on('disconnect', () => 
    connections.splice(connections.indexOf(socket), 1);
    console.log('Disconnected: %s sockets connected, ', connections.length);
    clients.forEach((client, i) => 
      if (client.socket_id === socket.id) 
        clients.splice(i, 1);
      
    );
  );
);

server.listen(PORT, () => 
  console.log(`Server listening on port $PORT`);
);

更新

看了jib的评论,我修改了我的客户端js如下。

import React,  Component  from 'react';
import io from 'socket.io-client';

class App extends Component 
  constructor(props) 
    super(props);
    this.room = 'test-room';
    this.socket = io.connect('http://localhost:5000');
    this.peerConnection = new RTCPeerConnection(
      iceServers: [
        
          urls: 'stun:stun.l.google.com:19302'
        
      ]
    );
  ;

  componentDidMount() 
    this.socket.on('connect', () => 
      this.socket.emit('join', this.room, err => 
        if (err) 
          console.error(err);
         else 
          this.socket.on('offer', offer => 
            console.log('OFFER RECEIVED: ', offer);
            this.createAnswer(offer);
          );

          this.socket.on('candidate', candidate => 
            this.peerConnection.addIceCandidate(candidate).catch(error => console.error(error));
            console.log('CANDIDATE RECEIVED', candidate);
          );

          this.socket.on('answer', answer => 
            console.log('ANSWER RECEIVED:', answer);
            this.peerConnection.setRemoteDescription(answer);
          );
        
      );
    );
  

  startCall = async () => 
    this.peerConnection.oniceconnectionstatechange = () => console.log('ICE CONNECTION STATE: ', this.peerConnection.iceConnectionState);

    this.peerConnection.onicecandidate = e => 
      const iceCandidate = e.candidate;
      this.socket.emit('candidate',  room: this.room, candidate: iceCandidate );
      console.log('candidate generated', e.candidate);
    ;

    this.peerConnection.ontrack = e => 
      this.remoteVideo.srcObject = e.streams[0];
      console.log('REMOTE STREAMS: ', this.peerConnection.getRemoteStreams());
    ;

    try 
      const stream = await navigator.mediaDevices.getUserMedia( video:  width: 150, height: 150 , audio: false );
      for (const track of stream.getTracks()) 
        this.peerConnection.addTrack(track, stream);
      

      this.localVideo.srcObject = stream;
      console.log('LOCAL STREAMS: ', this.peerConnection.getLocalStreams())

      return this.createOffer();
     catch (error) 
      console.error(error);
    
  

  createOffer = async () => 
    try 
      const offer = await this.peerConnection.createOffer();
      await this.peerConnection.setLocalDescription(offer);
      this.socket.emit('offer',  room: this.room, offer );
      console.log('SENDING OFFER: ', offer);
     catch (error) 
      console.error(error);
    
  

  createAnswer = async description => 
    this.peerConnection.onicecandidate = e => 
      const iceCandidate = e.candidate;
      this.socket.emit('candidate',  room: this.room, candidate: iceCandidate );
      console.log('candidate generated', e.candidate);
    ;

    this.peerConnection.ontrack = e => 
      this.remoteVideo.srcObject = e.streams[0];
    ;

    this.peerConnection.setRemoteDescription(description)
      .then(() => navigator.mediaDevices.getUserMedia( video:  width: 150, height: 150 , audio: false ))
      .then(stream => 
        for (const track of stream.getTracks()) 
          this.peerConnection.addTrack(track, stream);
        

        this.localVideo.srcObject = stream;

        return this.peerConnection.createAnswer();
      )
      .then(answer => 
        this.peerConnection.setLocalDescription(answer);
        return answer;
      )
      .then(answer => 
        this.socket.emit('answer',  room: this.room, answer );
        console.log('SENDING ANSWER: ', answer);
      )
      .catch(error => console.error(error))
  

  render() 
    return (
      <div>
        <h1>Webrtc</h1>
        <div>
          <button onClick=this.startCall>CALL</button>
        </div>
        <div style= display: 'flex' >
          <div>
            <video id='localVideo' autoPlay muted playsInline ref=ref => this.localVideo = ref />
            <p>LOCAL VIDEO</p>
          </div>
          <div>
            <video id='remoteVideo' autoPlay muted playsInline ref=ref => this.remoteVideo = ref />
            <p>REMOTE VIDEO</p>
          </div>
        </div>
      </div>
    );
  


export default App;

我的控制台上的错误仍然存​​在...知道为什么吗?

【问题讨论】:

你在两边都设置了相同的候选人。为什么同一个JS中有两个peer?如果您只是在进行本地循环演示,则根本不需要信号。请参阅this answer 寻求帮助。 您好jib,在查看链接中的答案后,我对代码进行了一些修改。但是我的控制台上的错误仍然存​​在。为了进一步说明,我将我的信令服务器托管在 heroku 上,并且在我拨打电话时也可以看到本地和远程视频。知道什么可能导致控制台上的错误吗? 谷歌浏览器可能存在问题。我在 Firefox 和 Safari 上对此进行了测试,在这些浏览器上我没有收到任何错误。 【参考方案1】:

我的控制台上的错误仍然存​​在...知道为什么吗?

这是known bug in Chrome(请★修复它!)

要查看它,请在 Chrome 78 的 Web 控制台中输入以下内容:

const pc = new RTCPeerConnection(); pc.setRemoteDescription(await pc.createOffer())

然后

pc.addIceCandidate(undefined)

它会产生:

TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection':
 Candidate missing values for both sdpMid and sdpMLineIndex

现在试试

pc.addIceCandidate()

它说:

TypeError: Failed to execute 'addIceCandidate' on 'RTCPeerConnection':
 1 argument required, but only 0 present.

两者都违反了latest spec,它说pc.addIceCandidate()“候选人结束指示”“适用于所有媒体描述。” em>

解决方法

在 Chrome 修复它之前,您可以放心地忽略此错误,或者捕获 TypeError 并取消它。

我建议不要将 if (candidate) pc.addIceCandidate(candidate) 作为一种解决方法,因为一旦 Chrome 修复了此问题,它将阻止 iceConnectionState 在 Chrome 或任何其他浏览器中进入 "completed" 状态。

【讨论】:

阅读您的回答后,我决定忽略该错误。非常感谢您的帮助! @jib,我正面临这个错误,从你的回答来看,chrome 似乎还没有解决它!是这样吗? @WagnerPatriota 实际上,上游错误最近已修复,现在在 Chrome 90 中对我有效。? 在 Chrome 91.0.4472.101 中仍然看到这个问题 @AzimjonIlkhomov WFM 在 Mac 上的 91.0.4472.101 中。我看到Promise&lt;fulfilled&gt;: undefined 我认为这是正确的。您是否将上述逐字输入控制台?您看到了什么错误?

以上是关于WebRTC 无法在控制台上的 RTCPeerConnection 错误上执行“addIceCandidate”,但仍可以显示远程和本地视频的主要内容,如果未能解决你的问题,请参考以下文章

在 kafka 控制台上无法输入大小超过 4095 个字符的消息

我无法在 Google Play 控制台上重置敏感应用权限声明

我无法在 Google Play 商店控制台上上传手机和观看 apk

Google beta 测试:我无法在开发者控制台上看到我的应用程序的当前/总安装量

我无法测试我在Google Play控制台上注册的应用

Log4j控制台上的警告