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

标签 javascript reactjs google-chrome socket.io webrtc

我正在尝试使用 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;

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

最佳答案

The error on my console still persists... any idea why?

这是一个 known bug in Chrome (请★修好它!)

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

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()“候选结束指示” “适用于所有媒体描述。”/p>

解决方法

您可以安全地忽略此错误,直到 Chrome 修复它,或者捕获 TypeError 并抑制它。

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

关于javascript - WebRTC 无法在控制台上的 RTCPeerConnection 错误上执行 'addIceCandidate' 但仍可以显示远程和本地视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58908081/

相关文章:

javascript - 切换关闭其他打开的 div

javascript - Jade 中数组的长度未定义

javascript - 无法在 Firefox 上从 Javascript 提交表单

reactjs - react native Web ScrollView 快照?

javascript - 有没有办法让浏览器中的时间快进来触发页面上设置的setTimeouts?

javascript - 外部 Javascript 文件和 Uncaught ReferenceError

reactjs - 在 render 方法中递归地检查组件的子组件

javascript - 在 react 中多次触发反跳

c++ - 如何开始编辑 Chromium 的源代码?

html - Chrome 中的 Arial 窄字体问题