javascript WebRTC 无法添加第三个对等点:“无法在稳定状态下设置远程应答”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30109011/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WebRTC Failed to Add a Third Peer: "Cannot set remote answer in state stable"
提问by Lingyuan He
I am writing a multi-peer WebRTC video chat. Two peers have no trouble connecting, no error or warning in console, and video works well, but I cannot add a third party to the chat successfully.
我正在编写一个多点 WebRTC 视频聊天。两个对等连接没有问题,控制台没有错误或警告,视频效果很好,但我无法成功添加第三方到聊天。
On the host (the first participant, Firefox), the error appear as "Cannot set remote answer in state stable"when trying to create an answer. At the second participant (Chrome), the error is "Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS". At he third peer, the error is "the error is "Failed to set remote answer sdp: Called in wrong state: STATE_RECEIVEDINITIATE".
在主持人(第一个参与者,Firefox)上,尝试创建答案时,错误显示为“无法将远程答案设置为稳定状态”。在第二个参与者 (Chrome) 中,错误是“无法设置远程应答 sdp: Called in wrong state: STATE_INPROGRESS”。在第三个对等点上,错误是“错误是“无法设置远程应答 sdp:Called in wrong state: STATE_RECEIVEDINITIATE”。
As it turn out, the first peer failed to have video with the third peer. Other two links appear fine.
事实证明,第一个对等点未能与第三个对等点进行视频。其他两个链接显示正常。
Generally, my communication model is as below, self_id
is a unique id per each peer in the session, and locate_peer_connection()
will return the local peer_connection of the particular peer from which we receive message:
通常,我的通信模型如下所示,self_id
是会话中每个对等方的唯一 ID,locate_peer_connection()
并将返回我们从中接收消息的特定对等方的本地 peer_connection:
a new client send "peer_arrival" to the session using signalling server
all peers already in the session setlocaldescription, create offer and send to the new client
new client create answers to all other peers and setremotedescription
new client has video coming up
新客户端使用信令服务器向会话发送“peer_arrival”
会话中已经存在的所有对等点 setlocaldescription,创建报价并发送到新客户端
新客户端创建所有其他对等点的答案并设置远程描述
新客户有视频
Signalling is done using WebSocket on a node.js server.
信令是在 node.js 服务器上使用 WebSocket 完成的。
I have some of the core code below, some more note:
我在下面有一些核心代码,还有一些注意事项:
self_id is an unique id per client in a session
peer_connection stores peerConnection to other nodes, and peer_id store the respective user_id of these objects
local_stream is the local video stream from getUserMedia (already considered different browser)
self_id 是会话中每个客户端的唯一 ID
peer_connection 存储 peerConnection 到其他节点,peer_id 存储这些对象各自的 user_id
local_stream 是来自 getUserMedia 的本地视频流(已经被认为是不同的浏览器)
Any insights in to the issue? Is there something wrong with my model?
对这个问题有什么见解吗?我的模型有问题吗?
// locate a peer connection according to its id
function locate_peer_connection(id) {
var index = peer_id.indexOf(id);
// not seen before
if (index == -1) {
add_peer_connection();
peer_id.push(id);
index = peer_id.length - 1;
}
return index;
}
// add a peer connection
function add_peer_connection() {
console.log('add peer connection');
// add another peer connection for use
peer_connection.push(new rtc_peer_connection({ "iceServers": [{ "url": "stun:"+stun_server }]}));
// generic handler that sends any ice candidate to the other peer
peer_connection[peer_connection.length - 1].onicecandidate = function (ice_event) {
if (ice_event.candidate) {
signaling_server.send(
JSON.stringify({
type: "new_ice_candidate",
candidate: ice_event.candidate,
id: self_id,
token:call_token
})
);
console.log('send new ice candidate, from ' + self_id);
}
};
// display remote video streams when they arrive using local <video> MediaElement
peer_connection[peer_connection.length - 1].onaddstream = function (event) {
video_src.push(event.stream); // store this src
video_src_id.push(peer_connection.length - 1);
if (video_src.length == 1) { // first peer
connect_stream_to_src(event.stream, document.getElementById("remote_video"));
// video rotating function
setInterval(function() {
// rorating video src
var video_now = video_rotate;
if (video_rotate == video_src.length - 1) {
video_rotate = 0;
} else {
video_rotate++;
}
var status = peer_connection[video_src_id[video_rotate]].iceConnectionState;
if (status == "disconnected" || status == "closed") { // connection lost, do not show video
console.log('connection ' + video_rotate + ' liveness check failed');
} else if (video_now != video_rotate) {
connect_stream_to_src(video_src[video_rotate], document.getElementById("remote_video"));
}
}, 8000);
// hide placeholder and show remote video
console.log('first remote video');
document.getElementById("loading_state").style.display = "none";
document.getElementById("open_call_state").style.display = "block";
}
console.log('remote video');
};
peer_connection[peer_connection.length - 1].addStream(local_stream);
}
// handle new peer
function new_peer(signal) {
// locate peer connection
var id = locate_peer_connection(signal.id);
console.log('new peer ' + id);
// create offer
peer_connection[id].createOffer(function(sdp) {
peer_connection[id].setLocalDescription(sdp,
function() { // call back
console.log('set local, send offer, connection '+ id);
signaling_server.send(
JSON.stringify({
token: call_token,
id: self_id,
type:"new_offer",
sdp: sdp
})
);
}, log_error);
}, log_error);
}
// handle offer
function new_offer_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('new offer ' + id);
// set remote description
peer_connection[id].setRemoteDescription(
new rtc_session_description(signal.sdp),
function() { // call back
peer_connection[id].createAnswer(function(sdp) {
peer_connection[id].setLocalDescription(sdp, function () {
console.log('set local, send answer, connection '+ id);
signaling_server.send(
JSON.stringify({
token: call_token,
id: self_id,
type:"new_answer",
sdp: sdp
})
);
},
log_error);
}, log_error);
}, log_error);
}
// handle answer
function new_answer_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('new answer ' + id);
peer_connection[id].setRemoteDescription(new rtc_session_description(signal.sdp),
function() {
console.log('receive offer answer, set remote, connection '+ id);
}
, log_error);
}
// handle ice candidate
function ice_candidate_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('get new_ice_candidate from ' + id);
if (typeof(RTCIceCandidate) != "undefined") {
peer_connection[id].addIceCandidate(
new RTCIceCandidate(signal.candidate)
);
} else { // firefox
peer_connection[id].addIceCandidate(
new mozRTCIceCandidate(signal.candidate)
);
}
}
function event_handler(event) {
var signal = JSON.parse(event.data);
if (signal.type === "peer_arrival") {
new_peer(signal);
} else if (signal.type === "new_ice_candidate") {
ice_candidate_handler(signal);
} else if (signal.type === "new_offer") { // get peer description offer
new_offer_handler(signal);
} else if (signal.type === "new_answer") { // get peer description answer
new_answer_handler(signal);
} else if (signal.type === "new_chat_message") { // chat message and file sharing info
add_chat_message(signal);
} else if (signal.type === "new_file_thumbnail_part") { // thumbnail
store_file_part(signal.name, "thumbnail", signal.id, signal.part, signal.length, signal.data);
if (file_store[signal.id].thumbnail.parts.length == signal.length) {
document.getElementById("file_list").innerHTML = get_file_div(signal.id, signal.name)+document.getElementById("file_list").innerHTML;
document.getElementById("file-img-"+signal.id).src = file_store[signal.id].thumbnail.parts.join("");
}
} else if (signal.type === "new_file_part") { // file
console.log('get new_file_part ' + signal.id);
store_file_part(signal.name, "file", signal.id, signal.part, signal.length, signal.data);
update_file_progress(signal.name, signal.id, file_store[signal.id].file.parts.length, signal.length);
}
}
// generic error handler
function log_error(error) {
console.log(error);
}
回答by mido
I might be wrong, but these two must be your major problems:
我可能是错的,但这两个一定是你的主要问题:
signaling_server.send(...
I am not seeing any target here, so guessing that the server just broadcasts this message to everyone. When you are sending an sdp to already established peer connection, you are bound to get the error which you are getting now. My suggesting would be to add a target id in the message, either the server can forward it to that particular peer or, server can just broadcast, butevent_handler
of the peer can check if the target id of message is same as it's own id, if not, just ignore the message.onicecandidate
event, you are broadcasting the ICE candidates to all remote peers, again this is meant for single peer, another issue might be,addIceCandidate
on PeerConnection before setting it's local and remote description would throw error, you need to some sort of mechanism to handle this( add ICE candidates only after setting the connections descriptions).
signaling_server.send(...
我在这里没有看到任何目标,因此猜测服务器只是向所有人广播此消息。当您向已经建立的对等连接发送 sdp 时,您一定会收到您现在遇到的错误。我的建议是在消息中添加一个目标 id,服务器可以将它转发到那个特定的对等方,或者,服务器可以只是广播,但是event_handler
对等方可以检查消息的目标 id 是否与它自己的 id 相同,如果不,只是忽略该消息。onicecandidate
事件,您正在向所有远程对等方广播 ICE 候选对象,这同样适用于单个对等方,另一个问题可能是,addIceCandidate
在 PeerConnection 上设置本地和远程描述之前会抛出错误,您需要某种机制来处理这个(仅在设置连接描述后添加 ICE 候选)。
finally a suggestion. I am guessing peer_connection
is an Array, if you change it Object, you can remove the redundancy of locate_peer_connection
,
最后一个建议。我猜peer_connection
是一个Array,如果你把它改成Object,就可以去掉冗余locate_peer_connection
,
you can do something like.
你可以做类似的事情。
if(peer_connection[signal.id]){
//do something...
}else{
peer_connection[signal.id] = new PeerConnection(...
}
回答by Sagar
i had the same problem when i was implementing one-to-many rtc broadcast, and what mido22 said is right. you might be sending/resetting existing established peer object with other incoming client. you have to create new RTCPeerConenction object for every new incoming client. the general work flow would be as follow.
我在实现一对多 rtc 广播时遇到了同样的问题,mido22 说的是对的。您可能正在与其他传入客户端发送/重置现有的已建立对等对象。您必须为每个新传入的客户端创建新的 RTCPeerConenction 对象。一般工作流程如下。
peerconnection=[];//empty array
then you initialize your media devices with getUserMedia
and store media stream into global variable so that it can be added when creating offer.
once this is done you inform your singalling server with unique id
your signalling server may then broadcast this to all clients except from which it is received. each client will then check if that unique id does exist in there peerconnection array and like mido22 said you can do this as
然后使用 getUserMedia 初始化媒体设备并将媒体流存储到全局变量中,以便在创建报价时添加。
完成此操作后,您将使用唯一 ID 通知您
的信令服务器,然后您的信令服务器可能会将其广播给所有客户端,但从其接收到的客户端除外。然后每个客户端将检查该唯一 ID 是否确实存在于 peerconnection 数组中,就像 mido22 说的那样,您可以这样做
if(peerconnection[signal.id])
{
//do something with existing peerconnections
}
else
{
peerconnection[signal.id]=new RTCPeerConnection({"stun_server_address"});
//register peerconnection[signal.id].onicecandidate callback
//register peerconnection[signal.id].onaddstream callback
//createoffer
//if local stream is ready then
peerconnection[signal.id].addStream(localStream);
//and rest of the stuff go as it is like in one-to-one call..
//setLocalDescriptor setRemoteDescriptor
}