Jitsi Phone Dial-in and IVR Connector

Jitsi Phone Dial-in and IVR Connector

Jitsi Meet is a popular open source project for video conferencing and remote meetings. Users can use the service freely at meet.jit.si. Alternatively, those with the expertise can install and run Jitsi Meet on their own servers, allowing installers to fully control the service on infrastructure they control. In addition to video communication, Jitsi Meet also allows users to call in to a meeting and users a conference can dial-out to add a phone user. 

The meet.jit.si service uses Voximplant for inbound dialing and its Interactive Voice Response (IVR) functions. This guide shows how Jitsi Meet self-installers can use Voximplant to replicate the call-in and IVR functionality found on meet.jit.si and how to add outbound dialing from Meet. We start with some architecture for context and then provide a step-by-step guide with easy to reuse code for setting up Jigasi (Jitsi’s SIP gateway) and Voximplant’s environment. 

Many thanks to the Jitsi team, especially Damian Minkov for sharing their environment details and review of this post. 

Architecture

Jitsi Meet has many internal components that allow it to be scaled to support tens of millions of users. When you consider all the individual elements it looks very complex, but keep in mind most deployments use an all-in-one Jitsi Meet install that combines many of these components on the same server without exposing them.  Our goal here is to connect Public Switched Telephone Network (PSTN) phone network callers with Jitsi Meet mobile and web users.  To keep this post focused, we will just focus on the parts that matter for adding PSTN services with Voximplant. Those are the blue boxes in the diagram below.

Jitsi Meet with Jigasi

Jitsi Meet is a front-end web application that uses WebRTC media and XMPP for signaling. Jigasi is a SIP gateway service designed to work with Jitsi Meet. Jigasi takes SIP signaling and RTP media from SIP devices and converts that to Jitsi’s XMPP signaling with WebRTC-compatible media.  For inbound calls - i.e., those coming from the PSTN phone network into Jitsi Meet - Jigasi connects to the appropriate Meet server and its media is relayed by the Jitsi Video Bridge (JVB) component. For outbound calls - those initiated by a Jitsi Meet user to the phone network - the Jicofo component will discover available Jigasi units and connect to one.

Voximplant

Voximplant is a Communications Platform as a Service (CPaaS) that provides voice, video, and messaging APIs for building a near limitless variety of communications applications. The core to its service is a serverless execution environment that lets you control calls with only JavaScript called VoxEngine. Like other popular serverless environments such as AWS’s Lamda, VoxEngine includes a cloud-based Integrated Development Environment (IDE) for loading and modifying code. Voximplant also has HTTP API for provisioning, configuration, loading code, and gathering Call Detail Records (CDRs).

In this architecture, VoxEngine is used to: 

1. Receive incoming calls and gather bridge ID and password information via an IVR
2. Send outgoing calls from Jigasi to the PSTN
3. Manage mute and kick functions for those PSTN callers during the call 

Jigasi Optimization features

To help maximize the performance of the Jigasi gateways, Voximplant has implemented some unique features. For example, Voximplant supports intrasession SSRC mixing. This feature multiplexes multiple media streams into the same session, helping to reduce memory and port management processing in Jigasi. 

In addition, Voximplant supports RTP Header Extensions for Client-to-Mixer Audio Level Indication (RFC6464). That mouthful of a spec means Voximplant sends audio-level information to Jitsi Meet. Jitsi Meet uses this as part of its last-N protocol to identify who the active speaker is and to change the view as needed. Voximplant offloads this processing from Jigasi, helping to make it more scalable.  

Conference Info Services

Jitsi Meet’s user interface is set up assuming users can call a single pool of dial-in numbers and connect to any conference on the system. To do this, the meet.jit.si service uses a number mapper service to link each created conference - i.e. meet.jit.si/voximplant - to a unique numeric PIN that does not change as long as it is used once every 30 days. This PIN is designed to be easy to key in on the phone via DTMF by the caller so that they can join the right conference. 

