archive
47
comms/webRTC-over-websockets/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
webRTC-over-websockets
|
||||
======================
|
||||
|
||||
|
||||
Small demo of webRTC over websockets, using node.js and HTML5. It allows you to setup peer2peer and group chat calls. You can also add new users on the fly (but not rooms, there are predefined - see `data` directory).
|
||||
|
||||
Ideally, would be best to use XMPP (with strophe.js library) for signaling since it has presence and roster support, with BOSH or even better websockets over XMPP.
|
||||
|
||||
|
||||
Files
|
||||
-------
|
||||
### Data
|
||||
|
||||
- `data/people.json` this is the roster file, where people are defined.
|
||||
- `data/rooms.json` this is where rooms are defined
|
||||
|
||||
### JS files
|
||||
|
||||
- `js/main.js` this is the file responsible for UI updates
|
||||
- `js/webrtc.js` this is the webrtc wrapper
|
||||
- `js/signaling.js` signalling client, implemented using websockets
|
||||
- `js/bandwitdh.js` this is the file for bandwith estimation
|
||||
- `js/adapter.js` this is the file used in webrtc demo's, it wraps browser dependencies
|
||||
|
||||
### Server
|
||||
- `websocket-server.js` this is the websocket server, used by node.js. It requires websocket library, you can install it using the following command :
|
||||
- $ npm install websocket
|
||||
|
||||
After this you can run the server with:
|
||||
$ node server.js [settings.json]
|
||||
|
||||
- `index.html` portal page (see settings below)
|
||||
|
||||
#### Settings
|
||||
Defined in settings.json, in case you don't provide it:
|
||||
- refreshRate [default 5s], refresh presence rate to all connected users
|
||||
- autoCall [default true], whether to call all people in the room as soon as you connect to it
|
||||
- acceptNewUsers [default false], whether to accept users that are not defined in people.json file
|
||||
|
||||
|
||||
Things to change if you install the app on your server:
|
||||
- in `js/main.js` change the websocket address to point to your server
|
||||
- change json files `data/people.json` and `data/rooms.json` with your address book
|
||||
- change images
|
||||
|
||||
|
||||
|
||||
56
comms/webRTC-over-websockets/data/people.json
Normal file
@@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"name" : "Veselin",
|
||||
"id": "veselin",
|
||||
"img" : "images/avatar.png",
|
||||
"room" : ["work", "home", "webrtc"]
|
||||
},
|
||||
{
|
||||
"name" : "Johan",
|
||||
"id": "johan",
|
||||
"img" : "images/johan.jpg",
|
||||
"room" : ["work"]
|
||||
},
|
||||
{
|
||||
"name" : "Rob",
|
||||
"id": "rob",
|
||||
"img" : "images/rob.jpg",
|
||||
"room" : ["work"]
|
||||
},
|
||||
{
|
||||
"name" : "Pieter",
|
||||
"id": "pieter",
|
||||
"img" : "images/pieter.jpg",
|
||||
"room" : ["work"]
|
||||
},
|
||||
{
|
||||
"name" : "Andre",
|
||||
"id": "andre",
|
||||
"img" : "images/andre.jpg",
|
||||
"room" : ["webrtc"]
|
||||
},
|
||||
{
|
||||
"name" : "Jonas",
|
||||
"id": "jonas",
|
||||
"img" : "images/jonas.jpg",
|
||||
"room" : ["work" , "webrtc"]
|
||||
},
|
||||
{
|
||||
"name" : "Jo",
|
||||
"id": "jo",
|
||||
"img" : "images/jo.jpg",
|
||||
"room" : ["work"]
|
||||
},
|
||||
{
|
||||
"name" : "Nick",
|
||||
"id": "nikola",
|
||||
"img" : "images/nikola.jpg",
|
||||
"room" : ["home"]
|
||||
},
|
||||
{
|
||||
"name" : "Christof",
|
||||
"id": "christof",
|
||||
"img" : "images/christof.jpg",
|
||||
"room" : ["work"]
|
||||
}
|
||||
]
|
||||
BIN
comms/webRTC-over-websockets/data/ringtone.wav
Normal file
17
comms/webRTC-over-websockets/data/rooms.json
Normal file
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"name" : "Home",
|
||||
"id": "home",
|
||||
"img" : "images/home.jpg"
|
||||
},
|
||||
{
|
||||
"name" : "webrtc",
|
||||
"id": "webrtc",
|
||||
"img" : "images/webrtc.jpg"
|
||||
},
|
||||
{
|
||||
"name" : "work",
|
||||
"id": "work",
|
||||
"img" : "images/work.jpg"
|
||||
}
|
||||
]
|
||||
BIN
comms/webRTC-over-websockets/images/andre.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
comms/webRTC-over-websockets/images/avatar.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
comms/webRTC-over-websockets/images/browsers.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
comms/webRTC-over-websockets/images/christof.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
comms/webRTC-over-websockets/images/football.jpg
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
comms/webRTC-over-websockets/images/home.jpg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
comms/webRTC-over-websockets/images/home.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
comms/webRTC-over-websockets/images/jo.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
comms/webRTC-over-websockets/images/johan.jpg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
comms/webRTC-over-websockets/images/jonas.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
comms/webRTC-over-websockets/images/nikola.jpg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
comms/webRTC-over-websockets/images/offline-icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
comms/webRTC-over-websockets/images/online-icon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
comms/webRTC-over-websockets/images/person.jpg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
comms/webRTC-over-websockets/images/pieter.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
comms/webRTC-over-websockets/images/rob.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
comms/webRTC-over-websockets/images/webrtc.jpg
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
comms/webRTC-over-websockets/images/work.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
135
comms/webRTC-over-websockets/index.html
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>WebRTC</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<!-- Le styles -->
|
||||
<link href="js/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 9px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
/* Enable use of floated navbar text */
|
||||
.navbar-text.pull-right {
|
||||
float: none;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main">
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
<li><a href="#" onclick="talkFunction(false)"><i class="icon-user icon-white"></i> People</a></li>
|
||||
<li><a href="#" onclick="talkFunction(true)"><i class="icon-facetime-video icon-white"></i> Talk</a></li>
|
||||
</ul>
|
||||
<form id="form" class="navbar-form pull-right">
|
||||
<input id="user" class="span2" type="text" placeholder="User name">
|
||||
<button id="submit" type="submit" class="btn">Sign in</button>
|
||||
</form>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div id="people">
|
||||
<div id="people_content">
|
||||
<div class="carousel slide span10" id="myPeopleCarousel">
|
||||
<div class="carousel-inner people-carousel"></div>
|
||||
<a data-slide="prev" href="#myPeopleCarousel" class="left carousel-control">‹</a>
|
||||
<a data-slide="next" href="#myPeopleCarousel" class="right carousel-control">›</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail span2">
|
||||
<video id="sourceSmallvid" autoplay></video>
|
||||
</div>
|
||||
|
||||
<div id="room_content">
|
||||
<div class="carousel slide span10" id="myRoomCarousel">
|
||||
<div class="carousel-inner rooms-carousel"></div>
|
||||
<a data-slide="prev" href="#myRoomCarousel" class="left carousel-control">‹</a>
|
||||
<a data-slide="next" href="#myRoomCarousel" class="right carousel-control">›</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inner"></div><!--/inner-->
|
||||
</div><!--/people-->
|
||||
|
||||
<div id="share" class="span12">
|
||||
</div><!--/share-->
|
||||
|
||||
<div id="me" class="span12">
|
||||
</div><!--/me-->
|
||||
|
||||
</div><!--/row-->
|
||||
</div><!--/.fluid-container-->
|
||||
</div><!--/.main-->
|
||||
|
||||
<div id="acceptModal" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="callerTitle"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="caller"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="accept" href="#" class="btn btn-primary">Accept</a>
|
||||
<a id="reject" href="#" class="btn">Decline</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userModal" class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="userTitle"></h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="callerUser"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a id="acceptNewUser" href="#" class="btn btn-primary">Accept</a>
|
||||
<a id="rejectNewUser" href="#" class="btn">Decline</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--<script src="http://code.jquery.com/jquery.js"></script> -->
|
||||
<script src="js/jquery-1.7.2.min.js"></script>
|
||||
<script src="js/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="js/adapter.js"></script>
|
||||
<script src="js/bandwidth.js"></script>
|
||||
<script src="js/webrtc.js"></script>
|
||||
<script src="js/signaling.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
89
comms/webRTC-over-websockets/js/adapter.js
Normal file
@@ -0,0 +1,89 @@
|
||||
var RTCPeerConnection = null;
|
||||
var getUserMedia = null;
|
||||
var attachMediaStream = null;
|
||||
var reattachMediaStream = null;
|
||||
var webrtcDetectedBrowser = null;
|
||||
|
||||
if (navigator.mozGetUserMedia) {
|
||||
console.log("This appears to be Firefox");
|
||||
|
||||
webrtcDetectedBrowser = "firefox";
|
||||
|
||||
// The RTCPeerConnection object.
|
||||
RTCPeerConnection = mozRTCPeerConnection;
|
||||
|
||||
// The RTCSessionDescription object.
|
||||
RTCSessionDescription = mozRTCSessionDescription;
|
||||
|
||||
// The RTCIceCandidate object.
|
||||
RTCIceCandidate = mozRTCIceCandidate;
|
||||
|
||||
// Get UserMedia (only difference is the prefix).
|
||||
// Code from Adam Barth.
|
||||
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
|
||||
|
||||
// Attach a media stream to an element.
|
||||
attachMediaStream = function(element, stream) {
|
||||
console.log("Attaching media stream");
|
||||
element.mozSrcObject = stream;
|
||||
element.play();
|
||||
};
|
||||
|
||||
reattachMediaStream = function(to, from) {
|
||||
console.log("Reattaching media stream");
|
||||
to.mozSrcObject = from.mozSrcObject;
|
||||
to.play();
|
||||
};
|
||||
|
||||
// Fake get{Video,Audio}Tracks
|
||||
MediaStream.prototype.getVideoTracks = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
MediaStream.prototype.getAudioTracks = function() {
|
||||
return [];
|
||||
};
|
||||
} else if (navigator.webkitGetUserMedia) {
|
||||
console.log("This appears to be Chrome");
|
||||
|
||||
webrtcDetectedBrowser = "chrome";
|
||||
|
||||
// The RTCPeerConnection object.
|
||||
RTCPeerConnection = webkitRTCPeerConnection;
|
||||
|
||||
// Get UserMedia (only difference is the prefix).
|
||||
// Code from Adam Barth.
|
||||
getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
|
||||
|
||||
// Attach a media stream to an element.
|
||||
attachMediaStream = function(element, stream) {
|
||||
element.src = webkitURL.createObjectURL(stream);
|
||||
};
|
||||
|
||||
reattachMediaStream = function(to, from) {
|
||||
to.src = from.src;
|
||||
};
|
||||
|
||||
// The representation of tracks in a stream is changed in M26.
|
||||
// Unify them for earlier Chrome versions in the coexisting period.
|
||||
if (!webkitMediaStream.prototype.getVideoTracks) {
|
||||
webkitMediaStream.prototype.getVideoTracks = function() {
|
||||
return this.videoTracks;
|
||||
};
|
||||
webkitMediaStream.prototype.getAudioTracks = function() {
|
||||
return this.audioTracks;
|
||||
};
|
||||
}
|
||||
|
||||
// New syntax of getXXXStreams method in M26.
|
||||
if (!webkitRTCPeerConnection.prototype.getLocalStreams) {
|
||||
webkitRTCPeerConnection.prototype.getLocalStreams = function() {
|
||||
return this.localStreams;
|
||||
};
|
||||
webkitRTCPeerConnection.prototype.getRemoteStreams = function() {
|
||||
return this.remoteStreams;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
console.log("Browser does not appear to be WebRTC-capable");
|
||||
}
|
||||
36
comms/webRTC-over-websockets/js/bandwidth.js
Normal file
@@ -0,0 +1,36 @@
|
||||
//http://stackoverflow.com/questions/4583395/calculate-speed-using-javascript
|
||||
/*
|
||||
this is only download estimation, obviously, in peer2peer default deployment, you will
|
||||
have full mesh, n*times both upstream and donwstream stream comming to you (which is bad,
|
||||
but unless you have a server in between this is what it is). Remark: simetrical upstream/downstream
|
||||
is not common in DSL deplyoment, where downstream bandwith is much higher.
|
||||
*/
|
||||
var BANDWITDH = (function(){
|
||||
var imageAddr;
|
||||
var size;
|
||||
var startTime, endTime;
|
||||
var downloadSize;
|
||||
var download = new Image();
|
||||
|
||||
return {
|
||||
/*
|
||||
don't forget to change the address every time you make a request to avoid browser caching.
|
||||
For a demo, I will be using the same as in the stackoverflow example
|
||||
*/
|
||||
init: function(callback, address, size){
|
||||
imageAddr = address !== undefined ? address : "http://www.tranquilmusic.ca/images/cats/Cat2.JPG" + "?n=" + Math.random();
|
||||
downloadSize = size !== undefined ? size : 5616998;
|
||||
startTime = (new Date()).getTime();
|
||||
download.src = imageAddr;
|
||||
download.onload = function() {
|
||||
endTime = (new Date()).getTime();
|
||||
var duration = (endTime - startTime) / 1000;
|
||||
var bitsLoaded = downloadSize * 8;
|
||||
var speedBps = (bitsLoaded / duration).toFixed(2);
|
||||
var speedKbps = (speedBps / 1024).toFixed(2);
|
||||
var speedMbps = (speedKbps / 1024).toFixed(2);
|
||||
callback(speedMbps);
|
||||
}
|
||||
},
|
||||
}
|
||||
})();
|
||||
1109
comms/webRTC-over-websockets/js/bootstrap/css/bootstrap-responsive.css
vendored
Normal file
9
comms/webRTC-over-websockets/js/bootstrap/css/bootstrap-responsive.min.css
vendored
Normal file
6158
comms/webRTC-over-websockets/js/bootstrap/css/bootstrap.css
vendored
Normal file
9
comms/webRTC-over-websockets/js/bootstrap/css/bootstrap.min.css
vendored
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
2276
comms/webRTC-over-websockets/js/bootstrap/js/bootstrap.js
vendored
Normal file
6
comms/webRTC-over-websockets/js/bootstrap/js/bootstrap.min.js
vendored
Normal file
4
comms/webRTC-over-websockets/js/jquery-1.7.2.min.js
vendored
Normal file
258
comms/webRTC-over-websockets/js/main.js
Normal file
@@ -0,0 +1,258 @@
|
||||
var socketAddress = 'ws://localhost:1337/';
|
||||
|
||||
var RTCApp = {
|
||||
name: null,
|
||||
webRTC: null,
|
||||
commChannel: null,
|
||||
message: null
|
||||
};
|
||||
|
||||
RTCApp.commChannel = signaling({webSocketAddress : socketAddress, id: RTCApp.name });
|
||||
|
||||
RTCApp.commChannel.addCallback('presence' , presenceCallback);
|
||||
RTCApp.commChannel.addCallback('s-offer', accept);
|
||||
RTCApp.commChannel.addCallback('roster', roster);
|
||||
|
||||
|
||||
RTCApp.webRTC = webrtc({sourcevid : document.getElementById('sourceSmallvid'),
|
||||
stunServer : "stun.l.google.com:19302",
|
||||
commChannel : RTCApp.commChannel,
|
||||
onremote : remoteCallback,
|
||||
constrains : 'dynamic'
|
||||
});
|
||||
|
||||
RTCApp.webRTC.startVideo();
|
||||
|
||||
var users = {};
|
||||
var caller, newUser;
|
||||
var snd = new Audio("data/ringtone.wav");
|
||||
var newUsers = {};
|
||||
|
||||
$(document).ready(function() {
|
||||
//List of people and rooms is either retrived by uncommenting lines below,
|
||||
//or after first successfull login (when the socket server send it to the logged user)
|
||||
|
||||
//loadFromJSON("data/people.json", "ajax-modal", ".people-carousel", false);
|
||||
//loadFromJSON("data/rooms.json", "ajax-room-modal", ".rooms-carousel", true);
|
||||
|
||||
$('#people_content').hide();
|
||||
$('#room_content').hide();
|
||||
$('#people').fadeIn();
|
||||
$('[id^="myCarousel"]').carousel({interval: false});
|
||||
});
|
||||
|
||||
$("#form").submit(function(event) {
|
||||
event.preventDefault();
|
||||
RTCApp.name = $("#user").val();
|
||||
RTCApp.commChannel.sendPresence(RTCApp.name, 'on');
|
||||
|
||||
$('#user').attr('readonly', true);
|
||||
$("#submit").hide();
|
||||
});
|
||||
|
||||
$('#accept').bind('click', function() {
|
||||
RTCApp.commChannel.answer(caller, 'accept');
|
||||
$('#acceptModal').modal('hide');
|
||||
});
|
||||
|
||||
$('#reject').bind('click', function() {
|
||||
RTCApp.commChannel.answer(caller, 'reject');
|
||||
$('#acceptModal').modal('hide');
|
||||
});
|
||||
|
||||
function accept(message){
|
||||
if(message.from === RTCApp.name){
|
||||
console.log("can't call yourself ");
|
||||
return;
|
||||
}
|
||||
caller = message.from;
|
||||
snd.play();
|
||||
$('#callerTitle').text('Incoming call');
|
||||
$('#caller').text('Caller id '+ message.from);
|
||||
$('#acceptModal').modal('show');
|
||||
}
|
||||
|
||||
function talkFunction(flag){
|
||||
var callback = flag === true? "hide" : "show";
|
||||
$('#people_content')[callback]();
|
||||
$('#room_content')[callback]();
|
||||
}
|
||||
|
||||
function remoteCallback(from, added, stream){
|
||||
var resource = 'resource_'+ from;
|
||||
if(added){
|
||||
if (window.webkitURL) {
|
||||
$('.inner').append('<video class="span4" id=' + resource + ' src=' +
|
||||
window.webkitURL.createObjectURL(event.stream) + ' autoplay></video>');
|
||||
} else {
|
||||
$('.inner').append('<video class="span4" id=' + resource + ' mozSrcObject=' +
|
||||
event.stream + ' autoplay></video>');
|
||||
}
|
||||
} else {
|
||||
$('#'+resource).attr('src',"");
|
||||
$('#'+resource).remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$('#acceptNewUser').bind('click', function() {
|
||||
delete newUsers[newUser];
|
||||
addNewUser({
|
||||
"name" : newUser,
|
||||
"id": newUser,
|
||||
"img" : "images/person.jpg"
|
||||
}, "ajax-modal", ".people-carousel");
|
||||
$('#userModal').modal('hide');
|
||||
});
|
||||
|
||||
$('#rejectNewUser').bind('click', function() {
|
||||
newUsers[newUser] = false;
|
||||
$('#userModal').modal('hide');
|
||||
});
|
||||
|
||||
function presenceCallback(message){
|
||||
roomFlag = message.room !== undefined;
|
||||
var user_id, room_id;
|
||||
if(!roomFlag){
|
||||
$(".ajax-modal").each(function(){
|
||||
user_id = $(this).attr('user-id');
|
||||
if(user_id !== undefined && user_id === message.name){
|
||||
if(message.status === 'on'){
|
||||
$(this).find('img').attr('src', 'images/online-icon.png');
|
||||
} else {
|
||||
$(this).find('img').attr('src', 'images/offline-icon.png');
|
||||
}
|
||||
}
|
||||
});
|
||||
if(users[message.name] === undefined && message.status === 'on'){
|
||||
console.log('presence received from the person that is not in the address book ' + message.name);
|
||||
if(newUsers[message.name] === undefined){
|
||||
newUsers[message.name] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
$(".ajax-room-modal").each(function(){
|
||||
room_id = $(this).attr('user-id');
|
||||
if(room_id !== undefined && room_id === message.name){
|
||||
if(message.status === 'on'){
|
||||
$(this).find('img').attr('src', 'images/online-icon.png');
|
||||
}else {
|
||||
$(this).find('img').attr('src', 'images/offline-icon.png');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$('#main').delegate('a.ajax-modal', 'click', function() {
|
||||
event.preventDefault();
|
||||
var user_id = $(this).attr('user-id');
|
||||
if(user_id !== undefined && user_id !== RTCApp.name)
|
||||
RTCApp.commChannel.callOtherParty(user_id);
|
||||
});
|
||||
|
||||
$('#main').delegate('a.ajax-room-modal', 'click', function() {
|
||||
event.preventDefault();
|
||||
var room = $(this).attr('user-id');
|
||||
RTCApp.commChannel.joinRoom(room);
|
||||
});
|
||||
|
||||
$.ajaxSetup({
|
||||
'beforeSend' : function(xhr) {
|
||||
xhr.overrideMimeType('text/html; charset=ISO-8859-1');
|
||||
},
|
||||
});
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
if(RTCApp.commChannel !== null)
|
||||
RTCApp.commChannel.sendPresence(RTCApp.name, 'off');
|
||||
}
|
||||
|
||||
function addNewUser(jsonData, class_name, class_div){
|
||||
users[jsonData.id] = jsonData;
|
||||
$('.people-carousel').empty();
|
||||
var array = $.map(users, function (value, key) { return value; });
|
||||
addDataToDiv(array, class_name, class_div, false);
|
||||
}
|
||||
|
||||
function loadFromJSON(file, class_name, class_div, roomFlag){
|
||||
$.getJSON(file, function(data) {
|
||||
addDataToDiv(data, class_name, class_div, roomFlag);
|
||||
});
|
||||
}
|
||||
|
||||
function roster(message){
|
||||
addDataToDiv(message.people, "ajax-modal", ".people-carousel", false);
|
||||
addDataToDiv(message.rooms, "ajax-room-modal", ".rooms-carousel", true);
|
||||
|
||||
if(message.people.length > 0)
|
||||
$('#people_content').show();
|
||||
if(message.rooms.length > 0)
|
||||
$('#room_content').show();
|
||||
|
||||
if(users[RTCApp.name] === undefined){
|
||||
console.log('user not known by the system, create an avatar');
|
||||
addNewUser({
|
||||
"name" : RTCApp.name,
|
||||
"id": RTCApp.name,
|
||||
"img" : "images/person.jpg"
|
||||
}, "ajax-modal", ".people-carousel");
|
||||
}
|
||||
if(users[RTCApp.name].room !== undefined){
|
||||
for(var i=0; i < users[RTCApp.name].room.length; i ++)
|
||||
RTCApp.commChannel.joinRoom(users[RTCApp.name].room[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function addDataToDiv(data, class_name, class_div, roomFlag){
|
||||
var items = [];
|
||||
var i = 0;
|
||||
var groupIndex = 12;
|
||||
|
||||
$.each(data, function(key, value) {
|
||||
var name = this.name;
|
||||
var image = this.img;
|
||||
var id = this.id;
|
||||
if(!roomFlag)
|
||||
users[id] = value;
|
||||
|
||||
if(i % groupIndex === 0){
|
||||
if(i === 0)
|
||||
items.push('<div class="item active">');
|
||||
else
|
||||
items.push('<div class="item">');
|
||||
items.push('<ul class="thumbnails">');
|
||||
}
|
||||
|
||||
items.push('<li class="span1"><div class="thumbnail"><img src="' + image
|
||||
+' " alt=""></a> <h4>' + name + '</h4><p><a href="#" class="btn btn-primary ' +
|
||||
class_name + ' " user-id="' + id +
|
||||
'" >Talk<img src="images/offline-icon.png" width="24" height="24" align="left" alt=""> </a></p></div></li>');
|
||||
|
||||
if( (i % groupIndex) === (groupIndex - 1) ){
|
||||
items.push('</ul>');
|
||||
items.push('</div>');
|
||||
}
|
||||
i++;
|
||||
});
|
||||
|
||||
if(items.lenght > 0 && items[items.lenght - 1].indexOf('div') < 0 ){
|
||||
items.push('</ul>');
|
||||
items.push('</div>');
|
||||
}
|
||||
|
||||
$(items.join('')).appendTo(class_div);
|
||||
}
|
||||
|
||||
|
||||
setInterval(function(){
|
||||
for(newUser in newUsers){
|
||||
if(newUsers[newUser]){
|
||||
$('#userTitle').text('Accept a new user?');
|
||||
$('#callerUser').text('User id '+ newUser);
|
||||
$('#userModal').modal('show');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, 10000);
|
||||
76
comms/webRTC-over-websockets/js/signaling.js
Normal file
@@ -0,0 +1,76 @@
|
||||
var signaling = function(options){
|
||||
|
||||
var socket = new WebSocket(options.webSocketAddress);
|
||||
var logg = function(s) { console.log(s); };
|
||||
|
||||
var myId = options.id;
|
||||
|
||||
var that = {};
|
||||
var callbacks = {};
|
||||
|
||||
function getCallback(type){
|
||||
return callbacks[type] !== undefined ? callbacks[type] : function(){
|
||||
console.log("Callback of type " + type + " not found");
|
||||
};
|
||||
}
|
||||
|
||||
that.addCallback = function(type, f){
|
||||
callbacks[type] = f;
|
||||
}
|
||||
|
||||
that.sendMessage = function(message, to) {
|
||||
sendMsg(message, to);
|
||||
}
|
||||
|
||||
that.sendPresence = function(_name, stat){
|
||||
_status = stat || 'on';
|
||||
myId = _name;
|
||||
sendMsg({type: 'presence', name: _name, status: _status});
|
||||
}
|
||||
|
||||
that.joinRoom = function(room){
|
||||
sendMsg({type: 'room'}, room);
|
||||
}
|
||||
|
||||
that.callOtherParty = function(to){
|
||||
sendMsg({type: 's-offer'}, to);
|
||||
};
|
||||
|
||||
that.answer = function(to, _answer){
|
||||
sendMsg({type: 's-answer', answer: _answer}, to);
|
||||
};
|
||||
|
||||
function sendMsg(message, to){
|
||||
message.from = myId;
|
||||
if(to !== undefined)
|
||||
message.to = to;
|
||||
var mymsg = JSON.stringify(message);
|
||||
logg("SOCKET Send: " + mymsg);
|
||||
socket.send(mymsg);
|
||||
}
|
||||
|
||||
socket.addEventListener("message", onMessage, false);
|
||||
|
||||
socket.addEventListener("error", function(event) {
|
||||
logg("SOCKET Error: " + event);
|
||||
});
|
||||
|
||||
socket.addEventListener("close", function(event) {
|
||||
logg("SOCKET Close: " + event);
|
||||
});
|
||||
|
||||
function onMessage(evt) {
|
||||
logg("RECEIVED: " + evt.data);
|
||||
processSignalingMessage(evt.data);
|
||||
}
|
||||
|
||||
//message comes as a JSON from the websocket server
|
||||
function processSignalingMessage(message) {
|
||||
var msg = JSON.parse(message);
|
||||
logg("processSignalingMessage type(" + msg.type + ")= " + message);
|
||||
getCallback(msg.type)(msg);
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
}
|
||||
210
comms/webRTC-over-websockets/js/webrtc.js
Normal file
@@ -0,0 +1,210 @@
|
||||
var webrtc = function(options) {
|
||||
var my = {};
|
||||
|
||||
var commChannel = options.commChannel,
|
||||
stunServer = options.stunServer,
|
||||
sourcevid = options.sourcevid,
|
||||
remoteCallback = options.onremote;
|
||||
|
||||
var localStream;
|
||||
var peerConn = {};
|
||||
|
||||
var mediaConstraints = {'mandatory': {'OfferToReceiveAudio':true, 'OfferToReceiveVideo':true }};
|
||||
|
||||
if(options.constrains === 'dynamic'){
|
||||
BANDWITDH.init(function(bandwitdh){
|
||||
console.log('calculate bandwitdh');
|
||||
if(!isNaN(bandwitdh) && bandwitdh < 0.5){
|
||||
mediaConstraints = {'mandatory': {
|
||||
'OfferToReceiveAudio':true,
|
||||
'OfferToReceiveVideo':false }};
|
||||
}
|
||||
console.log('bandwitdh is ' + bandwitdh + ' [Mbps]');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//callback to start p2p connection between two parties
|
||||
commChannel.addCallback('s-answer', call);
|
||||
|
||||
commChannel.addCallback('offer', processSignalingMessage);
|
||||
commChannel.addCallback('answer', processSignalingMessage);
|
||||
commChannel.addCallback('candidate', processSignalingMessage);
|
||||
commChannel.addCallback('bye', processSignalingMessage);
|
||||
|
||||
function RTCPeer(pc_config, name) {
|
||||
this.from = name;
|
||||
this.rtc = new RTCPeerConnection(pc_config);
|
||||
that = this;
|
||||
|
||||
this.rtc.onaddstream = function(event){
|
||||
logg("Added remote stream");
|
||||
remoteCallback(that.from, true, event.stream);
|
||||
};
|
||||
this.rtc.onremovestream = function(event) {
|
||||
logg("Remove remote stream");
|
||||
remoteCallback(that.from, false);
|
||||
};
|
||||
this.rtc.onicecandidate = function(event) {
|
||||
logg("send on Icecandidate");
|
||||
if (event.candidate) {
|
||||
commChannel.sendMessage({type: 'candidate',
|
||||
label: event.candidate.sdpMLineIndex,
|
||||
id: event.candidate.sdpMid,
|
||||
candidate: event.candidate.candidate}, that.from);
|
||||
} else {
|
||||
logg("End of candidates.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setLocalDescriptionAndMessage(sessionDescription){
|
||||
logg("setLocalDescriptionAndMessage");
|
||||
this.rtc.setLocalDescription(sessionDescription);
|
||||
commChannel.sendMessage(sessionDescription, this.from);
|
||||
}
|
||||
|
||||
RTCPeer.prototype.createOffer = function(callback){
|
||||
logg("createOffer to " + this.from);
|
||||
that = this;
|
||||
this.rtc.createOffer(function(sessionDescription){
|
||||
callback.call(that, sessionDescription);
|
||||
}, null, mediaConstraints);
|
||||
}
|
||||
|
||||
RTCPeer.prototype.createAnswer = function(callback){
|
||||
logg("createAnswer to " + this.from);
|
||||
that = this;
|
||||
this.rtc.createAnswer(function(sessionDescription){
|
||||
callback.call(that, sessionDescription);
|
||||
}, null, mediaConstraints);
|
||||
}
|
||||
|
||||
RTCPeer.prototype.getRTC = function(){
|
||||
return this.rtc;
|
||||
}
|
||||
|
||||
RTCPeer.prototype.getFrom = function(){
|
||||
return this.from;
|
||||
}
|
||||
|
||||
var logg = function(s) { console.log(s); };
|
||||
|
||||
my.startVideo = function() {
|
||||
try {
|
||||
getUserMedia({audio: true, video: true}, successCallback, errorCallback);
|
||||
} catch (e) {
|
||||
getUserMedia("video,audio", successCallback, errorCallback);
|
||||
}
|
||||
function successCallback(stream) {
|
||||
attachMediaStream(sourcevid, stream);
|
||||
localStream = stream;
|
||||
logg('local stream started');
|
||||
}
|
||||
function errorCallback(error) {
|
||||
logg('An error occurred: [CODE ' + error.code + ']');
|
||||
}
|
||||
}
|
||||
|
||||
my.stopVideo = function() {
|
||||
sourcevid.src = "";
|
||||
}
|
||||
|
||||
my.onHangUp = function() {
|
||||
logg("Hang up.");
|
||||
closeSession();
|
||||
}
|
||||
|
||||
// start the connection upon user request
|
||||
function call(msg) {
|
||||
if(msg.answer !== 'accept') {
|
||||
console.log('call not accepted');
|
||||
return;
|
||||
}
|
||||
|
||||
if (peerConn[msg.from] === undefined && localStream) {
|
||||
logg("Creating PeerConnection with "+ msg.from);
|
||||
createPeerConnection(msg.from);
|
||||
} else if (!localStream){
|
||||
alert("Please start the video first");
|
||||
logg("localStream not started");
|
||||
return;
|
||||
} else {
|
||||
logg("peer SDP offer already made");
|
||||
}
|
||||
logg("create offer");
|
||||
peerConn[msg.from].createOffer(setLocalDescriptionAndMessage);
|
||||
}
|
||||
|
||||
|
||||
function createPeerConnection(from) {
|
||||
try {
|
||||
logg("Creating peer connection with " + from);
|
||||
var servers = [];
|
||||
servers.push({'url':'stun:' + stunServer});
|
||||
var pc_config = {'iceServers':servers};
|
||||
peerConn[from] = new RTCPeer(pc_config, from);
|
||||
logg("Connected using stun server "+ stunServer);
|
||||
} catch (e) {
|
||||
alert("Failed to create PeerConnection, exception: " + e.message);
|
||||
return;
|
||||
}
|
||||
logg('Adding local stream...');
|
||||
peerConn[from].getRTC().addStream(localStream);
|
||||
}
|
||||
|
||||
|
||||
function processSignalingMessage(msg) {
|
||||
logg("processSignalingMessage type(" + msg.type + ")= " + msg);
|
||||
|
||||
if (msg.type === 'offer') {
|
||||
if(peerConn[msg.from] === undefined && localStream) {
|
||||
createPeerConnection(msg.from);
|
||||
//set remote description
|
||||
peerConn[msg.from].getRTC().setRemoteDescription(new RTCSessionDescription(msg));
|
||||
//create answer
|
||||
logg("Sending answer to peer.");
|
||||
peerConn[msg.from].createAnswer(setLocalDescriptionAndMessage);
|
||||
} else {
|
||||
logg('peerConnection has already been started');
|
||||
}
|
||||
} else if (msg.type === 'answer' && peerConn[msg.from] !== undefined) {
|
||||
logg("setRemoteDescription...");
|
||||
peerConn[msg.from].getRTC().setRemoteDescription(new RTCSessionDescription(msg));
|
||||
} else if (msg.type === 'candidate' && peerConn[msg.from] !== undefined) {
|
||||
var candidate = new RTCIceCandidate({sdpMLineIndex:msg.label, candidate:msg.candidate});
|
||||
peerConn[msg.from].getRTC().addIceCandidate(candidate);
|
||||
} else if (msg.type === 'bye' && peerConn[msg.from] !== undefined) {
|
||||
onRemoteHangUp(msg.from);
|
||||
} else {
|
||||
logg("message unknown:" + msg);
|
||||
}
|
||||
}
|
||||
|
||||
function onRemoteHangUp(from) {
|
||||
logg("Remote(" + from + ") Hang up ");
|
||||
remoteCallback(from, false);
|
||||
peerConn[from].getRTC().close();
|
||||
delete peerConn[from];
|
||||
}
|
||||
|
||||
function closeSession() {
|
||||
for(var index in peerConn){
|
||||
remoteCallback(peerConn[index].getFrom(), false);
|
||||
peerConn[index].getRTC().close();
|
||||
delete peerConn[index];
|
||||
}
|
||||
commChannel.sendMessage({type: 'bye'});
|
||||
}
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
if (Object.keys(peerConn).length > 0) {
|
||||
closeSession();
|
||||
}
|
||||
}
|
||||
|
||||
return my;
|
||||
};
|
||||
|
||||
|
||||
|
||||
117
comms/webRTC-over-websockets/package-lock.json
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"d": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
|
||||
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
|
||||
"requires": {
|
||||
"es5-ext": "^0.10.50",
|
||||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"es5-ext": {
|
||||
"version": "0.10.53",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
||||
"integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
|
||||
"requires": {
|
||||
"es6-iterator": "~2.0.3",
|
||||
"es6-symbol": "~3.1.3",
|
||||
"next-tick": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
||||
"requires": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"es6-symbol": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
|
||||
"requires": {
|
||||
"d": "^1.0.1",
|
||||
"ext": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"ext": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
|
||||
"integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
|
||||
"requires": {
|
||||
"type": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"type": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
|
||||
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
||||
},
|
||||
"next-tick": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
|
||||
},
|
||||
"type": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
|
||||
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
|
||||
},
|
||||
"typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||
"requires": {
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"websocket": {
|
||||
"version": "1.0.31",
|
||||
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz",
|
||||
"integrity": "sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==",
|
||||
"requires": {
|
||||
"debug": "^2.2.0",
|
||||
"es5-ext": "^0.10.50",
|
||||
"nan": "^2.14.0",
|
||||
"typedarray-to-buffer": "^3.1.5",
|
||||
"yaeti": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"yaeti": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
|
||||
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
|
||||
}
|
||||
}
|
||||
}
|
||||
7
comms/webRTC-over-websockets/settings.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"websocketPort" : 1337,
|
||||
"refreshRate" : 5000,
|
||||
"autoCall" : true,
|
||||
"acceptNewUsers" : true,
|
||||
"sendRoster" : true
|
||||
}
|
||||
195
comms/webRTC-over-websockets/websocket-server.js
Normal file
@@ -0,0 +1,195 @@
|
||||
var WebSocketServer = require('websocket').server;
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
var people = {};
|
||||
var connectionDict = {};
|
||||
var rooms = {};
|
||||
var numberOfConnections = 0;
|
||||
var roster = {};
|
||||
|
||||
var settings = {
|
||||
websocketPort: 1337,
|
||||
refreshRate : 5000,
|
||||
autoCall : true,
|
||||
acceptNewUsers : false
|
||||
}
|
||||
|
||||
fs.readFile(process.argv[2] || './settings.json', function(err, data) {
|
||||
if (err) {
|
||||
console.log('No settings.json found ('+err+'). Using default settings');
|
||||
} else {
|
||||
settings = JSON.parse(data.toString('utf8', 0, data.length));
|
||||
}
|
||||
console.log(settings);
|
||||
});
|
||||
|
||||
fs.readFile('data/people.json', 'utf8', function (err, data) {
|
||||
if (err) throw err;
|
||||
var obj = JSON.parse(data.toString('utf8', 0, data.length));
|
||||
roster.people = obj;
|
||||
for(var i=0; i< obj.length; i ++){
|
||||
people[obj[i].id] = 'off';
|
||||
};
|
||||
console.log('Loaded ' + obj.length + ' people from people.json file');
|
||||
});
|
||||
|
||||
fs.readFile('data/rooms.json', 'utf8', function (err, data) {
|
||||
if (err) throw err;
|
||||
var obj = JSON.parse(data.toString('utf8', 0, data.length));
|
||||
roster.rooms = obj;
|
||||
console.log('Loaded ' + obj.length + ' rooms from rooms.json file');
|
||||
});
|
||||
|
||||
var server = http.createServer(function(request, response) {
|
||||
// process HTTP request. Since we're writing just WebSockets serve we don't have to implement anything.
|
||||
}).listen(settings.websocketPort, function() {
|
||||
console.log('Socket server is listening on port ' + settings.websocketPort);
|
||||
});
|
||||
|
||||
wsServer = new WebSocketServer({
|
||||
httpServer: server
|
||||
});
|
||||
|
||||
setInterval(sendPresence, settings.refreshRate);
|
||||
|
||||
// This callback function is called every time someone tries to connect to the WebSocket server
|
||||
// if the type of message is presence, it will send the broadcast. If the type is room, it will send the offer type as multicast,
|
||||
// if from and to field are defined, and connection.name that maches to exists, it will send the unicast.
|
||||
// Otherwise, it will send the broadcast.
|
||||
|
||||
wsServer.on('request', function(request) {
|
||||
numberOfConnections ++;
|
||||
console.log('Connection from origin ' + request.origin);
|
||||
var connection = request.accept(null, request.origin);
|
||||
console.log('Connection address ' + connection.remoteAddress);
|
||||
console.log('Number of connections ' + numberOfConnections);
|
||||
|
||||
connection.on('message', function(message) {
|
||||
if (message.type === 'utf8') {
|
||||
// process WebSocket message
|
||||
console.log('Received Message ' + message.utf8Data);
|
||||
|
||||
//parse the message
|
||||
msg = JSON.parse(message.utf8Data);
|
||||
var type = msg.type;
|
||||
var from = msg.from;
|
||||
var to = msg.to;
|
||||
var name;
|
||||
|
||||
//accept only messages that are authorized, in simple case, we assume that the
|
||||
//first call is the presence with a name of the caller
|
||||
if(type !== 'presence' && connection.name === undefined){
|
||||
console.log('connection not allowed, name not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
//after presence message, socket connection is 'named', only such connections participate later.
|
||||
if(type === 'presence'){
|
||||
name = msg.name;
|
||||
if(people[name] === undefined && !settings.acceptNewUsers){
|
||||
console.log('Unknown user not allowed');
|
||||
return;
|
||||
}
|
||||
var status = msg.status;
|
||||
if(status === 'on'){
|
||||
connection.name = name;
|
||||
connectionDict[name] = connection;
|
||||
console.log('adding '+ name)
|
||||
people[name] = 'on';
|
||||
if(settings.sendRoster){
|
||||
connection.send(JSON.stringify({type: "roster", people: roster.people, rooms: roster.rooms}));
|
||||
}
|
||||
} else {
|
||||
remove(name);
|
||||
}
|
||||
} else if(type === 'room'){
|
||||
if(rooms[to] === undefined)
|
||||
rooms[to] = [];
|
||||
console.log('Number of people in the room ' + rooms[to].length);
|
||||
console.log('Sending multicast from ' + from + ": to room "+ to);
|
||||
if(settings.autoCall){
|
||||
for(var i = 0; i < rooms[to].length; i ++){
|
||||
var x = rooms[to][i];
|
||||
if(connectionDict[x] !== undefined){
|
||||
console.log('Sending offer from ' + from + ": to "+ x);
|
||||
sendOffer(connectionDict[x], from, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(rooms[to].indexOf(from) < 0)
|
||||
rooms[to].push(from);
|
||||
return;
|
||||
} else if(to !== undefined && from !== undefined){
|
||||
if(connectionDict[to] !== undefined && people[to] !== 'off'){
|
||||
console.log('Sending unicast from ' + from + ":"+ to);
|
||||
connectionDict[to].send(message.utf8Data, sendCallback);
|
||||
}
|
||||
} else {
|
||||
console.log("message couldn't be passed to " + to);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Sending broadcast from '+ from);
|
||||
|
||||
// broadcast message to all clients that have name attached
|
||||
for(var client in connectionDict){
|
||||
if(client !== name){
|
||||
console.log('Sending data to '+ client);
|
||||
connectionDict[client].send(message.utf8Data, sendCallback);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connection.on('close', function(conn) {
|
||||
console.log('Peer disconnected.');
|
||||
numberOfConnections --;
|
||||
remove(connection.name);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function sendOffer(connection, _from, _to){
|
||||
connection.send(JSON.stringify({type: 's-offer', from: _from, to: _to}), sendCallback);
|
||||
|
||||
}
|
||||
|
||||
function sendPresence(){
|
||||
for(var _name in people){
|
||||
for(var client in connectionDict){
|
||||
if(people[_name] === 'off'){
|
||||
console.log('Person '+ _name + '[off] -> ' + client);
|
||||
connectionDict[client].send(JSON.stringify({type: 'presence', name: _name, status: 'off'}), sendCallback);
|
||||
} else if(people[_name] === 'on'){
|
||||
console.log('Person '+ _name + '[on] -> ' + client);
|
||||
connectionDict[client].send(JSON.stringify({type: 'presence', name: _name, status: 'on'}), sendCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var room in rooms){
|
||||
for(var i = 0; i < rooms[room].length; i ++){
|
||||
var client = rooms[room][i];
|
||||
if(connectionDict[client] !== undefined){
|
||||
console.log('Room '+ room + ' -> ' + client);
|
||||
connectionDict[client].send(JSON.stringify({type: 'presence', name: room,
|
||||
status: 'on', room: true}), sendCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function remove(name){
|
||||
if(name !== undefined && name !== null){
|
||||
console.log('removing '+ name);
|
||||
people[name] = 'off';
|
||||
delete connectionDict[name];
|
||||
}
|
||||
}
|
||||
|
||||
function sendCallback(err) {
|
||||
if (err){
|
||||
console.error("send() error: " + err);
|
||||
}
|
||||
}
|
||||
|
||||