我試圖實現一個實時音頻/視頻組呼叫,但現在我只想讓它只有兩個參與者。WebRTC錯誤:無法創建遠程會話描述。調用狀態錯誤
它不工作,我不明白爲什麼:(實際上當我用兩個不同的帳戶在同一時間測試它時,我看到一些錯誤消息,但它仍然有效,但是當我測試它時在現實不同網絡的朋友,相同的錯誤消息出現,但在這種情況下,我們無法聽到或看到對方)。
我使用Chrome 53在Linux(Ubuntu的16.04)。
錯誤消息對於發送報價的同行來說,瀏覽器控制檯中有6個錯誤: 1st:
Failed to create remote session description: OperationError: Failed to set remote offer sdp: Called in wrong state: STATE_SENTOFFER
第2,第3,第4和第5:
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
在你想看到控制檯中的所有消息的情況下,在誰發送的報價在同行中的人在這裏: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()
}
}
}
最後是代碼相關的信號。但首先,我想澄清一下,我做這個網站,導軌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}
和紅寶石一(在服務器端):
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」手段「提供/答覆交換完成「,因此我無法理解提供/答覆交換是否完成......當我嘗試向我們提供時,爲什麼它不起作用與朋友一起嗎?爲什麼它要在這種情況下設置更多的遠程會話描述(當提議/應答交換應該完成時)? 所以基本上我的主要問題是:發生了什麼,我該如何解決?
如果您已經達到了這個問題的部分,謝謝,我很感激! :)
感謝您的回答:) 是的,我已經在一本書中看到過,但正如我之前所說的,現在我只想讓它參與2個參與者,並且錯誤僅用2來測試。將爲更多的人提供服務,這就是爲什麼你可以在信令服務器中看到爲更多人準備的信息,但不是在客戶端,因爲如果我沒有讓它工作到2個,那就不是這個時刻以增加更多的併發症。 – Marta
如果你想測試它,這是[網站](https://lanformon.herokuapp.com/),你可以加入任何這些電話,並嘗試2人(使用不同的瀏覽器或使用相同的但隱身窗口中的一個窗口,因此您可以使用2個帳戶)。我剛剛爲SO創建了兩個用戶,電子郵件爲「[email protected]」和「[email protected]」,均使用密碼:「password」 – Marta
我認爲如果您想要2個用戶或5.每一個你需要一個peerConnection。 –