WebRTC错误:创建远程会话描述失败.在错误的状态下调用



我正在尝试实现实时音频/视频组呼叫,但现在我只想为两个参与者获得它。

它不工作,我不明白为什么:(实际上当我用两个不同的帐户同时测试它时,我看到一些错误消息,但它无论如何都有效,但当我在真实的不同网络中与朋友测试时,出现相同的错误消息,但在这种情况下,我们无法听到或看到对方)。

我在Linux上使用Chrome 53 (Ubuntu 16.04)。

错误消息如下,对于发送报价的对等体,浏览器控制台中有6个错误。1:

Failed to create remote session description: OperationError: Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER

第二、三、四、五:

addIceCandidate error: OperationError: Error processing ICE candidate

6:

Failed to set local session description: OperationError: CreateAnswer failed because remote_description is not an offer

对于接收要约并发送和回答的对等端,浏览器控制台有1个错误:

Failed to create remote session description: OperationError: Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS

如果您想在控制台中查看所有消息,则发送offer的对等体中的消息如下:WebRTC error from offer peer。另一个对等浏览器控制台中有:来自对等回答的WebRTC错误。

HTML文件中重要的代码如下所示(后面我会展示其他带有Javascript代码的文件):

<div class='row'>
  <div class='col-xs'>
    <div class='box center-xs middle xs'>
      <h1>Call CallNameExample</h1>
    </div>
  </div>
</div>
<div class='row'>
  <div class='col-xs'>
    <div class='box center-content'>
      <button class='btn btn-info btn-37 no-padding circle' id='btnChangeCamStatus'>
        <i class='material-icons' id='iconCamOff'>
          videocam_off
        </i>
        <i class='material-icons hidden' id='iconCamOn'>
          videocam
        </i>
      </button>
      <button class='btn btn-info btn-37 no-padding circle' id='btnChangeMicStatus'>
        <i aria-hidden='true' class='fa fa-microphone-slash' id='iconMicOff'></i>
        <i aria-hidden='true' class='fa fa-microphone hidden' id='iconMicOn'></i>
      </button>
    </div>
  </div>
</div>
<div class='row'>
  <div class='col-xs'>
    <div class='box center-xs middle xs'>
      <video autoplay height='200px' id='bigRemoteVideo' width='200px'></video>
    </div>
  </div>
</div>
<script>
  var room = "1"
  var localVideo = document.getElementById("localVideo")
  var bigRemoteVideo = document.getElementById("bigRemoteVideo")
  document.getElementById("btnChangeCamStatus").addEventListener("click", function() {
    if (localStream.getVideoTracks()[0].enabled) {
      disableCam()
      $("#iconCamOff").addClass("hidden")
      $("#iconCamOn").removeClass("hidden")
    } else {
      enableCam()
      $("#iconCamOff").removeClass("hidden")
      $("#iconCamOn").addClass("hidden")
    }
  }, false);
  document.getElementById("btnChangeMicStatus").addEventListener("click", function() {
    if (localStream.getAudioTracks()[0].enabled) {
      disableMic()
      $("#iconMicOff").addClass("hidden")
      $("#iconMicOn").removeClass("hidden")
    } else {
      enableMic()
      $("#iconMicOff").removeClass("hidden")
      $("#iconMicOn").addClass("hidden")
    }
  }, false);
  function setLocalVideo(stream) {
    localVideo.src = window.URL.createObjectURL(stream)
  }
  function setRemoteVideo(stream) {
    bigRemoteVideo.src = window.URL.createObjectURL(stream)
  }
  localVideo.addEventListener('loadedmetadata', function() {
    console.log('Local video videoWidth: ' + this.videoWidth +
      'px,  videoHeight: ' + this.videoHeight + 'px');
  });
  bigRemoteVideo.addEventListener('loadedmetadata', function() {
    console.log('Remote video videoWidth: ' + this.videoWidth +
      'px,  videoHeight: ' + this.videoHeight + 'px');
  });
  // Starts the party:
  (function(){
    enableUserMedia()
    window.createOrJoin(room)
    console.log("Attempted to create or join room: " + room)
  }())
</script>

其他Javascript文件包含下面的代码(这里所有的文件放在一起):

