WebRTC 无法添加第三个对等点:"Cannot set remote answer in state stable"
WebRTC Failed to Add a Third Peer: "Cannot set remote answer in state stable"
我正在编写多点 WebRTC 视频聊天。
两个同伴连接没有问题,控制台没有错误或警告,视频效果很好,但我无法成功添加第三方到聊天中。
在主机(第一个参与者,Firefox)上,尝试创建答案时出现 "Cannot set remote answer in state stable" 错误。在第二个参与者 (Chrome) 处,错误是 "Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS"。在他的第三个对等点,错误是 "the error is "Failed to set remote answer sdp: Called in wrong state: STATE_RECEIVEDINITIATE".
事实证明,第一个点无法与第三个点视频。其他两个链接显示正常。
通常,我的通信模型如下,self_id
是会话中每个对等点的唯一 ID,locate_peer_connection()
将 return 本地 peer_connection我们从中接收消息的特定对等方:
新客户端使用信令服务器
向会话发送"peer_arrival"
已在会话中的所有对等点设置本地描述,创建报价并发送给新客户
新客户为所有其他同行创建答案并设置远程描述
新客户即将发布视频
信号是在 node.js 服务器上使用 WebSocket 完成的。
下面是我的一些核心代码,还有一些注释:
self_id 是会话中每个客户端的唯一 ID
peer_connection存储peerConnection到其他节点,peer_id存储这些对象各自的user_id
local_stream是getUserMedia的本地视频流(已经考虑不同浏览器)
对这个问题有什么见解吗?我的模型有问题吗?
// 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);
}
我可能是错的,但这两个一定是你的主要问题:
signaling_server.send(...
我在这里没有看到任何目标,所以猜测服务器只是向所有人广播了这条消息。当您向已建立的对等连接发送 sdp 时,您一定会遇到现在遇到的错误。我的建议是在消息中添加一个目标 id,或者服务器可以将它转发给那个特定的对等方,或者服务器可以只广播,但是对等方的 event_handler
可以检查消息的目标 id 是否与它有自己的 ID,如果没有,请忽略该消息。
onicecandidate
事件,您正在向所有远程对等点广播 ICE 候选人,这同样适用于单个对等点,另一个问题可能是,在设置之前在 PeerConnection 上 addIceCandidate
它的本地和远程描述会引发错误,您需要某种机制来处理这个问题(仅在设置连接描述后添加 ICE 候选人)。
终于提个建议。我猜peer_connection
是一个Array,如果改成Object,就可以去掉locate_peer_connection
、
的冗余
你可以做类似的事情。
if(peer_connection[signal.id]){
//do something...
}else{
peer_connection[signal.id] = new PeerConnection(...
}
我在实现一对多rtc广播的时候遇到了同样的问题,mido22说的是对的。您可能 sending/resetting 已与其他传入客户端建立对等对象。您必须为每个新传入的客户端创建新的 RTCPeerConenction 对象。一般的工作流程如下。
peerconnection=[];//empty array
然后您使用 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
}
我正在编写多点 WebRTC 视频聊天。 两个同伴连接没有问题,控制台没有错误或警告,视频效果很好,但我无法成功添加第三方到聊天中。
在主机(第一个参与者,Firefox)上,尝试创建答案时出现 "Cannot set remote answer in state stable" 错误。在第二个参与者 (Chrome) 处,错误是 "Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS"。在他的第三个对等点,错误是 "the error is "Failed to set remote answer sdp: Called in wrong state: STATE_RECEIVEDINITIATE".
事实证明,第一个点无法与第三个点视频。其他两个链接显示正常。
通常,我的通信模型如下,self_id
是会话中每个对等点的唯一 ID,locate_peer_connection()
将 return 本地 peer_connection我们从中接收消息的特定对等方:
新客户端使用信令服务器
向会话发送"peer_arrival"
已在会话中的所有对等点设置本地描述,创建报价并发送给新客户
新客户为所有其他同行创建答案并设置远程描述
新客户即将发布视频
信号是在 node.js 服务器上使用 WebSocket 完成的。
下面是我的一些核心代码,还有一些注释:
self_id 是会话中每个客户端的唯一 ID
peer_connection存储peerConnection到其他节点,peer_id存储这些对象各自的user_id
local_stream是getUserMedia的本地视频流(已经考虑不同浏览器)
对这个问题有什么见解吗?我的模型有问题吗?
// 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);
}
我可能是错的,但这两个一定是你的主要问题:
signaling_server.send(...
我在这里没有看到任何目标,所以猜测服务器只是向所有人广播了这条消息。当您向已建立的对等连接发送 sdp 时,您一定会遇到现在遇到的错误。我的建议是在消息中添加一个目标 id,或者服务器可以将它转发给那个特定的对等方,或者服务器可以只广播,但是对等方的event_handler
可以检查消息的目标 id 是否与它有自己的 ID,如果没有,请忽略该消息。onicecandidate
事件,您正在向所有远程对等点广播 ICE 候选人,这同样适用于单个对等点,另一个问题可能是,在设置之前在 PeerConnection 上addIceCandidate
它的本地和远程描述会引发错误,您需要某种机制来处理这个问题(仅在设置连接描述后添加 ICE 候选人)。
终于提个建议。我猜peer_connection
是一个Array,如果改成Object,就可以去掉locate_peer_connection
、
你可以做类似的事情。
if(peer_connection[signal.id]){
//do something...
}else{
peer_connection[signal.id] = new PeerConnection(...
}
我在实现一对多rtc广播的时候遇到了同样的问题,mido22说的是对的。您可能 sending/resetting 已与其他传入客户端建立对等对象。您必须为每个新传入的客户端创建新的 RTCPeerConenction 对象。一般的工作流程如下。
peerconnection=[];//empty array
然后您使用 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
}