I want to create simple video chat app. I use WebRTC and WebSockets with Node.js. I want to connect two devicaes in the same network but iceConnectionState stay on 'checking' state ale the time. What could be the problem?
EDITED
server.js
//var uuid = require('uuid');
var fs = require('fs');
var webSocketServer = require('websocket').server;
var cfg = {
ssl: false,
port: 15555,
ssl_key: '/etc/apache2/ssl/apache.key',
ssl_cert: '/etc/apache2/ssl/apache.crt'
};
var httpServer = (cfg.ssl) ? require('https') : require('http');
var server = null;
if(cfg.ssl) {
server = httpServer.createServer(
{
key: fs.readFileSync(cfg.ssl_key),
cert: fs.readFileSync(cfg.ssl_cert)
}, function() {} ).listen(cfg.port);
} else {
server = httpServer.createServer().listen(cfg.port);
}
var wsServer = new webSocketServer({
httpServer: server
});
rooms = {};
// this is executed each time the websocket
// server receives an request
wsServer.on('request', function(request) {
// allow all incoming connections
var connection = request.accept(null, request.origin);
// here we read the incoming messages and try to parse them to JSON
connection.on('message', function(message) {
// try to parse JSON
try {
var data = JSON.parse(message.utf8Data);
} catch (e) {
console.log('This does not look like valid JSON');
}
// if JSON is valid process the request
if (data !== undefined && data.type !== undefined) {
switch (data.type) {
case 'createRoom':
var roomName = data.roomName;
console.log('CREATE_ROOM request received');
rooms[roomName] = {
creatorConnection: connection,
partnerConnection: false,
}
var data = {
type: 'roomCreated',
payload: roomName
};
return send(rooms[roomName].creatorConnection, data);
break;
case 'offer':
console.log('OFFER received from client');
if (rooms[data.roomName].partnerConnection) {
// send error to user
var data = {
type: 'error',
payload: 'room is already full'
};
return send(connection, data);
}
rooms[data.roomName].partnerConnection = this;
console.log('OFFER send to host');
return send(rooms[data.roomName].creatorConnection, data);
break;
// send to other guy
default:
if (this === rooms[data.roomName].partnerConnection) {
console.log('send to creator : ' + data.type);
return send(rooms[data.roomName].creatorConnection, data);
}
console.log('send to parther : ' + data.type);
return send(rooms[data.roomName].partnerConnection, data);
break;
}
}
// if JSON is invalid or type is missing send error
else {
var data = {
type: 'error',
payload: 'ERROR FROM SERVER: Incorrect data or no data received'
};
send(connection, data);
}
});
// this function sends data to the other user
var send = function(connection, data) {
try {
connection.sendUTF(JSON.stringify(data));
} catch (e) {
console.log('\n\n!!!### ERROR while sending message ###!!!\n');
console.log(e + '\n');
return;
}
};
});
client.js
function WebRTC() {
var wsServer = false;
var localStream = false;
var remoteStream = false;
var peerConnection = false;
var roomName = null;
var otherSDP = false;
var othersCandidates = []; // other guy's icecandidates
var socketEvent = document.createEvent('Event');
socketEvent.initEvent('socketEvent', true, true);
var mediaConstraints = {
audio: true,
video: true
};
var peerConnectionConfig = {
iceServers: [
{url : 'stun:stun.l.google.com:19302'},
{url : 'stun:stun.anyfirewall.com:3478'},
{url : 'turn:turn.bistri.com:80',
credential: 'homeo',
username: 'homeo'},
{url : 'turn:turn.anyfirewall.com:443?transport=tcp',
credential: 'webrtc',
username: 'webrtc'}
]
};
var offerAnswerConstraints = { mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}};
var sendToServer = function(data) {
try {
wsServer.send(JSON.stringify(data));
return true;
} catch (e) {
logError(e);
return false;
}
};
var createRTCIceCandidate = function(candidate) {
var iceCandidate;
debug(candidate);
debug(JSON.parse(candidate));
if (typeof(webkitRTCIceCandidate) === 'function') {
iceCandidate = new webkitRTCIceCandidate(candidate);
} if (typeof(mozRTCIceCandidate) === 'function') {
iceCandidate = new mozRTCIceCandidate((candidate));
} else if (typeof(RTCIceCandidate) === 'function') {
iceCandidate = new RTCIceCandidate(candidate);
}
return iceCandidate;
};
var createRTCSessionDescription = function(description) {
var newSdp;
if (typeof(RTCSessionDescription) === 'function') {
newSdp = new RTCSessionDescription(description);
} else if (typeof(webkitRTCSessionDescription) === 'function') {
newSdp = new webkitRTCSessionDescription(description);
} else if (typeof(mozRTCSessionDescription) === 'function') {
newSdp = new mozRTCSessionDescription(description);
}
return newSdp;
};
var getRTCPeerConnection = function(stream) {
var peerConnection = null;
if (typeof(RTCPeerConnection) === 'function') {
peerConnection = new RTCPeerConnection(peerConnectionConfig);
} else if (typeof(webkitRTCPeerConnection) === 'function') {
peerConnection = new webkitRTCPeerConnection(peerConnectionConfig);
} else if (typeof(mozRTCPeerConnection) === 'function') {
peerConnection = new mozRTCPeerConnection(peerConnectionConfig);
}
debug("Creating new RTCPeerConnection");
peerConnection.addStream(stream);
peerConnection.onaddstream = function(e) {
debug("Remote stream received");
remoteStream = e.stream;
socketEvent.eventType = 'streamAdded';
document.dispatchEvent(socketEvent);
};
peerConnection.onicecandidate = function(event) {
debug("Retrieving ICE data status changed : " + event.target.iceGatheringState)
var data = {
type: 'iceCandidate',
roomName: roomName,
payload: event
};
sendToServer(data);
};
peerConnection.oniceconnectionstatechange = function(event) {
debug("ICE connection status changed : " + event.target.iceConnectionState)
};
return peerConnection;
};
var setIceCandidates = function(iceCandidate) {
// push icecandidate to array if no SDP of other guys is available
if (!otherSDP) {
othersCandidates.push(iceCandidate);
}
// add icecandidates immediately if not Firefox & if remoteDescription is set
if (otherSDP && iceCandidate.candidate && iceCandidate.candidate !== null) {
peerConnection.addIceCandidate(createRTCIceCandidate(iceCandidate.candidate));
}
};
var handshakeDone = function() {
console.log('handshakeDone');
peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function() {
// add other guy's ice-candidates to connection
for (var i = 0; i < othersCandidates.length; i++) {
if (othersCandidates[i].candidate) {
peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
}
}
// fire event
socketEvent.eventType = 'p2pConnectionReady';
document.dispatchEvent(socketEvent);
}, logError);
};
var createOffer = function() {
peerConnection = getRTCPeerConnection(localStream);
debug('Offer creating');
peerConnection.createOffer(function(description) {
debug('Offer created');
debug('Local description setting');
peerConnection.setLocalDescription(description, function() {
debug('Local description set');
var data = {
type: 'offer',
roomName: roomName,
payload: description
};
sendToServer(data);
}, logError);
}, logError);
};
var createAnswer = function() {
peerConnection = getRTCPeerConnection(localStream);
debug('Offer answering');
debug('Remote description setting');
peerConnection.setRemoteDescription(createRTCSessionDescription(otherSDP), function () {
debug('Remote description set');
debug('Answer creating');
peerConnection.createAnswer(function(description) {
debug('Answer created');
debug('Local description setting');
peerConnection.setLocalDescription(description, function() {
debug('Local description set');
for (var i = 0; i < othersCandidates.length; i++) {
if (othersCandidates[i].candidate) {
peerConnection.addIceCandidate(ceateRTCIceCandidate(othersCandidates[i].candidate));
}
}
// send SDP to other guy
var data = {
type: 'answer',
roomName: roomName,
payload: description
};
sendToServer(data);
}, logError);
}, logError);
}, logError);
};
this.connectToSocket = function(wsUrl) {
wsServer = new WebSocket(wsUrl);
wsServer.onopen = function(event) {
console.log((new Date()) + ' Connection successfully established');
};
wsServer.onerror = function(e) {
console.log((new Date()) + ' WebSocket connection error: ');
logError(e);
};
wsServer.onclose = function(event) {
console.log((new Date()) + ' Connection was closed');
logError(e);
};
wsServer.onmessage = function(message) {
try {
var data = JSON.parse(message.data);
} catch (e) {
logError(e);
return;
}
switch (data.type) {
case 'roomCreated':
roomName = data.payload;
socketEvent.eventType = 'roomCreated';
document.dispatchEvent(socketEvent);
break;
case 'offer':
otherSDP = data.payload;
createAnswer();
break;
case 'answer':
otherSDP = data.payload;
handshakeDone();
break;
case 'iceCandidate':
setIceCandidates(data.payload);
break;
}
};
};
this.getRoomName = function() {
return roomName;
};
this.createRoom = function(roomName) {
var media = getMedia();
var onSuccess = function(stream) {
localVideo.attr('src', URL.createObjectURL(stream));
localStream = stream;
var data = {
type: 'createRoom',
roomName: roomName,
payload: false
};
return sendToServer(data)
};
media(mediaConstraints, onSuccess, logError);
};
this.joinRoom = function(rName) {
var media = getMedia();
var onSuccess = function(stream) {
localVideo.attr('src', URL.createObjectURL(stream));
localStream = stream;
//TODO
roomName = rName;
createOffer();
};
media(mediaConstraints, onSuccess, logError);
};
var getMedia = function() {
var media = null;
if (navigator.getUserMedia) {;
media = navigator.getUserMedia.bind(navigator);
} else if (navigator.webkitGetUserMedia) {
media = navigator.webkitGetUserMedia.bind(navigator);
} else if (navigator.mozGetUserMedia) {
media = navigator.mozGetUserMedia.bind(navigator);
}
return media;
};
// get the other guys media stream
this.getRemoteStream = function() {
return remoteStream;
};
}
main.js
var createRoomButton = $('#createRoomButton');
var joinRoomButton = $('#joinRoomButton');
var roomNameInput = $('#roomNameInput');
var localVideo = $('#localVideo');
var remoteVideo = $('#remoteVideo');
var roomNameField = $('#roomNameField');
var wsProtocol = location.protocol == 'http:' ? 'ws://' : 'wss://';
var wsServerAddress = wsProtocol + document.location.host + ":15555" ;
initialize();
function initialize() {
WebRTC = new WebRTC();
WebRTC.connectToSocket(wsServerAddress);
createRoomButton.click(function() {
WebRTC.createRoom(roomNameInput.val());
});
joinRoomButton.click(function() {
WebRTC.joinRoom(roomNameInput.val());
});
document.addEventListener('socketEvent', function(socketEvent) {
switch (socketEvent.eventType) {
case 'roomCreated':
$('#loginSection').hide();
$('#roomSection').show();
roomNameField.html(WebRTC.getRoomName());
break;
case 'p2pConnectionReady':
$('#loginSection').hide();
$('#roomSection').show();
roomNameField.html(WebRTC.getRoomName());
break;
case 'streamAdded':
var stream = WebRTC.getRemoteStream();
remoteVideo.attr('src', URL.createObjectURL(stream));
break;
}
});
}
common.js
function debug(message) {
console.log(message);
}
function logError(e) {
console.error(e);
}
index.html
<!doctype html>
<html>
<head>
<title>WebRTC</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<link href="css/bootstrap.css" rel="stylesheet">
</head>
<body style="padding: 5em;">
<section id="loginSection">
<div class="row" style="margin-bottom: 2em;">
<div clsss="col-xs-12">
<button id="createRoomButton" type="button" class="btn btn-default btn-lg center-block">
Create room
</button>
</div>
</div>
<div class="row" style="margin-bottom: 4em;">
<div clsss="col-xs-12">
<button id="joinRoomButton" type="button" class="btn btn-default btn-lg center-block">
Join room
</button>
</div>
</div>
<div class="row">
<div clsss="col-xs-4">
<input id="roomNameInput" type="text" style="width: 200px;" class="form-control center-block"
placeholder="Room's name" aria-describedby="basic-addon2">
</div>
</div>
</section>
<section id="roomSection" style="display: none;">
<h3>
Room's name:
<span id="roomNameField" style="font-weight: bold;"></span>
</h3>
<div class="video-wrapper">
<video id="remoteVideo" autoplay="true"></video>
<video id="localVideo" autoplay="true" muted="true" width="200" height="200"></video>
</div>
</section>
<script src="js/jquery-2.1.4.js" type="text/javascript"></script>
<script src="js/bootstrap.js" type="text/javascript"></script>
<script src="js/common.js" type="text/javascript"></script>
<script src="js/client.js" type="text/javascript"></script>
<script src="js/main.js" type="text/javascript"></script>
</body>
</html>