提问人:SSpoke 提问时间:11/10/2023 最后编辑:SSpoke 更新时间:11/18/2023 访问量:35
WebRTC 问题视频流有时不会显示,除非我强制显示它
WebRTC question video streams sometimes don't show up unless I force it to show
问:
我有一个网站,我尝试使用WebRTC https://www.camspark.com/
由于某种原因,我必须没有正确实现它,它不会 100% 地显示视频流,它存在某种计时问题,任何 1 毫秒的滞后都会迫使它不显示视频流。
这是我的代码,您可以在 https://www.camspark.com/static/camspark.js 加载 javascript
var ws;
var peerConnection, localStream;
//STUN = (Session Traversal Utilities for NAT)
var rtc_server = {
iceServers: [
{urls: "stun:stun.l.google.com:19302"},
{urls: "stun:stun.services.mozilla.com"},
{urls: "stun:stun.stunprotocol.org:3478"},
{urls: "turn:numb.viagenie.ca", credential: "webrtc", username: "admin%40camspark.com"},
{url: "stun:stun.l.google.com:19302"},
{url: "stun:stun.services.mozilla.com"},
{url: "stun:stun.stunprotocol.org:3478"},
{url: "turn:numb.viagenie.ca", credential: "webrtc", username: "admin%40camspark.com"}
]
}
//offer SDP = [Session Description Protocol] tells other peers what you would like
var rtc_media_constraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}
};
var rtc_peer_options = {
optional: [
{DtlsSrtpKeyAgreement: true}, //To make Chrome and Firefox to interoperate.
]
}
var finishSDPVideoOfferOrAnswer = false;
var isOfferer = false;
var iceCandidates = [];
var PeerConnection = RTCPeerConnection || window.PeerConnection || window.webkitPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var IceCandidate = RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
function hasSupportForVideoChat() {
return window.RTCPeerConnection && window.RTCIceCandidate && window.RTCSessionDescription && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (RTCPeerConnection.prototype.addStream || RTCPeerConnection.prototype.addTrack) ? true : false;
}
function reloadCameraStream(isOfferer_) {
loadMyCameraStream();
loadStrangerCameraStream(isOfferer_);
addStatusMsg("Attempted to Reload Camera Streams.");
}
function loadMyCameraStream() {
if (getUserMedia) {
getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
function(localMediaStream) {
//Add my video
$("div#videoBox video#you")[0].muted = true;
$("div#videoBox video#you")[0].autoplay = true;
$("div#videoBox video#you").attr('playsinline', '');
$("div#videoBox video#you").attr('webkit-playsinline', '');
$("div#videoBox video#you")[0].srcObject = localMediaStream;
localStream = localMediaStream;
},
function(e) {
addStatusMsg("Your Video has error : " + e);
}
);
} else {
addStatusMsg("Your browser does not support WebRTC (Camera/Voice chat).");
return;
}
}
function loadStrangerCameraStream(isOfferer_) {
if(!hasSupportForVideoChat())
return;
//Only add pending ICE Candidates when getOffer() is finished.
finishSDPVideoOfferOrAnswer = false;
iceCandidates = []; //clear ICE Candidates array.
isOfferer = isOfferer_;
peerConnection = new PeerConnection(rtc_server, rtc_peer_options);
if (peerConnection.addTrack !== undefined)
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
else
peerConnection.addStream(localStream);
peerConnection.onicecandidate = function(e) {
if (!peerConnection || !e || !e.candidate)
return;
ws.send(JSON.stringify(['ice_candidate', {"candidate": e.candidate}]));
};
if (peerConnection.addTrack !== undefined) {
//newer technology
peerConnection.ontrack = function(e) {
//e.streams.forEach(stream => doAddStream(stream));
//addStatusMsg("ontrack called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.streams[0];
$("div#videoBox video#stranger")[0].autoplay = true;
};
} else {
//older technology
peerConnection.onaddstream = function(e) {
//addStatusMsg("onaddstream called");
//Add stranger video
$("div#videoBox video#stranger").attr('playsinline', '');
$("div#videoBox video#stranger").attr('webkit-playsinline', '');
$('div#videoBox video#stranger')[0].srcObject = e.stream;
$("div#videoBox video#stranger")[0].autoplay = true;
};
}
if(isOfferer) {
peerConnection.createOffer(
function(offer) {
peerConnection.setLocalDescription(offer, function () {
//both offer and peerConnection.localDescription are the same.
//addStatusMsg('createOffer, localDescription: ' + JSON.stringify(peerConnection.localDescription));
//addStatusMsg('createOffer, offer: ' + JSON.stringify(offer));
ws.send(JSON.stringify(['send_offer', {"offer": peerConnection.localDescription}]));
},
function(e) {
addStatusMsg('createOffer, set description error' + e);
});
},
function(e) {
addStatusMsg("createOffer error: " + e);
},
rtc_media_constraints
);
}
}
function closeStrangerCameraStream() {
$('div#videoBox video#stranger')[0].srcObject = null
if(peerConnection)
peerConnection.close();
}
function iceCandidate(candidate) {
//ICE = Interactive Connectivity Establishment
if(!finishSDPVideoOfferOrAnswer) {
iceCandidates.push(candidate);
//addStatusMsg("Queued iceCandidate");
return;
}
if(!peerConnection) {
addStatusMsg("iceCandidate peerConnection not created error.");
return;
}
peerConnection.addIceCandidate(new IceCandidate(candidate));
//addStatusMsg("Added on time, Peer Ice Candidate = " + JSON.stringify(candidate));
}
function getAnswer(answer) {
if(!hasSupportForVideoChat())
return;
if(!peerConnection) {
addStatusMsg("getAnswer peerConnection not created error.");
return;
}
peerConnection.setRemoteDescription(new SessionDescription(answer), function() {
//addStatusMsg("getAnswer SessionDescription answer is ok");
finishSDPVideoOfferOrAnswer = true;
while (iceCandidates.length) {
var candidate = iceCandidates.shift();
try {
peerConnection.addIceCandidate(new IceCandidate(candidate));
//addStatusMsg("Adding queued ICE Candidates");
} catch(e) {
addStatusMsg("Error adding queued ICE Candidates error:" + e);
}
}
iceCandidates = [];
},
function(e) {
addStatusMsg("getAnswer SessionDescription fail error: " + e);
});
}
function getOffer(offer) {
if(!hasSupportForVideoChat())
return;
if(!peerConnection) {
addStatusMsg("getOffer peerConnection not created error.");
return;
}
//addStatusMsg("getOffer setRemoteDescription offer: " + JSON.stringify(offer));
peerConnection.setRemoteDescription(new SessionDescription(offer), function() {
finishSDPVideoOfferOrAnswer = true;
while (iceCandidates.length) {
var candidate = iceCandidates.shift();
try {
peerConnection.addIceCandidate(new IceCandidate(candidate));
//addStatusMsg("Adding queued ICE Candidates");
} catch(e) {
addStatusMsg("Error adding queued ICE Candidates error:" + e);
}
}
iceCandidates = [];
if(!isOfferer) {
peerConnection.createAnswer(
function(answer) {
peerConnection.setLocalDescription(answer);
//addStatusMsg("getOffer create answer sent: " + JSON.stringify(answer));
ws.send(JSON.stringify(['send_answer', {"answer": answer}]));
},
function(e) {
addStatusMsg("getOffer setRemoteDescription create answer fail: " + e);
}
);
}
});
}
对于服务器端,我使用 java 服务器,它在 Java 中具有此代码
you.videoChatOfferer = true;
stranger.videoChatOfferer = false;
JSONObject obj = new JSONObject();
JSONArray list = new JSONArray();
list.put("connected");
obj.put("videoChatOfferer", you.videoChatOfferer); //first guy offerer for WebRTC.
list.put(obj);
server.sendMessage(websocket, list.toString()); //connected to chat partner
obj = new JSONObject();
list = new JSONArray();
list.put("connected");
obj.put("videoChatOfferer", stranger.videoChatOfferer); //second guy isn't offerer.
list.put(obj);
server.sendMessage(stranger.getWebSocket(), list.toString()); //connected to chat partner
这是您在聊天中键入的强制命令,它强制视频流刷新并在 90% 的时间内运行良好。
} else if(newMessage.toLowerCase().startsWith("/forcevid")) {
UserProfile stranger = you.stranger;
if(stranger == null) {
obj = new JSONObject();
list = new JSONArray();
list.put("gotMessage");
obj.put("msg", "Couldn't force the video streams as stranger is glitched up");
list.put(obj);
sendMessage(websocket, list.toString()); //send message to you
return;
}
if(you.videoChatOfferer) {
you.videoChatOfferer = false;
stranger.videoChatOfferer = true;
} else if(stranger.videoChatOfferer) {
stranger.videoChatOfferer = false;
you.videoChatOfferer = true;
}
obj = new JSONObject();
list = new JSONArray();
list.put("reloadCameraStream");
obj.put("videoChatOfferer", you.videoChatOfferer); //first guy offerer for WebRTC.
list.put(obj);
sendMessage(websocket, list.toString()); //connected to chat partner
obj = new JSONObject();
list = new JSONArray();
list.put("reloadCameraStream");
obj.put("videoChatOfferer", stranger.videoChatOfferer); //second guy isn't offerer.
list.put(obj);
sendMessage(stranger.getWebSocket(), list.toString()); //connected to chat partner
}
我的数据包处理程序也有这个 ECHO 命令用于应答/提供
public void processPackets(WebSocket websocket, String message) {
UserProfile you = clients.get(websocket);
if(you == null) //strange why this happens, but o well.
return;
JSONArray packet = new JSONArray(new JSONTokener(message));
String opcode = (String) packet.get(0);
opcode = opcode.toLowerCase().trim();
//System.out.println("PACKET OpCode = " + opcode);
JSONObject obj = new JSONObject();
JSONArray list = new JSONArray();
switch(opcode) {
case "ice_candidate":
JSONObject candidatePackage = (JSONObject) packet.get(1);
JSONObject candidate = (JSONObject) candidatePackage.get("candidate");
obj = new JSONObject();
list = new JSONArray();
list.put("iceCandidate");
obj.put("candidate", candidate);
list.put(obj);
writeStranger(you, list.toString()); //send ice candidate to stranger
break;
case "send_answer":
JSONObject sendAnswerPackage = (JSONObject) packet.get(1);
JSONObject answer = (JSONObject) sendAnswerPackage.get("answer");
obj = new JSONObject();
list = new JSONArray();
list.put("getAnswer");
obj.put("answer", answer);
list.put(obj);
writeStranger(you, list.toString()); //send answer to stranger
break;
case "send_offer":
JSONObject offerPackage = (JSONObject) packet.get(1);
JSONObject offer = (JSONObject) offerPackage.get("offer");
obj = new JSONObject();
list = new JSONArray();
list.put("getOffer");
obj.put("offer", offer);
list.put(obj);
writeStranger(you, list.toString()); //send ice candidate to stranger
break;
答:
0赞
Kemal Kaplan
11/18/2023
#1
这种竞争条件的主要原因似乎是 loadMyCameraStream 和 loadStrangerCameraStream 之间缺少序列依赖关系。您可以尝试在 loadMyCameraStream 中调用 loadStrangerCameraStream ;
getUserMedia.call(navigator, { video: {facingMode: "user", aspectRatio: 4 / 3/*height: 272, width: 322*/}, audio: { echoCancellation : true } },
function(localMediaStream) {
//Add my video
$("div#videoBox video#you")[0].muted = true;
$("div#videoBox video#you")[0].autoplay = true;
$("div#videoBox video#you").attr('playsinline', '');
$("div#videoBox video#you").attr('webkit-playsinline', '');
$("div#videoBox video#you")[0].srcObject = localMediaStream;
localStream = localMediaStream;
loadStrangerCameraStream(isOfferer_); // <-- from here
},
对于与您的流程相似的简约工作示例,您可以查看以下代码;
https://github.com/kaplanke/tamed-chat-client/blob/master/test/standalone_client.html
评论