首页 > 解决方案 > Flutter WebRTC 无法连接到对等点。无法设置远程应答 sdp:在错误状态下调用:kStable

问题描述

所以我正在使用颤振(和 web 的颤振)来构建一个 WebRTC 客户端。我有一个 spring-boot 服务器充当两个客户端的中间人。他们都订阅了 WebSocket 以从对方那里获取消息。仅此而已。

我越来越Error: InvalidStateError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote answer sdp: Called in wrong state: kStable

我不知道为什么会发生这个错误。

这是信号的代码

 typedef void StreamStateCallback(MediaStream stream);

class CallingService {
  String sendToUserId;
  String currentUserId;
  final String authToken;
  final StompClient _client;
  final StreamStateCallback onAddRemoteStream;
  final StreamStateCallback onRemoveRemoteStream;
  final StreamStateCallback onAddLocalStream;
  RTCPeerConnection _peerConnection;
  List<RTCIceCandidate> _remoteCandidates = [];
  String destination;
  var hasOffer = false;
  var isNegotiating = false;

  final Map<String, dynamic> _constraints = {
    'mandatory': {
      'OfferToReceiveAudio': true,
      'OfferToReceiveVideo': true,
    },
    'optional': [],
  };

  CallingService(
      this._client,
      this.sendToUserId,
      this.currentUserId,
      this.authToken,
      this.onAddRemoteStream,
      this.onRemoveRemoteStream,
      this.onAddLocalStream) {
    destination = '/app/start-call/$sendToUserId';
    print("destination $destination");
    _client.subscribe(
        destination: destination,
        headers: {'Authorization': "$authToken"},
        callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
  }

  Future<void> startCall() async {
    await processRemoteStream();
    RTCSessionDescription description =
        await _peerConnection.createOffer(_constraints);
    await _peerConnection.setLocalDescription(description);
    var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
      'description': {'sdp': description.sdp, 'type': description.type},
    });
    sendMessage(message);
  }

  Future<void> processMessage(Map<String, dynamic> messageJson) async {
    var message = RtcMessage.fromJson(messageJson);
    if (message.from == currentUserId) {
      return;
    }
    print("processing ${message.messageType.toString()}");
    switch (message.messageType) {
      case RtcMessageType.BYE:
        // TODO: Handle this case.
        break;
      case RtcMessageType.LEAVE:
        // TODO: Handle this case.
        break;
      case RtcMessageType.CANDIDATE:
        await processCandidate(message);
        break;
      case RtcMessageType.ANSWER:
        await processAnswer(message);
        break;
      case RtcMessageType.OFFER:
        await processOffer(message);
        break;
    }
  }

  Future<void> processCandidate(RtcMessage candidate) async {
    Map<String, dynamic> map = candidate.data['candidate'];
    var rtcCandidate = RTCIceCandidate(
      map['candidate'],
      map['sdpMid'],
      map['sdpMLineIndex'],
    );
    if (_peerConnection != null) {
      _peerConnection.addCandidate(rtcCandidate);
    } else {
      _remoteCandidates.add(rtcCandidate);
    }
  }

  Future<void> processAnswer(RtcMessage answer) async {
    if (isNegotiating){
      return;
    }
    var description = answer.data['description'];
    await _peerConnection.setRemoteDescription(
        RTCSessionDescription(description['sdp'], description['type']));
  }

  Future<void> processOffer(RtcMessage offer) async {
    await processRemoteStream();
    var description = offer.data['description'];
    await _peerConnection.setRemoteDescription(
        new RTCSessionDescription(description['sdp'], description['type']));
    var answerDescription = await _peerConnection.createAnswer(_constraints);
    await _peerConnection.setLocalDescription(answerDescription);
    var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
      'description': {
        'sdp': answerDescription.sdp,
        'type': answerDescription.type
      },
    });
    sendMessage(answerMessage);
    if (_remoteCandidates.isNotEmpty) {
      _remoteCandidates
          .forEach((candidate) => _peerConnection.addCandidate(candidate));
      _remoteCandidates.clear();
    }
  }

  Future<void> processRemoteStream() async {
    await createStream();
    _peerConnection = await createPeerConnection(_iceServers, _config);
    _peerConnection.onSignalingState = (state) {
      isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
    };
    _peerConnection.onAddTrack = (MediaStream stream, _) {
      this.onAddRemoteStream(stream);
      print("sending stream from track");
    };

    _peerConnection.onAddStream = (MediaStream stream) {
      this.onAddRemoteStream(stream);
      print("sending stream");
    };
    _peerConnection.onRemoveStream =
        (MediaStream stream) => this.onRemoveRemoteStream(stream);
    _peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
      print("sending candidate");
      var data = {
        'candidate': {
          'sdpMLineIndex': candidate.sdpMlineIndex,
          'sdpMid': candidate.sdpMid,
          'candidate': candidate.candidate,
        },
      };
      var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
      sendMessage(message);
    };
  }

  void sendMessage(RtcMessage message) {
    _client.send(
        destination: destination,
        headers: {'Authorization': "$authToken"},
        body: jsonEncode(message.toJson()));
  }

  Map<String, dynamic> _iceServers = {
    'iceServers': [
      {"url" : "stun:stun2.1.google.com:19302"},
      {'url' : 'stun:stun.l.google.com:19302'},
      /*
       * turn server configuration example.
      {
        'url': 'turn:123.45.67.89:3478',
        'username': 'change_to_real_user',
        'credential': 'change_to_real_secret'
      },
       */
    ]
  };

  final Map<String, dynamic> _config = {
    'mandatory': {},
    'optional': [
      {'DtlsSrtpKeyAgreement': true},
    ],
  };

  Future<MediaStream> createStream() async {
    final Map<String, dynamic> mediaConstraints = {
      'audio': true,
      'video': {
        'mandatory': {
          'minWidth':
              '640', // Provide your own width, height and frame rate here
          'minHeight': '480',
          'minFrameRate': '30',
        },
        'facingMode': 'user',
        'optional': [],
      }
    };

    MediaStream stream = await navigator.getUserMedia(mediaConstraints);
    if (this.onAddLocalStream != null) {
      this.onAddLocalStream(stream);
    }
    return stream;
  }
}

我的第一个问题是我没有为要约/回答阶段设置本地描述。

但是,当我添加一个新的 stun 服务器时,我得到了同样的异常。无论哪种方式,我都没有显示远程流。

标签: flutterwebrtcflutter-web

解决方案


因此,当我创建报价和答案时,我没有设置本地描述。所以就是这样。

它仍然没有显示远程连接。


推荐阅读