I am developing a conference style application (many-to-many) for video calls this style. The code is available on GitHub but I do not have much node.js experience, hence I decided to create my own server using PHP.
I created the server using WebSockets. It is simple - it receives messages and forwards them to all other connected clients (i.e., not the client that sent the message). Just that - nothing more; nothing less.
But my problem is that this architecture does not allow clients to connect with more than one person, i.e., when a client tries to connect with the third person, the additional streams fail. Clients can only connect one-to-one.
I do not know whether the error is in JavaScript or if I need to improve the server. What can I do to make it connect to all clients who join?
See my code:
HTML
<script type="text/javascript" src="http://127.0.0.1/scr/js/jquery.js"></script>
JavaScript
var Server = new WebSocket('ws://127.0.0.1:1805/'),
myStream = null,
peerConn = null,
mediaConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
};
navigator.webkitGetUserMedia({
audio: true,
video: true
}, function(stream) {
myStream = stream;
$("body").append('<video width="320" height="240" muted="muted" autoplay="true" src="' + window.URL.createObjectURL(stream) + '"></video>');
createPeerConnection();
peerConn.addStream(myStream);
peerConn.createOffer(function(sessionDescription) {
peerConn.setLocalDescription(sessionDescription);
console.log("Sending offer description");
Server.send(JSON.stringify(sessionDescription));
}, null, mediaConstraints);
}, function() {
console.error('Error in my stream');
});
function createPeerConnection() {
console.log('Creating peer connection');
peerConn = new webkitRTCPeerConnection({
'iceServers': [{
'url': 'stun:stun.l.google.com:19302'
}, {
'url': 'turn:107.150.19.220:3478',
'credential': 'turnserver',
'username': 'subrosa'
}]
}, {
'optional': [{
'DtlsSrtpKeyAgreement': 'true'
}]
});
peerConn.onicecandidate = function(event) {
if (event.candidate) {
Server.send(JSON.stringify({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
}));
} else {
console.error('Candidate denied');
}
};
peerConn.onaddstream = function(event) {
console.log("Adding remote strem");
$("body").append('<video width="320" height="240" autoplay="true" src="' + window.URL.createObjectURL(event.stream) + '"></video>');
};
peerConn.onremovestream = function(event) {
console.log("Removing remote stream");
};
}
Server.addEventListener("message", function(message) {
var msg = JSON.parse(message.data);
if(!myStream) {
console.error('Error in my stream');
}
if (msg.type === 'offer') {
createPeerConnection();
console.log('Adding local stream...');
peerConn.addStream(myStream);
peerConn.setRemoteDescription(new RTCSessionDescription(msg));
console.log("Sending answer to peer.");
peerConn.createAnswer(function(sessionDescription) {
peerConn.setLocalDescription(sessionDescription);
Server.send(JSON.stringify(sessionDescription));
}, null, mediaConstraints);
} else if (msg.type === 'answer') {
peerConn.setRemoteDescription(new RTCSessionDescription(msg));
} else if (msg.type === 'candidate') {
var candidate = new RTCIceCandidate({
sdpMLineIndex: msg.label,
candidate: msg.candidate
});
peerConn.addIceCandidate(candidate);
}
}, false);
RTCPeerConnection is inherently a one-to-one connection between two clients (peers), so if you want to go beyond that you have to get clever.
The simplest step up is creating a mesh, essentially setting up one PeerConnection to each of the other participants, with all of the participants doing the same thing. You're going to hit a wall in client upload speed pretty fast this way though, usually topping out at 3-4 participants, typically based on the participant with the lowest upload speed.
For groups bigger than that you'd probably want some special setup like an MCU or Router solution, where essentially a special server acts as a super-participant that everyone connects to, which then either mixes together a video of everyone (usually with whomever is speaking as a larger video), or forwards everyone's video to everyone (since upload speed is usually the bottleneck).
The problem is that you're trying to use a single Peer Connection, but that will only work for a single connected party. You'll have to have an additional peer connection for each other party, and be able to associate websocket messages with users and a particular peer connection. You could do this yourself, or use a library like SimpleWebRTC that manages multiple user sessions for you.
Edit:
A very simplified explanation of how SimpleWebRTC works is this, which is one option for creating a mesh network of connected clients (all clients are connected to each other client):
The critical difference in this architecture to yours is that you are creating a single peer connection, but you need to create, store, and track an array of peer connections, and you have to map your websocket messages to particular peers.