When a conference is started on the web, Meet submits the URL to the number mapper and the PIN is returned. If no PIN exists then a new one is created. Jitsi Meet then displays this information when a user clicks on conference info or tries to share the meeting details. When a PSTN user calls in, VoxEngine makes a HTTP call to retrieve the PIN information and then passes the correct conference room in a SIP header. 

In addition, a phone number list service is used to retrieve one or more dial-in numbers that can be used to dial-into the service. While not strictly required in your own Jitsi Meet service, the Meet UI expects a phone number list service to be configured if Jigasi is enabled. 

How to Setup Jigasi with Voximplant

This next set of instructions is a simplified version of what the Jitsi team has implemented for meet.jit.si, but adapted for a standard Jitsi Meet installation. The instructions assume that you:

1. Already have Jitsi Meet installed.
2. Have access to navigate and configure your Jitsi Meet installation.
3. Have rudimentary JavaScript coding skills.
4. Are running an all-in-one Jitsi configuration and are ok adding a single Jigasi server on the same machine.

We will break these instructions into three major sections:

1. Setting up Voximplant - some basic provisioning and a high-level code walkthrough.
2. Configuring the Conference Info Services - we will do the minimum required here.
3. Configuring Jigasi.

Values from each of these steps will be required in the other steps, so be prepared to jump between systems. I try to minimize the effort with the order provided, but it is ok to jump between steps - i.e. you could install Jigasi first and go back later and enter the appropriate SIP parameters.

Setting up Voximplant 

For general information on getting started with Voximplant, check out the quick start here. Here we will provide a short step-by-step guide on how to setup Voximplant for use with Jigasi.   

1. Go to voximplant.com and create an account if you don’t have one already.
2. Purchase a phone number.

Go to the main menu in the upper left, select numbers, and click to buy a new one:

You can also get a free test number that requires that you enter a PIN code (at $1/month for a US number I prefer to just use a real number). 

3. Create an application and open it

4. Assign the number to the application.

Select “All Rules” for now.

5. Create a user.

This creates SIP user credentials that Jigasi will use to register with Voximplant.

In addition to creating a user for Jigasi, we also recommend creating a second user for testing with Jitsi Desktop. Jitsi Desktop is a desktop SIP client application that uses the same SIP stack and most of the same settings as Jigasi and is helpful for SIP testing to Voximplant independent of all the Meet components. 

6. Create an inbound scenario.

Go to scenarios and create a new Jigasi Inbound scenario for handling inbound calls from the PSTN to Jigasi. 

Cut & paste all the code from here into the scenario. 

7. Create an outbound scenario.

Repeat the procedure above for outbound. The code is available here. We will talk more about what this code is doing in the VoxEngine Code Walkthrough section.  

8. Create a mute IVR scenario

Do this one more time for the mute IVR using this code

9. Configure Routing.

The last thing we need to do is configure how incoming calls are handled. Go to Routing and create a new rule by clicking New Rule in the upper right corner.

Inbound rule

Let’s start by creating a rule for calls from the PSTN. Voximplant routes calls based on the SIP “to” field. For calls from the PSTN, the to field will be the PSTN number we just purchased. Enter that number into the pattern box:

Note: if you have multiple numbers assigned, you can use a regular expression to match additional inbound numbers.

Select the muteIVR scenario from the Available Scenarios dropdown and then add the Jigasi Inbound scenario you created earlier. Note the order is important here. Parts of the VoxEngine code are global in scope the inbound scenario expects global variables from the muteIVR scenario will be available prior to loading.

Outbound Rule

Repeat the same steps for the outbound scenario from Jigasi to the PSTN. If you want your users to have the ability to call any phone number in the world, you can put the .* regular expression.  In the example below, we limit calls to the United States only based on the +1 country code: 

Rule order

