- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我在 java 和 websocket 中有信令服务器。它适用于本地视频。但远程视频是黑屏或空白但它并不总是一片空白。如果您关闭服务器并再次打开它,远程视频将显示在您的 Remote 上。为什么有时出不来,有时出不来?
这是我的代码...
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition
|| window.msSpeechRecognition || window.oSpeechRecognition;
var localVideoStream = null;
var peerConn = null,
wsc = new WebSocket("ws://localhost:8080/signaling"),
peerConnCfg = {
'iceServers': [{
'url': 'stun:stun.l.google.com:19302'
}]
};
var videoCallButton = document.getElementById("caller");
var endCallButton = document.getElementById("callee");
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
videoCallButton.addEventListener("click", initiateCall);
endCallButton.addEventListener("click", function (evt) {
wsc.send(JSON.stringify({"closeConnection": true }));
});
var sdpConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
};
function prepareCall() {
peerConn = new RTCPeerConnection(peerConnCfg);
// send any ice candidates to the other peer
peerConn.onicecandidate = onIceCandidateHandler;
// once remote stream arrives, show it in the remote video element
peerConn.onaddstream = onAddStreamHandler;
};
// run start(true) to initiate a call
function initiateCall() {
prepareCall();
// get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
localVideoStream = stream;
localVideo.src = URL.createObjectURL(localVideoStream);
peerConn.addStream(localVideoStream);
createAndSendOffer();
}, function(error) { console.log(error);});
};
function answerCall() {
prepareCall();
// get the local stream, show it in the local video element and send it
navigator.getUserMedia({ "audio": true, "video": true }, function (stream) {
localVideoStream = stream;
localVideo.src = URL.createObjectURL(localVideoStream);
peerConn.addStream(localVideoStream);
createAndSendAnswer();
}, function(error) { console.log(error);});
};
wsc.onmessage = function (evt) {
var signal = null;
if (!peerConn) answerCall();
signal = JSON.parse(evt.data);
if (signal.sdp) {
console.log("Received SDP from remote peer.");
console.log("signal"+ signal);
peerConn.setRemoteDescription(new RTCSessionDescription(signal.sdp));
}
else if (signal.candidate) {
console.log("signal"+ signal.candidate);
console.log("Received ICECandidate from remote peer.");
peerConn.addIceCandidate(new RTCIceCandidate(signal.candidate));
} else if ( signal.closeConnection){
console.log("Received 'close call' signal from remote peer.");
endCall();
}else{
console.log("signal"+ signal.candidate);
}
};
function createAndSendOffer() {
peerConn.createOffer(
function (offer) {
var off = new RTCSessionDescription(offer);
peerConn.setLocalDescription(new RTCSessionDescription(off),
function() {
wsc.send(JSON.stringify({"sdp": off }));
},
function(error) { console.log(error);}
);
},
function (error) { console.log(error);}
);
};
function createAndSendAnswer() {
peerConn.createAnswer(
function (answer) {
var ans = new RTCSessionDescription(answer);
peerConn.setLocalDescription(ans, function() {
wsc.send(JSON.stringify({"sdp": ans }));
},
function (error) { console.log(error);}
);
},
function (error) {console.log(error);}
);
};
function onIceCandidateHandler(evt) {
if (!evt || !evt.candidate) return;
wsc.send(JSON.stringify({"candidate": evt.candidate }));
};
function onAddStreamHandler(evt) {
videoCallButton.setAttribute("disabled", true);
endCallButton.removeAttribute("disabled");
// set remote video stream as source for remote video HTML5 element
remoteVideo.src = window.URL.createObjectURL(evt.stream);
remoteVideo.play();
console.log("remote src : "+ remoteVideo.src);
};
function endCall() {
peerConn.close();
peerConn = null;
videoCallButton.removeAttribute("disabled");
endCallButton.setAttribute("disabled", true);
if (localVideoStream) {
localVideoStream.getTracks().forEach(function (track) {
track.stop();
});
localVideo.src = "";
}
if (remoteVideo){
remoteVideo.src = "";
window.URL.revokeObjectURL(remoteVideo);
}
};
最佳答案
WebRTC 空白/空视频的原因之一是丢包率高。在这种情况下,在服务器和客户端日志中,它将显示为连接成功且视频正常播放,因此您不会看到任何警告或错误。
要检查是否存在高丢包,您可以在 firefox 上访问 about:webrtc
,或在 chrome 上访问 chrome://webrtc-internals
。对于 Firefox,您可以导航到“RTP Stats”。您会看到它显示 Received: ... packets
和 Lost: ... packets
。您可以使用这些计数来计算丢包率。对于 chrome,有一个丢包率图表。您可能会有非常高的丢包率,例如 70%。
如果丢包率极高,原因之一是 MTU 较小 https://en.wikipedia.org/wiki/Maximum_transmission_unit在客户端网络接口(interface)上比服务器使用的 MTU。例如,您的客户端网络接口(interface)在未连接到 VPN 时可以具有 MTU=1500 字节,而在连接到 VPN 时 MTU=1250 字节。如果服务器发送 MTU=1400 的 RTP 数据包(通过 UDP),如果客户端未使用 VPN,则客户端可以接收到该数据包,但大于 1250 字节的数据包将被客户端网络接口(interface)丢弃。
如果你想在本地检查客户端MTU,你可以在mac或linux上运行ifconfig
。
Mac 示例,没有示例 vpn:
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
...
inet SOME_IP netmask 0xffffff00 broadcast 192.168.1.255
media: autoselect
status: active
Mac 示例,以 vpn 为例:
utun2: flags=80d1<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1250
inet SOME_IP --> SOME_IP netmask 0xffffffff
nd6 options=201<PERFORMNUD,DAD>
如何为服务器配置MTU:
如果您将 GStreamer 用于 WebRTC 服务器,则负载生成器元素(例如 rtpvp8pay
)具有设置所需 MTU 值的属性。通过使用 gst-inspect-1.0 rtpvp8pay
,您可以看到它默认使用 1400 作为 MTU,这可能比您的客户端网络接口(interface)可以处理的更大(例如,上例中的 1250)。您可以通过在 GStreamer 管道上设置较低的 MTU 来使其工作,这样您的客户端网络接口(interface)就不会再丢弃大部分包(只需更改服务器上 GStreamer 管道上的 MTU,包丢失率就可以降低到 0.01%)。
在这种情况下,当 VPN 刚重新连接时,传入视频可以工作约 10 秒,然后传入视频可能会卡住,随后的页面刷新可能会导致只有空白视频,且数据包丢失超过 70%。
这是一个非常具体的场景,但当它发生时,它是一个完全无声/隐藏的错误,因此希望这对某人有所帮助。
关于webrtc - 远程视频在 WebRTC 中黑屏或空白,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42720530/
我需要将文本放在 中在一个 Div 中,在另一个 Div 中,在另一个 Div 中。所以这是它的样子: #document Change PIN
奇怪的事情发生了。 我有一个基本的 html 代码。 html,头部, body 。(因为我收到了一些反对票,这里是完整的代码) 这是我的CSS: html { backgroun
我正在尝试将 Assets 中的一组图像加载到 UICollectionview 中存在的 ImageView 中,但每当我运行应用程序时它都会显示错误。而且也没有显示图像。 我在ViewDidLoa
我需要根据带参数的 perl 脚本的输出更改一些环境变量。在 tcsh 中,我可以使用别名命令来评估 perl 脚本的输出。 tcsh: alias setsdk 'eval `/localhome/
我使用 Windows 身份验证创建了一个新的 Blazor(服务器端)应用程序,并使用 IIS Express 运行它。它将显示一条消息“Hello Domain\User!”来自右上方的以下 Ra
这是我的方法 void login(Event event);我想知道 Kotlin 中应该如何 最佳答案 在 Kotlin 中通配符运算符是 * 。它指示编译器它是未知的,但一旦知道,就不会有其他类
看下面的代码 for story in book if story.title.length < 140 - var story
我正在尝试用 C 语言学习字符串处理。我写了一个程序,它存储了一些音乐轨道,并帮助用户检查他/她想到的歌曲是否存在于存储的轨道中。这是通过要求用户输入一串字符来完成的。然后程序使用 strstr()
我正在学习 sscanf 并遇到如下格式字符串: sscanf("%[^:]:%[^*=]%*[*=]%n",a,b,&c); 我理解 %[^:] 部分意味着扫描直到遇到 ':' 并将其分配给 a。:
def char_check(x,y): if (str(x) in y or x.find(y) > -1) or (str(y) in x or y.find(x) > -1):
我有一种情况,我想将文本文件中的现有行包含到一个新 block 中。 line 1 line 2 line in block line 3 line 4 应该变成 line 1 line 2 line
我有一个新项目,我正在尝试设置 Django 调试工具栏。首先,我尝试了快速设置,它只涉及将 'debug_toolbar' 添加到我的已安装应用程序列表中。有了这个,当我转到我的根 URL 时,调试
在 Matlab 中,如果我有一个函数 f,例如签名是 f(a,b,c),我可以创建一个只有一个变量 b 的函数,它将使用固定的 a=a1 和 c=c1 调用 f: g = @(b) f(a1, b,
我不明白为什么 ForEach 中的元素之间有多余的垂直间距在 VStack 里面在 ScrollView 里面使用 GeometryReader 时渲染自定义水平分隔线。 Scrol
我想知道,是否有关于何时使用 session 和 cookie 的指南或最佳实践? 什么应该和什么不应该存储在其中?谢谢! 最佳答案 这些文档很好地了解了 session cookie 的安全问题以及
我在 scipy/numpy 中有一个 Nx3 矩阵,我想用它制作一个 3 维条形图,其中 X 轴和 Y 轴由矩阵的第一列和第二列的值、高度确定每个条形的 是矩阵中的第三列,条形的数量由 N 确定。
假设我用两种不同的方式初始化信号量 sem_init(&randomsem,0,1) sem_init(&randomsem,0,0) 现在, sem_wait(&randomsem) 在这两种情况下
我怀疑该值如何存储在“WORD”中,因为 PStr 包含实际输出。? 既然Pstr中存储的是小写到大写的字母,那么在printf中如何将其给出为“WORD”。有人可以吗?解释一下? #include
我有一个 3x3 数组: var my_array = [[0,1,2], [3,4,5], [6,7,8]]; 并想获得它的第一个 2
我意识到您可以使用如下方式轻松检查焦点: var hasFocus = true; $(window).blur(function(){ hasFocus = false; }); $(win
我是一名优秀的程序员,十分优秀!