var localStream
var mediaConstraints = {video: true, audio: true}
function enableUserMedia(){
  console.log('Getting user media with constraints', mediaConstraints);
  navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
  if (navigator.getUserMedia) {
    navigator.getUserMedia(mediaConstraints, gotStream, gotError)
  }
  window.URL = window.URL || window.webkitURL
  function gotStream(stream) {
    console.log('Adding local stream.');
    setLocalVideo(stream)
    localStream = stream;
    //sendMessage('got user media');
    console.log('got user media');
    attachLocalMedia();
  }
  function gotError(error) {
    console.log("navigator.getUserMedia error: ", error);
  }
}
function disableCam(){
  localStream.getVideoTracks()[0].enabled = false
}
function disableMic(){
  localStream.getAudioTracks()[0].enabled = false
}
function enableCam(){
  localStream.getVideoTracks()[0].enabled = true
}
function enableMic(){
  localStream.getAudioTracks()[0].enabled = true
}
function disableUserMedia(){
  localStream.getVideoTracks()[0].stop();
  localStream.getAudioTracks()[0].stop();
}
window.onbeforeunload = function() {
  sendMessage("bye");
};
function hangup() {
  console.log("Hanging up.");
  stop();
  sendMessage("bye");
}
function handleRemoteHangup() {
  console.log("Session terminated.");
  stop();
}
function stop() {
  disableUserMedia();
  pc.close();
  console.log("PC STATE: " + pc.signalingState || pc.readyState);
  console.log("PC ICE STATE: " + pc.iceConnectionState)
  pc = null;
}
var isInitiator = false
var justJoinedRoom = false
var sdpConstraints = { // Set up audio and video regardless of what devices are present.
  'mandatory': {
    'OfferToReceiveAudio': true,
    'OfferToReceiveVideo': true
  }
}
function sendMessage(message){
  App.call.message(message);
}
function doCall() {
  console.log("Sending offer to peer");
  pc.createOffer(sdpConstraints)
    .then(setLocalAndSendMessage)
    .catch(handleCreateOfferError);
  //pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
function doAnswer() {
  console.log("Sending answer to peer.");
  pc.createAnswer()
    .then(setLocalAndSendMessage)
    .catch(onSetLocalSessionDescriptionError);
}
function setLocalAndSendMessage(sessionDescription) {
  console.log("setLocalAndSendMessage sending message" + JSON.stringify(sessionDescription));
  pc.setLocalDescription(sessionDescription)
    .then(
      function(){
        onSetLocalSuccess();
        sendMessage(sessionDescription);
      }
    )
    .catch(onSetLocalSessionDescriptionError);
}
function onSetLocalSuccess() {
  console.log('setLocalDescription complete');
}
function onSetRemoteSuccess() {
  console.log('setRemoteDescription complete');
  doAnswer();
}
function onSetLocalSessionDescriptionError(error) {
  console.error('Failed to set local session description: ' + error.toString())
}
function handleCreateOfferError(event) {
  console.error("createOffer() error: " + JSON.stringify(event))
}
function onSetRemoteSessionDescriptionError(error) {
  console.error("Failed to create remote session description: " + error.toString())
}
function handleReceivedOffer(message) {
  console.log("handleReceivedOffer: " + JSON.stringify(message));
  pc.setRemoteDescription(new RTCSessionDescription(message))
    .then(onSetRemoteSuccess)
    .catch(onSetRemoteSessionDescriptionError)
}
function handleReceivedAnswer(message) {
  console.log("handleReceivedAnswer: " + JSON.stringify(message));
  pc.setRemoteDescription(new RTCSessionDescription(message))
    .then(onSetRemoteSuccess)
    .catch(onSetRemoteSessionDescriptionError)
}
function handleReceivedCandidate(label, candidate) {
  pc.addIceCandidate(
    new RTCIceCandidate({
      sdpMLineIndex: label,
      candidate: candidate
    })
  ).then(successAddingIceCandidate).catch(errorAddingIceCandidate)
}
function successAddingIceCandidate() { console.log("addIceCandidate successfully") }
function errorAddingIceCandidate(error) { console.error("addIceCandidate error: " +  error.toString()) }
var remoteStream
var pc
var pcConfig = {
  'iceServers': [{
    'url': 'stun:stun.l.google.com:19302'
  }, {
    'url': 'turn:192.158.29.39:3478?transport=udp',
    'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
    'username': '28224511:1379330808'
  }]
}
function connectionStateCallback(){
  var state;
  if (pc) {
    state = pc.connectionState
    console.log("PC CONNECTION state change callback, state: " + state)
  }
}
function signalingStateCallback() {
  var state;
  if (pc) {
    state = pc.signalingState || pc.readyState;
    console.log("PC SIGNALING state change callback, state: " + state);
  }
}
function iceStateCallback() {
  var iceState;
  if (pc) {
    iceState = pc.iceConnectionState;
    console.log('PC ICE connection state change callback, state: ' + iceState);
  }
}
function createPeerConnection() {
  try {
    pc = new RTCPeerConnection(pcConfig);
    signalingStateCallback();
    pc.onsignalingstatechange = signalingStateCallback;
    console.log("PC ICE STATE: " + pc.iceConnectionState);
    pc.oniceconnectionstatechange = iceStateCallback;
    pc.onconnectionstatechange = connectionStateCallback;
    pc.onicecandidate = handleIceCandidate;
    pc.onaddstream = handleRemoteStreamAdded;
    pc.onremovestream = handleRemoteStreamRemoved;
    console.log('Created RTCPeerConnnection');
    attachLocalMedia();
  } catch (e) {
    console.error("Failed to create PeerConnection, exception: " + e.toString())
    return;
  }
}
function handleIceCandidate(event) {
  console.log("icecandidate event: " + JSON.stringify(event));
  if (event.candidate) {
    sendMessage({
      type: "candidate",
      label: event.candidate.sdpMLineIndex,
      id: event.candidate.sdpMid,
      candidate: event.candidate.candidate
    });
  } else {
    console.log("End of candidates.");
  }
}
function handleRemoteStreamAdded(event) {
  console.log("Remote stream added.");
  setRemoteVideo(event.stream);
  remoteStream = event.stream;
}
function handleRemoteStreamRemoved(event) { //In real life something should be done here but since the point of this website is to learn, this function is not a priority right now.
  console.log("Remote stream removed. Event: " + event);
}
function attachLocalMedia() {
  if (pc && localStream) {
    pc.addStream(localStream)
    console.log("Added localStream to pc")
    if (justJoinedRoom) {
      console.log("call to DOCALL() from attachLocalMedia()")
      doCall()
    }
  }
}

最后是与信令相关的代码。但首先我想澄清一下,我正在用Rails 5做这个网站,并通过ActionCable与WebSockets进行信令,所以通道的CoffeeScript文件(客户端)是这个:

window.createOrJoin = (roomID) ->
  App.call = App.cable.subscriptions.create { channel: "CallChannel", room: roomID },
    connected: ->
      # Called when the subscription is ready for use on the server
      createPeerConnection()
    disconnected: ->
      # Called when the subscription has been terminated by the server
    received: (data) ->
      # Called when there's incoming data on the websocket for this channel
      if (data["kindOfData"] == "created")
        console.log('Created room ' +  data["room"])
        window.isInitiator = true # ESTO ME SIRVE SOLO PARA 2 PERSONAS!! # CREO QUE YA NI LO USO
        attachLocalMedia()
      else if (data["kindOfData"] == "full")
        console.log('Room ' + data["room"] + ' is full')
      else if (data["kindOfData"] == "join")
        console.log('Another peer made a request to join room ' + data["room"])
        console.log('This peer is the initiator of room ' + data["room"] + '!')
        window.justJoinedRoom = false
      else if (data["kindOfData"] == "joined")
        console.log('joined: ' + data["room"])
        window.justJoinedRoom = true
        attachLocalMedia()
      else if (data["kindOfData"] == "log")
        console.log(data["info"])
      else if (data["kindOfData"] == "message") # This client receives a message
        console.log("Client received message: " + JSON.stringify(data["message"]));
        if (data["message"] == "bye")
          handleRemoteHangup()
        else if (data["message"]["type"] == "offer")
          handleReceivedOffer(data["message"]) # obj with "type" and "sdp"
        else if (data["message"]["type"] == "answer")
          handleReceivedAnswer(data["message"]) # obj with "type" and "sdp"
        else if (data["message"]["type"] == "candidate")
          handleReceivedCandidate(data["message"]["label"], data["message"]["candidate"])

    message: (data) ->
      console.log("Client sending message: " + JSON.stringify(data));
      @perform "message", {message: data, room: roomID}

和Ruby(服务器端):

class CallChannel < ApplicationCable::Channel
  def subscribed # Action automatically called when a client is subscribed to the channel
    stream_from "calls" # calls is a channel in common for everyone # ONLY FOR TESTING!!!
    stream_from "calls_room#{params[:room]}_person#{current_user.id}"
    @@hashUsersByRoom ||= Hash.new() # { |h,k| h[k] = Set.new }
    @@hashRoomsByUser ||= Hash.new() # { |h,k| h[k] = Set.new }
    result = createOrJoin(params[:room])
  end
  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
  def message(data)
    if data["message"].eql? "bye"
      if @@hashUsersByRoom[ data["room"] ] && @@hashUsersByRoom[ data["room"] ].include?( current_user.id )
        @@hashUsersByRoom[ data["room"] ].delete( current_user.id )
        if @@hashUsersByRoom[ data["room"] ].length() == 0
          @@hashUsersByRoom.delete( data["room"] )
          Call.find( data["room"] ).update_column("active", false)
        end
      end
      if @@hashRoomsByUser[ current_user.id ] && @@hashRoomsByUser[ current_user.id ].include?( data["room"] )
        @@hashRoomsByUser[ current_user.id ].delete( data["room"] )
        if @@hashRoomsByUser[ current_user.id ].length() == 0
          @@hashRoomsByUser.delete( current_user.id )
        end
      end
    end
    ActionCable.server.broadcast "calls_room#{data["room"]}", kindOfData: "log", info: "Client #{current_user.id} said: #{data["message"]}"
    ActionCable.server.broadcast "calls_room#{data["room"]}", kindOfData: "message", message: data["message"]
  end
  private
    def createOrJoin(room)
      ActionCable.server.broadcast "calls", kindOfData: "log", info: "Received request to create or join room #{room}"
      @@hashUsersByRoom[room] ||= Set.new()
      ActionCable.server.broadcast "calls", kindOfData: "log", info: "Room #{room} now has #{@@hashUsersByRoom[room].length()} + client(s)"
      if @@hashUsersByRoom[room].length == 0
        stream_from "calls_room#{room}" # Join the room
        @@hashUsersByRoom[ room ] << current_user.id
        @@hashRoomsByUser[ current_user.id ] ||= Set.new()
        @@hashRoomsByUser[ current_user.id ] << room
        ActionCable.server.broadcast "calls", kindOfData: "log", info: "Client ID #{current_user.id} created room #{room}"
        ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "created", room: room, user: current_user.id
        Call.find(room).update_column("active", true)
      elsif ( @@hashUsersByRoom[room].length() < Call.where(:id => room).pluck(:maximumNumberOfParticipants)[0] ) || ( @@hashUsersByRoom[ data["room"] ].include?( current_user.id ) )
        ActionCable.server.broadcast "calls", kindOfData: "log", info: "Client ID #{current_user.id} joined room #{room}"
        ActionCable.server.broadcast "calls_room#{room}", kindOfData: "join", room: room
        stream_from "calls_room#{room}" # Join the room
        @@hashUsersByRoom[ room ] << current_user.id
        @@hashRoomsByUser[ current_user.id ] ||= Set.new()
        @@hashRoomsByUser[ current_user.id ] << room
        ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "joined", room: room, user: current_user.id
        ActionCable.server.broadcast "calls_room#{room}", kindOfData: "ready"
      else # full room
        ActionCable.server.broadcast "calls_room#{room}_person#{current_user.id}", kindOfData: "full", room: room
      end
    end
end

在互联网上搜索,我看到有类似问题的人,但每个人都有不同的原因,没有一个对我的情况有用,但我在某处看到"STATE_INPROGRESS"意味着"Offer/answer exchange completed",因此我无法理解Offer/answer exchange是否已经完成…为什么当我和朋友一起使用时它不起作用?为什么它试图设置更多的远程会话描述在这种情况下(当提供/回答交换应该完成)?所以基本上我的主要问题是:发生了什么,我该如何解决它?

如果你达到了问题的这一部分,谢谢你,我很感激!:)

如果你想做多方,你需要为每对参与者一个peerconnection。您当前使用的是单个文件

相关内容

  • 没有找到相关文章

最新更新