We now have 2 rules — one will forward calls dialed to our inbound number and the other lets all US numbers dial out. Voximplant will execute these rules in order. Make sure you have the inbound rule that filters on a specific number (or numbers) before the outbound  first — otherwise any incoming call could get picked up by the outbound rule!   

VoxEngine Code Walkthrough

All the code you need is located in Voximplant’s jitsi-connector GitHub repo. Make sure you copy the code from there as that will be updated more frequently than this post.

There are only a few variables you will need to populate for your deployment. We will highlight some of the major parts of the code below to give you an idea of what it is doing. 

Outbound Jigasi to PSTN  Scenario

Let’s start with the outbound scenario since that is a simple one that doesn’t use any IVR interaction.

Globals and constants

We start with some global variables to hold our call objects and some configuration options:

let jigasiCall,      // holder for the call from Jigasi
   userCall,        // holder for the PSTN call to the user
   confInputMenu;

/*** Adjust these constants for your installation ***/
const OUTBOUND_CALL_DURATION_LIMIT = 5 * 60 * 1000;     // Max call duration allowed
const TTS_VOICE = Language.US_ENGLISH_FEMALE;           // Choose your preferred voice
// Enter the DIDs assigned to Jigasi here
const DIDS = [
   '18572707025'
];

 

Make sure you populate the phone numbers you purchased with Voximplant under `DIDS`. You are probably starting with a single number, but don’t forget you can add local numbers for additional countries. In the case of calling out from Jitsi Meet to a user, if you had a French number populated above then if you called a user in France the French number would show on the caller ID.

Answering the call from Jigasi

VoxEngine handles each leg of the call independently. When the user enters a phone number to call, Jigasi initiates an outbound SIP call to VoxEngine. First we need to answer that SIP call leg:

// Kick things off when we have an incoming call event from Jigasi
VoxEngine.addEventListener(AppEvents.CallAlerting,  e => {
Logger.write("DEBUG: inbound call from Jigasi");
jigasiCall = e.call;
jigasiCall.answer({}, {mixStreams: "mix", audioLevelExtension: true});

// we need to make sure we do not miss any received info (start muted)

jigasiCall.addEventListener(CallEvents.InfoReceived, info => handleReceivedInfo(info));

});

Once we have answered, we read a message to the Jitsi Meet bridge to let them know the call is going through. We use an event listener at the end of that playback to initiate the outbound call:

 // Play a prompt and start the outbound call
   jigasiCall.addEventListener(CallEvents.PlaybackFinished, startOutboundCall);
   jigasiCall.say(`Calling ${jigasiCall.number().slice(2,).split('').join(" ")}. ` +
       `Your call will be automatically ended in ${OUTBOUND_CALL_DURATION_LIMIT / (60 * 1000)} minutes.`);

Adding the outbound call leg

We wait for a callback that is triggered when the previous prompt finishes. That callback simply dials out to the PSTN leg:

function startOutboundCall(){
   Logger.write("DEBUG: starting outbound call to user");

   // Choose an outbound callerID to use for the call in case DIDs has more than one
   let didIndex = DIDS.findIndex(
       did => PhoneNumber.getInfo(did).region === PhoneNumber.getInfo(jigasiCall.number()).region);
   let callerId = DIDS[didIndex] ? DIDS[didIndex] : DIDS[0];

   // Call the PSTN number
   userCall = VoxEngine.callPSTN(jigasiCall.number(), callerId);

   // Handle outbound call events
   userCall.addEventListener(CallEvents.Ringing, () => jigasiCall.ring());
   userCall.addEventListener(CallEvents.InfoReceived,
       e => jigasiCall.sendInfo(e.mimeType, e.body, e.headers) );
   userCall.addEventListener(CallEvents.Failed, VoxEngine.terminate);
   userCall.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
}

Pass control to the muteIVR

As we will discuss soon, after the call is set up we need to start the In-call mute IVR to watch for mute/unmute signals which we will cover shortly. We can do that by starting a new InputMenu instance and starting it:

