diff --git a/assets/js/components/calls/callWindow.js b/assets/js/components/calls/callWindow.js index 98013e0b..5c1add66 100644 --- a/assets/js/components/calls/callWindow.js +++ b/assets/js/components/calls/callWindow.js @@ -38,7 +38,35 @@ ComunicWeb.components.calls.callWindow = { signalClient: undefined }; - //We have to begin to draw conversation UI + + + /** + * Initialize utilities + */ + + /** + * Get member information based on call ID + * + * @param {String} id The ID of the peer to process + * @return Information about the peer / empty object + * if no peer found + */ + call.getMemberByCallID = function(id){ + var memberInfo = undefined; + call.info.members.forEach(function(member){ + + if(member.user_call_id == id) + memberInfo = member; + + }); + return memberInfo; + } + + + + /** + * We have to begin to draw conversation UI + */ var callContainer = createElem2({ appendTo: byId("callsTarget") ? byId("callsTarget") : byId("wrapper"), //If call target is not found, add call in page wrapper type: "div", @@ -112,7 +140,7 @@ ComunicWeb.components.calls.callWindow = { - + /** * Create loading message area */ @@ -146,6 +174,7 @@ ComunicWeb.components.calls.callWindow = { * users */ call.setLoadingMessage = function(message){ + call.setLoadingMessageVisibility(true); call.window.loadingMessageContent.innerHTML = message; } @@ -155,42 +184,49 @@ ComunicWeb.components.calls.callWindow = { //Load user media call.setLoadingMessage("Waiting for your microphone and camera..."); - ComunicWeb.components.calls.userMedia.get() - .then(function(stream){ + ComunicWeb.components.calls.userMedia.get().then(function(stream){ + + //Check if connection has already been closed + if(!call.open) + return; //Mark as connecting call.setLoadingMessage("Connecting..."); call.streams.local = stream; - return true; - - }) - - //Start to automaticaly refresh information the call - .then(function(){ - - var interval = setInterval(function(){ - - if(!call.open) - return clearInterval(interval); - - ComunicWeb.components.calls.callWindow.refreshInfo(call); - - }, 4000); + //Initialize signaling server connection + ComunicWeb.components.calls.callWindow.initializeConnectionToSignalingServer(call); return true; - }) - - .catch(function(e){ + }).catch(function(e){ console.error("Get user media error: ", e); call.setLoadingMessageVisibility(false); return notify("Could not get your microphone and camera!", "danger"); }); + /** + * Start to automaticaly refresh information about the call + */ + var interval = setInterval(function(){ - //Initialize connection to signaling server + if(!call.open) + return clearInterval(interval); + + ComunicWeb.components.calls.callWindow.refreshInfo(call); + + }, 4000); + + }, + + + /** + * Initialize connection to signaling server + * + * @param {Object} call Information about the call + */ + initializeConnectionToSignalingServer: function(call) { //Get current user call ID call.info.members.forEach(function(member){ @@ -199,12 +235,46 @@ ComunicWeb.components.calls.callWindow = { call.localPeerID = member.user_call_id; }); + + //Create client instance and connect to server var config = ComunicWeb.components.calls.controller.getConfig(); call.signalClient = new SignalExchangerClient( config.signal_server_name, config.signal_server_port, call.localPeerID ); + + + /** + * Error when connecting to signaling server + */ + call.signalClient.onError = function(){ + call.setLoadingMessage("Could not connect to signaling server!"); + call.open = false; + }; + + /** + * Connection to signaling server is not supposed to close + */ + call.signalClient.onClosed = function(){ + call.setLoadingMessage("Connection to signaling server closed!"); + call.open = false; + } + + /** + * A remote peer sent a ready notice + */ + call.signalClient.onReadyMessage = function(peerID){ + ComunicWeb.components.calls.callWindow.readyToInitialize(call, peerID); + } + + /** + * A remote peer sent a signal + */ + call.signalClient.onSignal = function(signal, peerID){ + ComunicWeb.components.calls.callWindow.receivedSignal(call, peerID, signal); + } + }, /** @@ -234,8 +304,9 @@ ComunicWeb.components.calls.callWindow = { */ gotNewCallInfo: function(call) { - //Check if we are connected to signaling server - if(!call.signalClient.isConnected()) + //Check if we are connected to signaling server and we have got local + //streams + if(!call.signalClient || !call.signalClient.isConnected() || !call.streams.local) return; //Check if all other members rejected call @@ -255,6 +326,150 @@ ComunicWeb.components.calls.callWindow = { return; } + + //Process the connection to each accepted member + call.info.members.forEach(function(member){ + + //Ignores all not accepted connection + if(member.userID == userID() || member.accepted !== "accepted") + return; + + //Check if we have not peer information + if(!call.streams.hasOwnProperty("peer-" + member.userID)){ + + //If the ID of the current user is bigger than the remote + //peer user ID, we wait a ready signal from him + if(member.userID < userID()) + return; + + //Else we have to create peer + ComunicWeb.components.calls.callWindow.createPeerConnection(call, member, false); + } + + }); + + }, + + /** + * Create a peer connection + * + * @param {Object} call Information about the peer + * @param {Object} member Information about the member + * @param {Boolean} isInitiator Specify whether current user is the + * initiator of the connection or not + */ + createPeerConnection: function(call, member, isInitiator){ + + var peerConnection = { + peer: undefined + }; + call.streams["peer-" + member.userID] = peerConnection; + + //Get call configuration + var config = ComunicWeb.components.calls.controller.getConfig(); + + //Create peer + var peer = new SimplePeer({ + initiator: isInitiator, + stream: call.streams.local, + trickle: false, + config: { + 'iceServers': [ + { urls: config.stun_server }, + {"url": config.turn_server, + "credential": config.turn_username, + "username": config.turn_password} + ] + } + }); + peerConnection.peer = peer; + + var removePeerConnection = function(){ + peer.destroy(); + delete call.streams["peer-" + member.userID]; + } + + + peer.on("error", function(err){ + console.error("Peer error !", err, member); + removePeerConnection(); + }); + + peer.on("signal", function(data){ + console.log('SIGNAL', JSON.stringify(data)); + call.signalClient.sendSignal(member.user_call_id, JSON.stringify(data)); + }); + + peer.on("message", message => { + console.log("Message from remote peer: " + message); + }); + + + peer.on("close", function(){ + removePeerConnection(); + }); + + peer.on("stream", function(stream){ + alert("Remote stream available!"); + }); + + + //If this peer does not initialize connection, inform other peer we are ready + if(!isInitiator) + call.signalClient.sendReadyMessage(member.user_call_id); + }, + + + /** + * This method is called when a remote peers notify it is ready to + * establish connection + * + * @param {Object} call Information about the call + * @param {String} peerID Remote peer ID + */ + readyToInitialize: function(call, peerID){ + + var member = call.getMemberByCallID(peerID); + if(member == undefined) + return; + + //It the user with the smallest ID who send the ready message + //else it would mess everything up + if(member.userID > userID()) + return; + + this.createPeerConnection(call, member, true); + }, + + /** + * This method is called when we received a remote signal + * + * @param {Object} call Information about the call + * @param {String} peerID Remote peer ID + * @param {String} signal Received signal + */ + receivedSignal: function(call, peerID, signal){ + + console.log("Received signal from " + peerID, signal); + + var member = call.getMemberByCallID(peerID); + if(member == undefined) + return; + + //Check we have got peer information + if(!call.streams.hasOwnProperty("peer-" + member.userID)) + return; + + call.streams["peer-" + member.userID].peer.signal(JSON.parse(signal)); + }, + + /** + * + * @param {Object} call Information about remote call + * @param {*} stream + */ + streamAvailable: function(call, stream){ + }, /**