You can use any signaling implementation with any WebRTC Experiment; whether it is XMPP/SIP or PHP/MySQL or Socket.io/WebSockets or WebSync/SignalR or PeerServer/SignalMaster or other gateway.
Remember, there are some built-in implementations:
- RTCMultiConnection.js and Reliable Signaling
- DataChanel.js and Reliable Signaling
- Socket.io over Node.js
- WebSocket over Node.js
- WebSync for Signaling — useful only for .NET developers
- XHR/XMLHttpRequest Signaling — useful for both .NET and PHP developers!
If you wanna understand basics of WebRTC signaling; then scroll to bottom and check this section.
=
Your server side code can be as simple as possible like this:
io.sockets.on('connection', function (socket) {
socket.on('message', function (data) {
socket.broadcast.emit('message', data);
});
});
You can even use existing services like (for server side code only!):
- https://github.com/andyet/signalmaster
- https://github.com/peers/peerjs-server
- https://github.com/SignalR/SignalR
- http://millermedeiros.github.io/js-signals/
- https://github.com/sockjs/sockjs-client
=
There are dozens of WebRTC Experiments and Libraries; you can use any existing signaling server with any WebRTC Experiment/Library!
You just need to understand how signaling is implemented in WebRTC Experiments:
- All WebRTC Experiments has
openSocket
method; that can be defined in the HTML page; which allows you override/use any signaling implementation there! - All WebRTC Libraries has a public method i.e.
openSignalingChannel
; which can also be overridden/defined in the HTML page; also you can override it to easily use any signaling implementation exists out there!
Now you understood how default implementations can be overridden; it is time to understand how to override for any signaling implementation exists out there!
=
This array-like object will store onmessage
callbacks.
var onMessageCallbacks = {};
var websocket = new WebSocket('wss://something:port/');
var socket = io.connect('https://domain:port/');
var firebase = new Firebase('https://user.firebaseio.com/' + connection.channel);
For socket.io; you can pass default channel as URL parameter:
var socket = io.connect('https://domain:port/?channel=' + connection.channel);
Capture server messages:
websocket.onmessage = function (event) {
onMessageCallBack(event.data);
};
socket.on('message', function (data) {
onMessageCallBack(data);
});
firebase.on('child_added', function (snap) {
onMessageCallBack(snap.val());
snap.ref().remove(); // for socket.io live behaviour
});
and onMessageCallBack
:
function onMessageCallBack(data) {
data = JSON.parse(e.data);
if (data.sender == connection.userid) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
}
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
return {
send: function (message) {
websocket.send(JSON.stringify({
sender: connection.userid,
channel: channel,
message: message
}));
},
channel: channel
};
};
Read more here.
=
openSignalingChannel
for RTCMultiConnection.js and DataChanel.js (Client-Side Code)
Putting above 4-steps together! Here is your browser side code that overrides default signaling implementations:
var onMessageCallbacks = {};
var socketio = io.connect('http://localhost:8888/');
socketio.on('message', function(data) {
if(data.sender == connection.userid) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
});
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
return {
send: function (message) {
socketio.emit('message', {
sender: connection.userid,
channel: channel,
message: message
});
},
channel: channel
};
};
=
var onMessageCallbacks = {};
var currentUserUUID = Math.round(Math.random() * 60535) + 5000;
var socketio = io.connect('http://localhost:8888/');
socketio.on('message', function(data) {
if(data.sender == currentUserUUID) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
});
var config = {
openSocket = function (config) {
var channel = config.channel || 'main-channel';
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
return {
send: function (message) {
socketio.emit('message', {
sender: currentUserUUID,
channel: channel,
message: message
});
},
channel: channel
};
}
};
=
// global stuff
var onMessageCallbacks = {};
var currentUserUUID = Math.round(Math.random() * 60535) + 5000;
var websocket = new WebSocket('ws://localhost:8888/');
websocket.onmessage = function(e) {
data = JSON.parse(e.data);
if(data.sender == currentUserUUID) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
};
websocket.push = websocket.send;
websocket.send = function(data) {
data.sender = currentUserUUID;
websocket.push(JSON.stringify(data));
};
// overriding "openSignalingChannel" method
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
// directly returning socket object using "return" statement
return {
send: function (message) {
websocket.send(JSON.stringify({
sender: currentUserUUID,
channel: channel,
message: message
}));
},
channel: channel
};
};
=
-
The object returned by overridden
openSignalingChannel
oropenSocket
method MUST return an object with two things:send
method. Used to send data via signaling gateway.channel
object. Used for video-conferencing. If you skip it; it will make one-to-many instead of many-to-many.
-
onmessage
oron('message', callback)
MUST have same code as you can see a few lines above.
openSocket
method can return socket
object in three ways:
- Directly returning using
return
statement. - Passing back over
config.callback
object. - Passing back over
config.onopen
object.
Second option i.e. config.callback
is preferred.
var config = {
openSocket: function (config) {
var channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, '');
var socket = new Firebase('https://chat.firebaseIO.com/' + channel);
socket.channel = channel;
socket.on("child_added", function (data) {
config.onmessage && config.onmessage(data.val());
});
socket.send = function (data) {
this.push(data);
};
socket.onDisconnect().remove();
// first option: returning socket object using "return" statement!
return socket;
}
};
var config = {
openSocket: function (config) {
var SIGNALING_SERVER = 'wss://wsnodejs.nodejitsu.com:443';
var channel = config.channel || location.href.replace(/\/|:|#|%|\.|\[|\]/g, '');
var websocket = new WebSocket(SIGNALING_SERVER);
websocket.channel = config.channel;
websocket.onopen = function () {
websocket.push(JSON.stringify({
open: true,
channel: config.channel
}));
// second option: returning socket object using "config.callback" method
if (config.callback)
config.callback(websocket);
};
websocket.onmessage = function (event) {
config.onmessage(JSON.parse(event.data));
};
websocket.push = websocket.send;
websocket.send = function (data) {
websocket.push(JSON.stringify({
data: data,
channel: config.channel
}));
};
}
};
var config = {
openSocket: function (config) {
// --------
websocket.onopen = function () {
// --------
// third option: returning socket object using "config.onopen" method
if (config.onopen)
config.onopen(websocket);
};
// --------
}
};
=
How to use WebSync for Signaling?
<script src="fm.js"> </script>
<script src="fm.websync.js"> </script>
<script src="fm.websync.subscribers.js"> </script>
<script src="fm.websync.chat.js"> </script>
// www.RTCMultiConnection.org/latest.js
var connection = new RTCMultiConnection();
// ------------------------------------------------------------------
// start-using WebSync for signaling
var onMessageCallbacks = {};
var client = new fm.websync.client('websync.ashx');
client.setAutoDisconnect({
synchronous: true
});
client.connect({
onSuccess: function () {
client.join({
channel: '/chat',
userId: connection.userid,
userNickname: connection.userid,
onReceive: function (event) {
var message = JSON.parse(event.getData().text);
if (onMessageCallbacks[message.channel]) {
onMessageCallbacks[message.channel](message.message);
}
}
});
}
});
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
return {
send: function (message) {
client.publish({
channel: '/chat',
data: {
username: connection.userid,
text: JSON.stringify({
message: message,
channel: channel
})
}
});
}
};
};
// end-using WebSync for signaling
// ------------------------------------------------------------------
// check existing sessions
connection.connect();
// open new session
document.getElementById('open-new-session').onclick = function() {
connection.open();
};
=
First Step: Create Hub class:
public class WebRtcHub3: Hub {
public void Send(string message) {
Clients.All.onMessageReceived(message);
}
}
Second Step: Client side stuff:
var onMessageCallbacks = {};
var connection = new RTCMultiConnection();
var hub = $.connection.webRtcHub3;
$.support.cors = true;
$.connection.hub.url = '/signalr/hubs';
hub.client.onMessageReceived = function (message) {
var message = JSON.parse(message);
if (onMessageCallbacks[message.channel]) {
onMessageCallbacks[message.channel](message.message);
}
};
// start the hub
$.connection.hub.start();
Third Step: Overriding openSignalingChannel
method:
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
return {
send: function (message) {
message = JSON.stringify({
message: message,
channel: channel
});
hub.server.send(message);
}
};
};
=
new window.Firebase('https://' + connection.firebase + '.firebaseIO.com/' + connection.channel).once('value', function (data) {
var isRoomPresent = data.val() != null;
if (!isRoomPresent) {
connection.open(connection.sessionid);
} else {
connection.join(connection.sessionid);
}
});
var socket = io.connect('/');
socket.on('presence', function (isChannelPresent) {
if (!isChannelPresent)
connection.opne(connection.sessionid);
else
connection.join(connection.sessionid);
});
socket.emit('presence', channel);
Socket.io over Node.js demos can be found here.
var SIGNALING_SERVER = 'wss://wsnodejs.nodejitsu.com:443';
var websocket = new WebSocket(SIGNALING_SERVER);
websocket.onmessage = function (event) {
var data = JSON.parse(event.data);
if (data.isChannelPresent == false) {
connection.open(connection.sessionid);
} else {
connection.join(connection.sessionid);
}
};
websocket.onopen = function () {
websocket.send(JSON.stringify({
checkPresence: true,
channel: connection.channel
}));
};
WebSocket over Node.js demos can be found here.
=
// database has a single table; which has two columns:
// 1) Message (required to store JSON data)
// 2) ID (optional: as primary key)
// a simple function to make XMLHttpRequests
function xhr(url, callback, data) {
if (!window.XMLHttpRequest || !window.JSON) return;
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (callback && request.readyState == 4 && request.status == 200) {
// server MUST return JSON text
callback(JSON.parse(request.responseText));
}
};
request.open('POST', url);
var formData = new FormData();
// you're passing "message" parameter
formData.append('message', data);
request.send(formData);
}
// this object is used to store "onmessage" callbacks from "openSignalingChannel handler
var onMessageCallbacks = {};
// this object is used to make sure identical messages are not used multiple times
var messagesReceived = {};
function repeatedlyCheck() {
xhr('/Home/GetData', function (data) {
// if server says nothing; wait.
if (data == false) return setTimeout(repeatedlyCheck, 400);
// if already receied same message; skip.
if (messagesReceived[data.ID]) return setTimeout(repeatedlyCheck, 400);
messagesReceived[data.ID] = data.Message;
// "Message" property is JSON-ified in "openSignalingChannel handler
data = JSON.parse(data.Message);
// don't pass self messages over "onmessage" handlers
if (data.sender != connection.userid && onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
}
// repeatedly check the database
setTimeout(repeatedlyCheck, 1);
});
}
repeatedlyCheck();
// overriding "openSignalingChannel handler
connection.openSignalingChannel = function (config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
// let RTCMultiConnection know that server connection is opened!
if (config.onopen) setTimeout(config.onopen, 1);
// returning an object to RTCMultiConnection
// so it can send data using "send" method
return {
send: function (data) {
data = {
channel: channel,
message: data,
sender: connection.userid
};
// posting data to server
// data is also JSON-ified.
xhr('/Home/PostData', null, JSON.stringify(data));
},
channel: channel
};
};
Source code is available here: https://github.com/muaz-khan/XHR-Signaling
Remember: You can use same code JavaScript code both for PHP and ASP.NET.
=
You can find many other good examples here:
http://www.RTCMultiConnection.org/docs/openSignalingChannel/
=
- https://www.webrtc-experiment.com/docs/WebRTC-Signaling-Concepts.html
- http://www.RTCMultiConnection.org/FAQ/
- http://www.RTCMultiConnection.org/docs/sessionid/
- http://www.RTCMultiConnection.org/docs/channel-id/
=
WebRTC Experiments are released under MIT licence . Copyright (c) Muaz Khan.