userCall.addEventListener(CallEvents.Connected, ()=>{
       timeoutHandler(userCall, OUTBOUND_CALL_DURATION_LIMIT);

       // Start the in-call muteIVR scenario
       VoxEngine.sendMediaBetween(jigasiCall, userCall);
       startIVR(userCall, jigasiCall);

   });

Inbound PSTN to Jigasi Scenario

The inbound scenario is similar to the outbound example, except it adds an IVR and a HTTP request to our Number Mapper service to lookup the proper conference URL. 

Set the Constants

Make sure to specify the Voximplant user you created above and adjust the other parameters as needed:

const INBOUND_CALL_DURATION_LIMIT = 1 * 60 * 1000;              // Max call duration allowed
const TTS_VOICE = Language.US_ENGLISH_FEMALE;                   // Choose your preferred voice
const VOXIMPLANT_USER = "jigasi";                               // Enter your Voximplant user name here
const MAPPER_URL = "https://api.jitsi.net/conferenceMapper";    // Enter your own URL or use the public Jitsi service
const PROMPTS_NUMBER_HANGUP = 5;                                // # of prompts to be played before dropping the call
const HTTP_REQUEST_TIMEOUT_SEC = 3;                             // How long ot wait before timing out HTTP requests

IVR

VoxEngine has a IVR module that we load using a require at the top of our program:

require(Modules.IVR);

IVRs are defined by establishing a number of states. In each state the developer defines a prompt for the user to respond to.  The user enters some information by entering some DTMF keys, the entry is analyzed, and then the program either finishes, moves onto a new state, or the state is repeated.  To create a new state, create a new IVRState class instance with a name, IVR parameters including prompt, and then a handler for analyzing the data and what to do if no input is provided.

Our inbound pre-conference IVR is simple with only 2 states:

1. conferenceNumber — have the user enter their conference number 
2. confgetpassword — have the user enter the conference passcode

We would need some other kind of service to determine if the given conference has a password set. For simplicity, we ask the user to enter # if there is no password. 

// State that waits for the digit conference number
// if valid conference number was specified it forwards call to this conference
let confNumberState = new IVRState("conferencenumber", {
   type: "inputunknown",
   terminateOn: "#",
   timeout: 10000,
   prompt: {
       say: "Please enter the meeting eye dee and press pound",
       lang: TTS_VOICE
   }
}, data => {
   // Input finished
   let number = data.replace("#", "");
   getConferenceUrl(number);
}, () => {
   // Timeout
   countInitialPromptPlayed++;
   if (countInitialPromptPlayed > PROMPTS_NUMBER_HANGUP)
       triggerPlaybackOnInboundCall("noInput",
           "We did not receive any input, please try again later.",
           VoxEngine.terminate);
   else
       confNumberState.enter(userCall);
});

// State to check and enter the conference password.
// Once the password its entered, it forwards the call to the conference.
let confGetPasswordState = new IVRState("confgetpassword", {
       type: "inputunknown",
       terminateOn: "#",
       timeout: 10000,
       prompt: {
           say: "If your conference included a password please enter it now, followed by pound. " +
               "Just press pound to enter the conference with out a password",
           lang: TTS_VOICE
       }
   }, data => {
       // Input finished
       password = data.replace("#", "");
       triggerPlaybackOnInboundCall("confPasswordDefault",
           "Connecting you to your conference, please wait.", handleConferenceSuccessPlaybackFinished);
   }, () => confGetPasswordState.enter(userCall) // Input Timeout
);

Number Mapper HTTP Call

VoxEngine has an httpRequest method you can use to make REST API calls. In this case we use the PIN number the user entered to make that HTTP call, save that info, and move along to the next IVR state.

