Imitation of the radio using Freeswitch and a little about voip-conference

    It even happens that you need to get a monster like Freeswitch to work on the principle of an ordinary walkie-talkie.
    One says everyone is listening.

    And NodeJs and npm will help us with this modesl module for interacting with Freeswitch.




    At some point in our organization, in a large wireless project, the customer needed to emulate the behavior of the walkie-talkie on top of voip-telephony. The system was based on Freeswitch. In general, the communication system is organized on the basis of the mesh network and accordingly there is no centralized server, each node has its own instance of Freeswitch, which is responsible for various voice scenarios.

    Task


    As you know, everything is very simple in the radio: one speaks and everyone listens, which is what needs to be implemented. It is also necessary to make a conference call for several independent groups, and any subscriber can simultaneously be in several of them. And of course, there should be targeted calls on the network.
    Available:



    General scheme


    The logic is, of course, strange and confusing, but since the customer asks, we must do it. An example structure looks like this:
    The general structure of voice communication.

    Sip client - it can be both mod_portaudio and linphone or for example baresip.

    Local Conference is an internal conference for each node, the task of which is to support the cunning logic of working with walkie-talkie emulation and switching between global conferences.

    Global Conference is a general conference, there can be several of them, which will allow to unite different users into different groups.

    Training


    How to connect modesl


    You can connect modesl as follows:
    var esl = require( "modesl");
    var localServer = "localhost";
    var localServerPort = 8021;
    var localServerUser = "ClueCon";
    var connectionCallback = function() {
    	//соединение создано, можно работать
    	connection.on( "esl::end", function( event) {
    		//обрабатываем завершение соединения, например можно переподключиться
    	});
    }
    //создаем соединение
    var connection = new esl.Connection( localServer, localServerPort, localServerUser, connectionCallback);
    connection.once( "error", function() {
    	//обрабатываем ошибки соединения
    });
    


    Connection features in modesl:
    • Subscribe to events from Freeswitch. Here's an example of a subscription to all events:
      connection.on( "esl::event::**", function( event) {});
    • Synchronous Freeswitch Command Call
      Connection.prototype.api = function(command, args, cb)
    • Asynchronous Freeswitch Command Call
      Connection.prototype.bgapi = function(command, args, jobid, cb)


    How to work with conferences in Freeswitch


    A conference can be created simply by writing everything in the xml config, or you can do this by transferring all control from the xml config to a specific address and port. We will choose the 2nd option.
    In Freeswitch configs in the dialplan / public.xml file, you need to write something like:

    This means that if they call 5555, then redirect control to the address 127.0.0.1:8087.
    You should also write a script to create a conference:
    var esl = require('modesl');
    var esl_server = new esl.Server({port: 8087, myevents:true}, function(){
    	console.log("ConferenceServer server is up");
    });
    esl_server.on( 'connection::ready', function( conn, id) {
    	console.log( 'ConferenceServer new call', id);
    	conn.execute( 'conference', 'ConfName@default', function( err, result){
    		console.log( arguments);
    	});
    	conn.on('esl::end', function( evt, body) {
    		console.log( "ConferenceServer call ended ", id);
    	});
    });
    


    Implementation


    Conference entry


    Initially, when the node starts, a call to the local conference occurs. To enter any of the global conferences, the node makes a call from the Local Conference to the Global Conference. In Freeswitch, this can be done like this :
    fs_cli
    conference [conference name] dial sofia/internal/[sip address]
    

    nodejs
    	self.dial = function( sipAddress, callback){
    		self.connection.bgapi( "conference", conferenceName + " dial sofia/internal/" + sipAddress, function( result){
    			var resultId = result.getBody().indexOf( "SUCCESS")
    			if( resultId == -1){
    				var body = result.getBody();
    				var startIndex = body.indexOf( '[');
    				var result = body.substring( startIndex + 1, body.length - 2);
    				callbackHelper.call( callback, "Conference call error: " + result);
    			}
    			else
                    		callbackHelper.call( callback, null);
    		});
    	};
    

    This is a very interesting and convenient feature that allows you to combine different conferences into one.
    Next, we do a deaf for this Global Conference inside the Local Conference (this will allow us to make sure that the various global conferences into which the node is included do not hear each other).
    You can do it like this :
    fs_cli
    conference [conference name] deaf [memberId]
    

    nodejs
    	self.deaf = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " deaf " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    

    Where did memberId come from? You can get it by signing up for the conference_add_member event .

    Conversation


    On each node, before the start of the conversation, the Local Conference has the following form:
    The status of the local conference.

    The host hears everyone, and global conferences do not hear each other.
    When a node needs to say something in one of the global conferences, you must first make everyone a participant except himself mute . This is done simply by going over the list of participants with this function.
    	self.mute = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " mute " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    
    .
    Then you need to make undeaf for that global conference where the node will talk
    	self.undeaf = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " undeaf " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    
    .
    As a result, we schematically obtain the following:
    Scheme at the time of conversation.


    Since all global conferences are mute, an active global conference (one made by undeaf) will not be heard by others. When the conversation ends, we will return everything to its previous state.

    This is how you can summarize the code for working with conference participants.
    function FsConferenceAPI( connection){
    	var self = this;
    	self.connection = connection;
    	self.unmuteAll = function( conferenceName){
    		self.connection.bgapi( "conference", conferenceName + " unmute all", function( result){});
    	};	
    	self.dial = function( sipAddress, callback){
    		self.connection.bgapi( "conference", conferenceName + " dial sofia/internal/" + sipAddress, function( result){
    			var resultId = result.getBody().indexOf( "SUCCESS")
    			if( resultId == -1){
    				var body = result.getBody();
    				var startIndex = body.indexOf( '[');
    				var result = body.substring( startIndex + 1, body.length - 2);
    				callbackHelper.call( callback, "Conference call error: " + result);
    			}
                else
                    callbackHelper.call( callback, null);
    		});
    	};
    	self.kick = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " kick " + memberId, function( result){
    			var body = result.getBody();
    			if( body.indexOf( "OK kicked " + memberId) != -1)
    				callbackHelper.call( callback, null);
    			else
    				callbackHelper.call( callback, body);
    		});
    	};
        self.kickAll = function( conferenceName, callback){
    		self.connection.bgapi( "conference", conferenceName + " kick all", function( result){
    			var body = result.getBody();
    			if( body.indexOf( "OK kicked") != -1)
    				callbackHelper.call( callback, null);
    			else
    				callbackHelper.call( callback, body);
    		});
    	};
    	self.deaf = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " deaf " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    	self.undeaf = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " undeaf " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    	self.mute = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " mute " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    	self.unmute = function( conferenceName, memberId, callback){
    		self.connection.bgapi( "conference", conferenceName + " unmute " + memberId, function( result){
    			callbackHelper.call( callback, null);
    		});
    	};
    }
    


    Now that all the basic functionality is ready, just write a high-level logic of work. But this is a topic for a separate article :).

    conclusions



    The scheme turned out to be quite large and confusing.
    It is possible to solve this problem without using local conferences for each node, but you will have to make targeted calls to all global conferences in which we want to participate. In this case, we will encounter another problem: the sip client should not put calls on hold and it will be necessary to use switching between active calls. Such a scheme will also have its pros and cons.
    An important conclusion is that Freeswitch is a unique tool that allows you to implement a wide variety of schemes for working with voice.

    Also popular now: