WebRTC状态检查

时间:2018-12-03 07:49:30

标签: react-native webrtc

我想用本地主机上的WebRTC连接2台设备。所有设备均无法访问互联网。它们已连接到同一本地wifi。

我在React Native App上尝试过

在这种情况下,我是否需要滴流ICE候选人和addIceCandidate?如果我正确理解,则ICE候选人适合iceServer。但是我的情况iceServer为空(因为我仅处于离线状态,并且连接在同一本地wifi上):

const configuration = { iceServers: [{ urls: [] }] };

实际上,我交换要约和答案,但在setRemoteDescription之后,connectionState停留在checking上。

您可以看到我的React组件:

  constructor(props) {
    super(props);
    this.pc = new RTCPeerConnection(configuration);
  }

  state = initialState;

  componentDidMount() {
    const { pc } = this;

    if (pc) {
      this.setState({
        peerCreated: true
      });
    }

    this.setConnectionState();

    pc.oniceconnectionstatechange = () => this.setConnectionState();

    pc.onaddstream = ({ stream }) => {
      if (stream) {
        this.setState({
          receiverVideoURL: stream.toURL()
        });
      }
    };

    pc.onnegotiationneeded = () => {
      if (this.state.initiator) {
        this.createOffer();
      }
    };

    pc.onicecandidate = ({ candidate }) => {
      if (candidate === null) {
        const { offer } = this.state;
        const field = !offer ? 'offer' : 'data';

        setTimeout(() => {
          alert('setTimeout started');
          this.setState({
            [field]: JSON.stringify(pc.localDescription)
          });
        }, 2000);
      }
    };
  }

  @autobind
  setConnectionState() {
    this.setState({
      connectionState: this.pc.iceConnectionState
    });
  }

  getUserMedia() {
    MediaStreamTrack.getSources(() => {
      getUserMedia(
        {
          audio: false,
          video: true
        },
        this.getUserMediaSuccess,
        this.getUserMediaError
      );
    });
  }

  @autobind
  async getUserMediaSuccess(stream) {
    const { pc } = this;

    pc.addStream(stream);

    await this.setState({ videoURL: stream.toURL() });

    if (this.state.initiator) {
      return this.createOffer();
    }

    return this.createAnswer();
  }

  getUserMediaError(error) {
    console.log(error);
  }

  @autobind
  logError(error) {
    const errorArray = [...this.state.error, error];
    return this.setState({
      error: errorArray
    });
  }

  /**
   * Create offer
   *
   * @memberof HomeScreen
   */
  @autobind
  createOffer() {
    const { pc } = this;

    pc.createOffer()
      .then(offer => pc.setLocalDescription(offer))
      .then(() => {
        this.setState({
          offerCreated: true
        });
      })
      .catch(this.logError);
  }

  /**
   * Create anwser
   *
   * @memberof HomeScreen
   */
  @autobind
  async createAnswer() {
    const { pc } = this;
    const { data } = this.state;

    if (data) {
      const sd = new RTCSessionDescription(JSON.parse(data));

      await this.setState({
        offerImported: true
      });

      pc.setRemoteDescription(sd)
        .then(() => pc.createAnswer())
        .then(answer => pc.setLocalDescription(answer))
        .then(() => {
          this.setState({
            answerCreated: true
          });
        })
        .catch(this.logError);
    }
  }

  @autobind
  receiveAnswer() {
    const { pc } = this;
    const { data } = this.state;
    const sd = new RTCSessionDescription(JSON.parse(data));

    return pc
      .setRemoteDescription(sd)
      .then(() => {
        this.setState({
          answerImported: true
        });
      })
      .catch(this.logError);
  }

  /**
   * Start communication
   *
   * @param {boolean} [initiator=true]
   * @returns
   * @memberof HomeScreen
   */
  @autobind
  async start(initiator = true) {
    if (!initiator) {
      await this.setState({
        initiator: false
      });
    }

    return this.getUserMedia();
  }

有人可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

在局域网上没有iceServers很好,但是对等方仍必须交换至少一个候选者:其 host 候选者(基于其计算机的LAN IP地址)。

要么:

  1. 像往常一样使用onicecandidate->信令-> addIceCandidate来滴答候选人,或者

  2. 在交换pc.localDescription之前等待ICE处理(几秒钟)。

您似乎正在尝试后者。这种方法之所以有效,是因为...

Trickle ICE是一种优化。

使用onicecandidate的单个候选冰块的发信号(点滴)是旨在加速协商的优化。 setLocalDescription成功后,浏览器的内部 ICE Agent 启动,将发现的ICE候选者插入localDescription本身。等待几秒钟进行协商,就完全不需要滴答滴答:所有ICE候选人都将在录取通知书中并发送答复。

您的代码

从您的onicecandidate代码看来,您似乎已经在ICE完成后尝试收集localDescription(并且您已经编写了使其可以在两端使用):

pc.onicecandidate = ({ candidate }) => {
  if (!candidate) {
    const { offer } = this.state;
    const field = !offer ? 'offer' : 'data';

    this.setState({
      [field]: JSON.stringify(pc.localDescription)
    });
  }
};

在要约方,您已经正确注释了createOffer中的等效代码:

pc.createOffer()
  .then(offer => pc.setLocalDescription(offer))
  .catch(this.logError);
// .then(() => {
//   this.setState({
//     offer: JSON.stringify(pc.localDescription)
//   });
// });

但是在答复者那边,您还没有,这很可能是问题所在:

createAnswer() {
    const { pc } = this;
    const { data } = this.state;

    if (data) {
      const sd = new RTCSessionDescription(JSON.parse(data));

      pc.setRemoteDescription(sd)
        .then(() => pc.createAnswer())
        .then(answer => pc.setLocalDescription(answer))
        .then(() => {
          this.setState({
            offer: JSON.stringify(pc.localDescription)
          });
        })
        .catch(this.logError);
    }
  }

这意味着它会在答答者的 ICE代理有时间将任何候选者插入答案之前,立即将答案发送回去。 这可能就是失败的原因。

在旁注::似乎没有什么可以等待getUserMedia完成,因此,根据您{{1 }}函数,该函数无法将任何轨道或​​流添加到连接中。但是假设您只是在做数据通道,这应该可以使用我建议的修复程序。