// Check if the specified number is valid conference number
function getConferenceUrl(number) {

   // Handle the HTTP request to get the conference mapping
   function onResponse(res) {
       if (res.code === 200) {
           let result = JSON.parse(res.text);
           if (result.conference) {
               confId = number;
               confJID = result.conference;

               // Move to the next IVR state to check for a password
               confGetPasswordState.enter(userCall);

           } else {
               triggerPlaybackOnInboundCall("unknownConference",
                   "You have specified an unknown conference number.",
                   handleConferenceFailedPlaybackFinished);
           }
       } else {
           Logger.write(`Conference number confirmation call failed for cid: ${number} with status: ${res.code},` +
               `message: ${res.text}, headers ${JSON.stringify(res.headers)}`);
           triggerPlaybackOnInboundCall("lookupError",
               "Something went wrong confirming your conference number, please try again.",
               handleConferenceFailedPlaybackFinished);
       }
   }

   // Helper function for grabbing conferencing info
   let url = MAPPER_URL + "?cid=" + number;
   Net.httpRequest(url, e => {
       if (e.code === 200 || (e.code >= 400 && e.code < 500)) {
           onResponse(e);
       } else {
           Logger.write(`retrying ${url} because of error: ${e.code} -> ${e.error}`);
           // e.code can be <= 8 https://voximplant.com/docs/references/voxengine/net/httprequestresult#code
           // or any of HTTP code (2xx-5xx)
           Net.httpRequest(url, e => {
               if (e.code !== 200) {
                   Logger.write(`httpRequest error after 2nd attempt for ${url}: ${e.code} -> ${e.error}`);
               }
               onResponse(e);
           }, {timeout: HTTP_REQUEST_TIMEOUT_SEC});
       }
   }, {timeout: HTTP_REQUEST_TIMEOUT_SEC});
}

Passing Conference information to Jigasi to start the call

Lastly, we need to send the information gathered by the IVR to Jigasi. The incoming call handling (not shown here) is similar to how we do things in the outbound scenario.  For the outbound call leg, we need to add some SIP headers with the conference URL and passcode (if needed) when we initiate the call:

// These are needed to route the call to the correct room
let extraHeaders = {
   "X-Room-Name": confJID,
   "X-Domain-Base": true,
   "VI-CallTimeout": 1800
};

// Add the password header if it exists
if (password)
   extraHeaders['Jitsi-Conference-Room-Pass'] = password;

jigasiCall = VoxEngine.callUser({
   username: VOXIMPLANT_USER,
   callerid: userCall.callerid(),
   displayName,
   extraHeaders,
   mixStreams: "mix",
   audioLevelExtension: true
});

Note the use of mixStreams and audioLevelExtension options to optimize performance. 

muteIVR scenario

The muteIVR scenario handles:

  • IVR states for muting
  • starting the mute-control IVR
  • communicating audio state information with Jigasi
  • total call duration timeout

Let’s touch on a few of these.

IVR States

To let the phone user control their audio, we include two states - one for mute and one for unmute. These states just check for *6 and call the sendMuteRequest function if pressed.  We also have some logic to start the IVR which we called from our inbound and outbound scenarios.

const muteIvrState = new IVRState("muteToggle", {
   type: "inputfixed",
   inputLength: 2,
   timeout: repromptTime,
   prompt: {
       say: "You are muted, press star 6 to un mute"
   }
}, data => {
   Logger.write(`DEBUG: muteToggle IVR input was ${data} `);
   if (data === '*6')
       sendMuteRequestToCall(jigasiCall, false);
}, data => {
   Logger.write(`DEBUG: muteToggle IVR timeout ${data} `);
   muteIvrState.enter(userCall);
});

const unMuteIvrState = new IVRState("unMuteToggle", {
   type: "inputfixed",
   inputLength: 2,
   timeout: repromptTime,
   prompt: {
       say: "You are un muted, press star 6 to mute"
   }
}, data => {
   Logger.write(`DEBUG: unMuteToggle IVR input was ${data} `);
   if (data === '*6')
       sendMuteRequestToCall(jigasiCall, true);
}, data => {
   Logger.write(`DEBUG: unMuteToggle IVR timeout ${data} `);
   unMuteIvrState.enter(userCall);
});

// Start the IVR - specify the user-leg first followed by the jigasi-leg
function startIVR(user, jigasi) {
   Logger.write(`DEBUG: Initializing muteIVR. audioMuted is ${audioMuted}`);
   userCall = user;
   jigasiCall = jigasi;
   userCall.addEventListener(CallEvents.PlaybackFinished, () => VoxEngine.sendMediaBetween(userCall, jigasiCall) );

   // Check if the user should start muted
   if(audioMuted)
       muteIvrState.enter(userCall);
   else
       unMuteIvrState.enter(userCall);
}

Handling messages to and from Jigasi 

Rather than muting locally with Voximplant, we actually ask Meet to mute the call so the Meet UI shows that caller muted.  When *6 is pressed, SIP INFO message is sent to Jigasi using the call.sendInfo method and muteRequest message:

function sendMuteRequestToCall(call, muted) {
   let request = {
       type: "muteRequest",
       id: uuidgen(),
       data: {
           audio: muted
       }
   };

   let requestString = JSON.stringify(request);
   Logger.write("DEBUG: Sending mute request: " + requestString);
   call.sendInfo("application/json", requestString);
}

A similar function handles the muteResponse if the bridge asks the caller to mute. 

We also include a timeoutHandler helper function in this scenario to prevent calls from running beyond a specified limit. This helps to prevent abuse and missed hang-ups. It will pay a prompt and then terminate the session. 

function timeoutHandler(call, maxDuration){
   Logger.write(`DEBUG: Started call limit timeout after Call Connected Listener. Waiting for ${maxDuration} ms`);
   setTimeout(() => {
       Logger.write('DEBUG: call limit timeout reached');

       call.addEventListener(CallEvents.PlaybackFinished, VoxEngine.terminate );
       call.say('Time limit exceeded. Thank you for trying out our service.');

   }, maxDuration);
}

Lastly, we have a function that sets the mute state based on the information that Jigasi sends:

function handleReceivedInfo(e) {
   Logger.write("DEBUG::handleReceivedInfo");

   if (e.mimeType === 'application/json') {

       let obj = JSON.parse(e.body);

       if (obj.type && obj.type === "muteRequest") {
           sendMuteResponseToCall(e.call, obj);

           if (userCall && userCall.state() === 'CONNECTED') {
               audioMuted = obj.data.audio;
               Logger.write(`DEBUG: muteRequest with ${obj.id} succeeded. audioMuted state is ${audioMuted}.\n`);
               muteIvrState.enter(userCall);
           } else {
               audioMuted = true;
               Logger.write("DEBUG: muteRequest - outbound call is not connected yet, starting Muted");
           }
       } else if (obj.type && obj.type === "muteResponse") {
           if (obj.status && obj.status === "OK") {
               audioMuted = audioMuted !== true;
               Logger.write(`DEBUG: muteResponse with ${obj.id} succeeded. audioMuted state is ${audioMuted}.\n`);
               if(audioMuted)
                   muteIvrState.enter(userCall);
               else
                   unMuteIvrState.enter(userCall);

           } else if (obj.status
               && obj.status === "FAILED") {
               Logger.write(`DEBUG: muteResponse with ${obj.id} failed`);
           }
       }
       else {
           Logger.write(`DEBUG: message handle error message: ${JSON.stringify(obj)}`)
       }
   }
}

 

Conference Info Services Setup

The Jitsi team has a Swagger definition doc for phone number list and conference mapper HTTP endpoints.  In this example we will set up a simple static phone number response and reuse Jitsi’s public API’s for the conference mapper. 

Phone number list

We can examine how the phone number list service works with meet.jit.si: 

curl 
‘https://api.jitsi.net/phoneNumberList?conference=voximplant@conference.meet.jit.si’

Returns a JSON object with a list of phone numbers in the following format:

