【问题标题】:WebRTC Error: Failed to create remote session description. Called in wrong stateWebRTC 错误:无法创建远程会话描述。在错误的状态下调用
【发布时间】:2016-09-24 13:47:06
【问题描述】:

我正在尝试实现实时音频/视频群组通话,但现在我只想为两个参与者获取它。

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

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

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

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

第六:

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

如果您想查看控制台中的所有消息,发送报价的对等点中的消息在这里:WebRTC error from offer peer。其他对等浏览器控制台中的控制台在这里:WebRTC error from answer peer。

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”的意思是“报价/答案交换已完成”,所以从那我不明白提议/答案交换是否已完成......为什么当我尝试与朋友一起使用它时它不起作用?以及为什么在这种情况下尝试设置更多的远程会话描述(当提议/答案交换应该完成时)? 所以基本上我的主要问题是:发生了什么,我该如何解决?

如果您遇到问题的这一部分,谢谢,我很感激! :)

【问题讨论】:

    标签: javascript ruby-on-rails websocket webrtc actioncable


    【解决方案1】:

    如果您想进行多方操作,您需要为每对参与者建立一个对等连接。您目前正在使用一个。

    请参阅官方 WebRTC 示例中的 this example。

    【讨论】:

    • 感谢您的回答 :) 是的,我已经在一本书中看到了这一点,但正如我之前所说,我现在只想要 2 个参与者,错误是只用 2 个测试它。在未来我会为更多人做这件事,这就是为什么你可以在为更多人准备的信号服务器中看到,但在客户端还没有,因为如果我还没有让它为 2 工作,它是现在不是增加更多复杂性的时候。
    • 如果您想测试它,这是website,您可以加入其中的任何一个电话并为 2 个人尝试(使用不同的浏览器或使用相同的浏览器但一个窗口隐身所以你可以使用2个帐户)。我刚刚为 SO 创建了两个用户,电子邮件“stackoverflowuser01@email.com”和“stackoverflowuser02@email.com”,密码:“password”
    • 我认为你想要 2 个用户还是 5 个用户都没关系。你需要为每个用户一个 peerConnection。
    • 每个用户需要 1 个 peerConnection 来连接另一个用户,所以如果一个用户想要连接另外 4 个用户(那么在通话中他们总共是 5 人),每个用户需要 4 个 peerConnection,每个peerConnection 与另一个用户连接,但如果他只想连接 1 个用户,他需要 1 个 peerConnection(总共有 2 个用户,另一个人和他自己),另一个人也只需要一个来连接第一个一。你可以看到我在说什么here和here
    猜你喜欢
    • 2020-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多