I am trying to create a simple webpage using WebRTC DataChannels that sends pings/pongs between browsers.
When Chrome initiates the connection and then Chrome connects, it works .
When Firefox initiates the connection and then Firefox connects, it works .
When Chrome initiates the connection and then Firefox connects, it works .
But when Firefox initiates the connection and then Chrome connects, it doesn't work . Chrome never receives data sent by Firefox.
I'm using Firefox 26 and Chromium 32, on Archlinux.
Here is my JavaScript code:
<!DOCTYPE html>
<html>
<head>
<title>WebRTC test</title>
<meta charset="utf-8">
</head>
<body>
<button id="create" disabled>Create data channel</button>
<script type="text/javascript">
// DOM
var create = document.getElementById('create');
// Compatibility
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
// Create a WebRTC object
var rtc = new RTCPeerConnection(null);
// Create a data channel
var sendChannel = rtc.createDataChannel('pingtest', {reliable: false});
var myMsg = 'ping';
function setRecvChannel(recvChannel) {
recvChannel.onmessage = function(event) {
if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
console.log('-> ' + window.btoa(event.data));
return; // Received channel's name, ignore
}
console.log('-> ' + event.data);
window.setTimeout(function() {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}, 500);
};
}
// Chrome and Firefox
sendChannel.onopen = function(event) {
setRecvChannel(sendChannel);
if(myMsg === 'ping') {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}
};
// Firefox
rtc.ondatachannel = function(event) {
setRecvChannel(event.channel);
};
// ICE
rtc.onicecandidate = function(event) {
if(event.candidate) {
console.log('<- ' + JSON.stringify(event.candidate));
ws.send(JSON.stringify(event.candidate));
}
};
// Signaling channel
var ws = new WebSocket('ws://127.0.0.1:49300/');
ws.onopen = function() {
create.disabled = false;
};
ws.onmessage = function(event) {
console.log('-> ' + event.data);
var data = JSON.parse(event.data);
if(data.sdp) {
rtc.setRemoteDescription(new RTCSessionDescription(data));
if(data.type === 'offer') {
myMsg = 'pong';
rtc.createAnswer(function(anwser) {
rtc.setLocalDescription(anwser, function () {
console.log('<- ' + JSON.stringify(anwser));
ws.send(JSON.stringify(anwser));
});
}, console.error);
}
}
else {
rtc.addIceCandidate(new RTCIceCandidate(data));
}
};
ws.onclose = function() {
create.disabled = true;
};
// Create an offer
create.onclick = function() {
rtc.createOffer(function(offer) {
rtc.setLocalDescription(offer, function () {
offer.sdp = offer.sdp;
console.log(offer.sdp);
console.log('<- ' + JSON.stringify(offer));
ws.send(JSON.stringify(offer));
});
}, console.error);
};
</script>
</body>
</html>
Here is the WebSocket-based signaling server I've created only for test purposes, it simply listens on port 49300 and broadcasts data received from clients to other clients:
#!/usr/bin/python
#-*- encoding: Utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from string import printable
from threading import Thread
from base64 import b64encode
from struct import unpack
from hashlib import sha1
PORT = 49300
activeSocks = []
def SignalingChannel(ip, port, sock):
print 'Connection from %s:%s' % (ip, port)
# Handling the HTTP request
try:
headers = sock.recv(8184)
assert headers.upper().startswith('GET')
assert headers.endswith('\r\n\r\n')
data = headers.strip().replace('\r', '').split('\n')[1:]
headers = {}
for header in data:
name, value = header.split(':', 1)
headers[name.strip().lower()] = value.strip()
assert headers['host']
assert 'upgrade' in headers['connection'].lower()
assert 'websocket' in headers['upgrade'].lower()
assert headers['sec-websocket-version'] == '13'
assert len(headers['sec-websocket-key']) == 24
guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
accept = b64encode(sha1(headers['sec-websocket-key'] + guid).digest())
sock.send('HTTP/1.1 101 Switching Protocols\r\n' +
'Connection: Upgrade\r\n' +
'Upgrade: websocket\r\n' +
'Sec-WebSocket-Accept: %s\r\n' % accept +
'\r\n')
except:
try:
msg = 'This is a RFC 6455 WebSocket server.\n'
sock.send('HTTP/1.1 400 Bad Request\r\n' +
'Connection: Close\r\n' +
'Content-Length: %d\r\n' % len(msg) +
'Content-Type: text/plain; charset=us-ascii\r\n' +
'Sec-WebSocket-Version: 13\r\n' +
'\r\n' +
msg)
except:
pass
sock.close()
print 'Disconnection from %s:%s' % (ip, port)
return
activeSocks.append(sock)
try:
data = sock.recv(2)
while len(data) == 2:
frame = data[0] + chr(ord(data[1]) & 0b01111111)
opcode = ord(data[0]) & 0b00001111
mask = ord(data[1]) & 0b10000000
paylen = ord(data[1]) & 0b01111111
if paylen == 126:
data = sock.recv(2)
frame += data
paylen = unpack('>H', data)[0]
elif paylen == 127:
data = sock.recv(8)
frame += data
paylen = unpack('>Q', data)[0]
if mask:
mask = sock.recv(4)
data = ''
received = True
while received and len(data) < paylen:
received = sock.recv(paylen - len(data))
data += received
if mask:
unmasked = ''
for i in xrange(len(data)):
unmasked += chr(ord(data[i]) ^ ord(mask[i % 4]))
else:
unmasked = data
frame += unmasked
if opcode != 8:
print '-- From port %d --' % port
if all(ord(c) < 127 and c in printable for c in unmasked):
print unmasked
else:
print repr(unmasked)
for destSock in activeSocks:
if destSock != sock:
destSock.send(frame)
else:
break
data = sock.recv(2)
except:
pass
activeSocks.remove(sock)
sock.close()
print 'Disconnection from %s:%s' % (ip, port)
listenSock = socket(AF_INET, SOCK_STREAM)
listenSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
listenSock.bind(('0.0.0.0', PORT))
listenSock.listen(20)
print 'Listening on port 49300...'
while True:
clientSock, (ip, port) = listenSock.accept()
Thread(target=SignalingChannel, args=(ip, port, clientSock)).start()
To run the code, launch the signaling server, open the webpage in two browser tabs, click the "Create data channel" button and look at the web console.
Any idea?
Looking through the Chrome/Firefox bug trackers it looks like this issue has been identified and resolved, but only in Chrome Canary 33.0.1715.0 or higher.
If you're unwilling to require the Chrome Canary build mentioned above, you can detect the bad peer combination, and have your 'offer' button signal the other client to make an offer.
Pseudocode:
socket.onMessage(msg) {
if(msg == "request-offer"){
doOffer();
}
...
}
createDataChannelButton.onClick() {
if(!canCreateChannelBasedOnBrowser){
socket.send("request-offer");
}
else {
doOffer();
}
}
With your example code:
<!DOCTYPE html>
<html>
<head>
<title>WebRTC test</title>
<meta charset="utf-8">
</head>
<body>
<button id="create" disabled>Create data channel</button>
<script type="text/javascript">
// DOM
// CHANGE: Add basic browser detection based on google's adapter.js file.
var rtcBrowserVersion = 0;
var rtcCanInitiateDataOffer = false;
if (navigator.mozGetUserMedia) {
rtcBrowserVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
rtcCanInitiateDataOffer = true;
} else if (navigator.webkitGetUserMedia) {
// Chrome Canary reports major version 35 for me. Can't find a reliable resource to confirm
// canary versions.
rtcBrowserVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
rtcCanInitiateDataOffer = rtcBrowserVersion >= 35;
}
var create = document.getElementById('create');
// Compatibility
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
// Create a WebRTC object
var rtc = new RTCPeerConnection(null);
// Create a data channel
var sendChannel = rtc.createDataChannel('pingtest', {reliable: false});
var myMsg = 'ping';
function setRecvChannel(recvChannel) {
recvChannel.onmessage = function(event) {
if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
console.log('-> ' + window.btoa(event.data));
return; // Received channel's name, ignore
}
console.log('-> ' + event.data);
window.setTimeout(function() {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}, 500);
};
}
// Chrome and Firefox
sendChannel.onopen = function(event) {
setRecvChannel(sendChannel);
if(myMsg === 'ping') {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}
};
// Firefox
rtc.ondatachannel = function(event) {
setRecvChannel(event.channel);
};
// ICE
rtc.onicecandidate = function(event) {
if(event.candidate) {
console.log('<- ' + JSON.stringify(event.candidate));
ws.send(JSON.stringify(event.candidate));
}
};
// Signaling channel
var ws = new WebSocket('ws://127.0.0.1:49300/');
ws.onopen = function() {
create.disabled = false;
};
ws.onmessage = function(event) {
console.log('-> ' + event.data);
var data = JSON.parse(event.data);
if(data.sdp) {
rtc.setRemoteDescription(new RTCSessionDescription(data));
if(data.type === 'offer') {
myMsg = 'pong';
rtc.createAnswer(function(anwser) {
rtc.setLocalDescription(anwser, function () {
console.log('<- ' + JSON.stringify(anwser));
ws.send(JSON.stringify(anwser));
});
}, console.error);
}
}
// CHANGE: Chrome with offer bug asked to initiate the offer.
else if(data.initiate === true){
doOffer();
}
else {
rtc.addIceCandidate(new RTCIceCandidate(data));
}
};
ws.onclose = function() {
create.disabled = true;
};
// Create an offer
// CHANGE: Create function for offer, so that it may be called from ws.onmessage
function doOffer(){
rtc.createOffer(function(offer) {
rtc.setLocalDescription(offer, function () {
offer.sdp = offer.sdp;
console.log(offer.sdp);
console.log('<- ' + JSON.stringify(offer));
ws.send(JSON.stringify(offer));
});
}, console.error);
}
create.onclick = function() {
// CHANGE: If this client is not able to negotiate a data channel, send a
// message to the peer asking them to offer the channel.
if(rtcCanInitiateDataOffer){
doOffer();
}
else {
ws.send(JSON.stringify({initiate:true}));
}
};
Related Issues:
It is also worth noting that there is currently a max message buffer size of 16KB when sending data to a Chrome peer. If you are sending large data messages, you will need to break them into 16KB chunks before transmitting them over the data channel.
FYI, I revised the HTML further to try and troubleshoot a problem I'm having talking to the C++ libjingle (neither Chrome, nor Firefox) ;)
This version shows on-page status. Not pretty, but useful.
<!DOCTYPE html>
<html>
<head>
<title>WebRTC test</title>
<meta charset="utf-8">
</head>
<body>
<button id="create" disabled>Create data channel</button>
<div id="output1">output1</div>
<div id="output2">output2</div>
<div id="output3">output3</div>
<div id="output4">output4</div>
<div id="output5">output5</div>
<div id="output6">output6</div>
<div id="output7">output7</div>
<div id="output8">output8</div>
<script type="text/javascript">
var myCounts = {
output1: 0,
output2: 0
};
var server1 = 'ws://127.0.0.1:49300/';
// CHANGE: Add basic browser detection based on google's adapter.js file.
var rtcBrowserVersion = 0;
var rtcCanInitiateDataOffer = false;
if (navigator.mozGetUserMedia) {
rtcBrowserVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
rtcCanInitiateDataOffer = true;
} else if (navigator.webkitGetUserMedia) {
// Chrome Canary reports major version 35 for me. Can't find a reliable resource to confirm
// canary versions.
rtcBrowserVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
rtcCanInitiateDataOffer = rtcBrowserVersion >= 35;
}
// DOM
var create = document.getElementById('create');
// Compatibility
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
// Create a WebRTC object
var rtc = new RTCPeerConnection(null);
// Create a data channel
var sendChannel = rtc.createDataChannel('PingTest', {reliable: false});
var myMsg = 'ping';
function setRecvChannel(recvChannel) {
recvChannel.onmessage = function(event) {
myCounts.output1++;
document.getElementById("output1").innerHTML = myCounts.output1 + ": " + event.data;
if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
console.log('-> ' + window.btoa(event.data));
return; // Received channel's name, ignore
};
console.log('-> ' + event.data);
window.setTimeout(function() {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}, 500);
};
}
// Chrome and Firefox
sendChannel.onopen = function(event) {
setRecvChannel(sendChannel);
if(myMsg === 'ping') {
console.log('<- ' + myMsg);
sendChannel.send(myMsg);
}
};
// Firefox
rtc.ondatachannel = function(event) {
myCounts.output2++;
document.getElementById("output2").innerHTML = myCounts.output2 + " channel: " + event.channel.label;
setRecvChannel(event.channel);
};
// ICE
rtc.onicecandidate = function(event) {
if(event.candidate) {
console.log('<- ' + JSON.stringify(event.candidate));
ws.send(JSON.stringify(event.candidate));
}
};
// Signaling channel
var ws = new WebSocket(server1);
document.getElementById("output3").innerHTML="created WebSocket (not connected) " + server1;
ws.onopen = function() {
create.disabled = false;
document.getElementById("output3").innerHTML="onOpen WebSocket " + server1;
};
ws.onmessage = function(event) {
document.getElementById("output3").innerHTML="onMessage WebSocket " + event.data;
console.log('-> ' + event.data);
var data = JSON.parse(event.data);
if (data.sdp) {
rtc.setRemoteDescription(new RTCSessionDescription(data));
if (data.type === 'offer') {
document.getElementById("output4").innerHTML="received SessiionDescription offer";
myMsg = 'pong';
rtc.createAnswer(function(anwser) {
rtc.setLocalDescription(anwser, function () {
console.log('<- ' + JSON.stringify(anwser));
ws.send(JSON.stringify(anwser));
});
}, console.error);
}
else if (data.type == 'answer') {
document.getElementById("output6").innerHTML="received SessiionDescription answer";
};
}
// CHANGE: Chrome with offer bug asked to initiate the offer.
else if (data.reverseInitiate === true) {
document.getElementById("output8").innerHTML="was asked to reverseInitiate, I doOffer";
doOffer();
}
else {
rtc.addIceCandidate(new RTCIceCandidate(data));
}
};
ws.onclose = function() {
create.disabled = true;
};
// Create an offer
// CHANGE: Create function for offer, so that it may be called from ws.onmessage
function doOffer(){
rtc.createOffer(function(offer) {
rtc.setLocalDescription(offer, function () {
offer.sdp = offer.sdp;
console.log(offer.sdp);
console.log('<- ' + JSON.stringify(offer));
ws.send(JSON.stringify(offer));
});
}, console.error);
}
create.onclick = function() {
// CHANGE: If this client is not able to negotiate a data channel, send a
// message to the peer asking them to offer the channel.
if (rtcCanInitiateDataOffer){
doOffer();
}
else {
document.getElementById("output7").innerHTML="sending reverseInitiate";
ws.send(JSON.stringify( {reverseInitiate:true} ));
};
};
</script>
</body>
</html>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.