{"message":"Phone numbers available.","numbers":
{"US":["+1.512.402.2718"],"UK":
["+44.121.468.3154"],"France":
["+33.1.84.88.6478"],"Germany":
["+49.89.380.38719"],"Netherlands":
["+31.85.208.1541"],"Spain":
["+34.932.205.409"],"Canada":
["+1.647.243.6108"],"Australia":
["+61.8.7150.1136"],"Brazil":
["+55.21.3500.0112"],"Japan":
["+81.3.4510.2372"],"Switzerland":
["+41.61.588.0496"]},"numbersEnabled":true}

 

We just need something to return the phone number(s) we configured in the Voximplant setup. We could create something on the Meet Jitsi service to send this information, but for quick demonstration purposes we can use a service like Mocky.io to return the phone number JSON object:

Now a request to the indicated Mocky.io URL returns the following:

{
    "message": "Phone numbers available.", 
    "numbers":{"US":["+1.212.555.5555"]},
    "numbersEnabled":true
}

Make note of this URL for when we configure Jigasi. 

As an alternative to running your own number mapper service or using something like mocky.io, you can also create a local file on your Jitsi Meet server to host your numbers too. This works well for single system installs where your numbers won’t change often. On the Debian Jitsi Meet install, the default location for hosting static files is /usr/share/jitsi-meet/. Just place the JSON object in a file in that directory -i.e. phone_numbers.json and then you can access that file from https://meet.yourdomain.tld/phone_numbers.json.

Conference Mapper

As explained above, the conference mapper is more complex since it is expected to create a new unique PIN for each newly created conference. It needs to both receive requests from Jitsi Meet to return the PIN for a given room URL and also from VoxEngine to map a room URL to a user-entered PIN.

If you only plan to allow your users to call into a fixed number of conference URLs, then you could create a simple service something like shown in the Phone Number List above. Another option is just to reuse Jitsi’s publicly available API for this.

For example, if I want to find the ID for https://meet.jit.si/voximplant:  

curl 
'https://api.jitsi.net/conferenceMapper?conference=voximplant@conference.meet.jit.si'

 

{
  "message": "Successfully retrieved conference mapping",
  "id": 2279215447,
  "conference": "voximplant@conference.meet.jit.si"
}

 

The reverse operation to map a PIN to a URL looks like:

curl 
'https://api.jitsi.net/conferenceMapper?id=2279215447’

and gives the same response as above.

When you create the conference, just make sure to substitute your own domain name for “meet.jit.si” and you can use this API for your own Meet deployment. 

Note we used the conferenceMapper URL above as our MAPPER_URL in the inbound VoxEngine scenario. 

Jigasi Install and Configuration

Jigasi is relatively simple to install and configure. As we will show, you just need to run the install and edit some text files with the appropriate values.

Jigasi install

Jitsi’s quick install instructions have an option for installing Jigasi and the Jigasi repo has its own set here.  

Installing Jigasi from the Debian package is simple:

apt-get -y install jigasi

This will ask you for the SIP user name and password you created in step 5 of the Voximplant setup. 

Jigasi Configuration

The Jigasi configuration files are located in /etc/jitsi/jigasi in the standard Debian install. The settings you specified during the installation should be in /etc/jitsi/jigasi/config:

# Jigasi settings
JIGASI_SIPUSER=jigasi@jigasi-demo.demo.voximplant.com
JIGASI_SIPPWD=dDhiSHpRa3I=
JIGASI_SECRET=A#LtRa5V
JIGASI_OPTS=""
JIGASI_HOSTNAME=meet.voximplant.com
JIGASI_HOST=localhost

Note the password is hashed.

SIP Communicator properties

The more important file is /etc/jitsi/jigasi/sip-communicator.properties. Note most of the lines will have a unique account ID for your instance - make sure you change this to match your instance - i.e. the number after acc in net.java.sip.communicator.impl.protocol.sip.acc012345678901 needs to be updated.

Edit/add the following to that file:

net.java.sip.communicator.impl.neomedia.audioSystem.audiosilence.captureDevice_list=["AudioSilenceCaptureDevice:noTransferData"]
net.java.sip.communicator.impl.protocol.sip.acc012345678901.USE_TRANSLATOR_IN_CONFERENCE=true
org.jitsi.jigasi.xmpp.acc.USE_TRANSLATOR_IN_CONFERENCE=true
net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true

Finally, set we need to set a few items for VoxEngine: 

# VoxEngine
# Use standard X- header 
net.java.sip.communicator.impl.protocol.sip.acc012345678901.JITSI_MEET_ROOM_HEADER_NAME=X-Room-Name
# don’t overload Jigasi with focus changes
net.java.sip.communicator.impl.protocol.sip.SKIP_REINVITE_ON_FOCUS_CHANGE_PROP=true

# can be enabled to disable audio mixing and use translator, jigasi will act as jvb, just forward every ssrc stream it receives.
net.java.sip.communicator.impl.protocol.sip.acc012345678901.USE_TRANSLATOR_IN_CONFERENCE=true
org.jitsi.jigasi.xmpp.acc.USE_TRANSLATOR_IN_CONFERENCE=true
# allow a conference to start muted
org.jitsi.jigasi.ENABLE_SIP_STARTMUTED=true

 

Last, we should adjust our codec settings to force the use of OPUS for optimal performance:

net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.AMR-WB/16000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.G722/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.GSM/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.H263-1998/90000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.H264/90000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.PCMA/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.PCMU/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.SILK/12000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.SILK/16000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.SILK/24000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.SILK/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.VP8/90000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.iLBC/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.speex/16000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.speex/32000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.speex/8000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.red/90000=0
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.ulpfec/90000=0

Setting all these codecs to 0 ensures they are disabled. Make sure you are left with following:

net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.opus/48000=1000
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.Encodings.telephone-event/8000=1
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.OVERRIDE_ENCODINGS=true

Save that file and restart Jigasi.

sudo service jigasi restart

Configure Meet

Next, we need to tell Meet our Conference info URLs: 

nano /etc/jitsi/meet/[your-domain]-config.js

Find/uncomment/add and populate these values with what you specified from the Conference Info Services Setup:

     dialInNumbersUrl: "https://www.mocky.io/v2/5e96723b2fff000f03025a44",
     dialInConfCodeUrl: "https://api.jitsi.net/conferenceMapper",

Add the call out button to Meet

If you want to give your users the ability to dial-out from their conference, then you will need to add the add users icon to your Meet interface:

nano /usr/share/jitsi-meet/interface_config.js

Add ‘invite’ inside the TOOLBAR_BUTTONS option:

TOOLBAR_BUTTONS: [
        'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
        'fodeviceselection', 'hangup', 'profile', 'info', 'chat', 'recording',
        'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
        'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
        'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone',
        'e2ee', 'localrecording'
    ],


Try it!

Load up a Meet URL in your browser and give it a try! To dial out, just click the “+”:

Check to make sure your number with PIN shows in the conference info panel and give your number a call:

Make it your own

Now that you have an idea of how the system works, there are many optimizations you can make with Voximplant:

1. Add new numbers for other regions or countries
2. Whitelist incoming callers for certain Meet URLs without authorization
3. Add an authorization PIN for outbound calling
4. Add outbound SIP calling
5. Use speech recognition to say a room name instead of entering it
6. Connect to a PBX or even an existing conferencing system 

If you have trouble with your calls, make sure you look at the logs in VoxEngine’s call history and reference Voximplant’s extensive documentation. Of course you are welcome to try out Voximplant’s video calling features too. Happy calling with Jitsi Meet using Voximplant!

Tags:webrtcJigasi
B6A24216-9891-45D1-9D1D-E7359CEB8282 Created with sketchtool.

Comments(0)

Add your comment

Please complete this field.

Recommended

Sign up for a free Voximplant developer account or talk to our experts
SIGN UP