We make video conferences in the browser in 10 minutes
- Tutorial

Voximplant uses user profiles that can be created using the HTTP API. To demonstrate the video conferencing, we made a small application that, at a url prompt, asks for the name of the participant, creates a user profile, and returns authentication parameters https://github.com/voximplant .
Unlike sound, voximplant transmits video between participants, peer-to-peer, which corresponds to the mechanics of webRTC. To organize a conference, participants need to make a video connection to each other - this will work well for up to about ten users, which with a margin covers most work scenarios. And the sound will be automatically mixed with standard voximplant mechanisms. For the correct sound mixing, we will create two internal conferences: # 1 for video calls and # 2 for participants from regular phones:

Red arrows show audio and video streams between conference participants in a browser, and blue arrows show audio streams for participants from phones. One of the advantages of voximplant is the ability to work flexibly with different threads on the cloud side, which allows you to create a variety of solutions.
First, sign up at voximplant.com and create a new application called “videoconf”.
Then, in the settings of this application, create the first, simplest scenario. He will be responsible for sending p2p audio / video between web clients and is called “VideoConferenceP2P”:
code
VoxEngine.forwardCallToUserDirect();
The next scenario in telephony is called “gatekeeper” - it processes the call from the web client and then redirects it to the conference with the corresponding conferenceID received from the webSDK, plus it provides the transmission of text messages between the conference and the client, for notification of the connection of new participants. Call this scenario “VideoConferenceGatekeeper”:
code
/**
* Video Conference Gatekeeper
* Handle inbound calls and route them to the conference
*/var call,
conferenceId,
conf;
/**
* Inbound call handler
*/
VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {
// Get conference id from headers
conferenceId = e.headers['X-Conference-Id'];
Logger.write('User '+e.callerid+' is joining conference '+conferenceId);
call = e.call;
/**
* Play some audio till call connected event
*/
call.startEarlyMedia();
call.startPlayback("http://cdn.voximplant.com/bb_remix.mp3", true);
/**
* Add event listeners
*/
call.addEventListener(CallEvents.Connected, sdkCallConnected);
call.addEventListener(CallEvents.Disconnected, function (e) {
VoxEngine.terminate();
});
call.addEventListener(CallEvents.Failed, function (e) {
VoxEngine.terminate();
});
call.addEventListener(CallEvents.MessageReceived, function(e) {
Logger.write("Message Received: "+e.text);
try {
var msg = JSON.parse(e.text);
} catch(err) {
Logger.write(err);
}
if (msg.type == "ICE_FAILED") {
conf.sendMessage(e.text);
} elseif (msg.type == "CALL_PARTICIPANT") {
conf.sendMessage(e.text);
}
});
// Answer the call
call.answer();
});
/**
* Connected handler
*/functionsdkCallConnected(e) {
// Stop playing audio
call.stopPlayback();
Logger.write('Joining conference');
// Call conference with specified id
conf = VoxEngine.callConference('conf_'+conferenceId, call.callerid(), call.displayName(), {"X-ClientType": "web"});
Logger.write('CallerID: '+call.callerid()+' DisplayName: '+call.displayName());
// Add event listeners
conf.addEventListener(CallEvents.Connected, function (e) {
Logger.write("VideoConference Connected");
VoxEngine.sendMediaBetween(conf, call);
});
conf.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
conf.addEventListener(CallEvents.Failed, VoxEngine.terminate);
conf.addEventListener(CallEvents.MessageReceived, function(e) {
call.sendMessage(e.text);
});
}
The following scenario is for incoming calls from regular phones to the conference phone number, which can be rented in a couple of clicks through the voximplant interface. After the connection, the voice synthesizer will prompt the caller to enter the conference ID and make the connection. Call this scenario “VideoConferencePSTNgatekeeper”:
code
var pin = "", call;
VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {
call = e.call;
e.call.addEventListener(CallEvents.Connected, handleCallConnected);
e.call.addEventListener(CallEvents.Disconnected, handleCallDisconnected);
e.call.answer();
});
functionhandleCallConnected(e) {
e.call.say("Hello, please enter your conference pin using keypad and press pound key to join the conference.", Language.UK_ENGLISH_FEMALE);
e.call.addEventListener(CallEvents.ToneReceived, function (e) {
e.call.stopPlayback();
if (e.tone == "#") {
// Try to call conference according the specified pinvar conf = VoxEngine.callConference('conf_'+pin, e.call.callerid(), e.call.displayName(), {"X-ClientType": "pstn_inbound"});
conf.addEventListener(CallEvents.Connected, handleConfConnected);
conf.addEventListener(CallEvents.Failed, handleConfFailed);
} else {
pin += e.tone;
}
});
e.call.handleTones(true);
}
functionhandleConfConnected(e) {
VoxEngine.sendMediaBetween(e.call, call);
}
functionhandleConfFailed(e) {
VoxEngine.terminate();
}
functionhandleCallDisconnected(e) {
VoxEngine.terminate();
}
The last and largest scenario is responsible for creating two conferences, connecting and disconnecting participants, managing audio streams and deleting profiles of disconnected users that have become unnecessary. We will call this script “VideoConference”, if you copy the code from the example, do not forget to substitute your values “account_name” and “api_key”:
code
/**
* Require Conference module to get conferencing functionality
*/require(Modules.Conference);
var videoconf,
pstnconf,
calls = [],
pstnCalls = [],
clientType,
/**
* HTTP API Access Info for user auto delete
*/
apiURL = "https://api.voximplant.com/platform_api",
account_name = "your_voximplant_account_name",
api_key = "your_voximplant_api_key";
// Add event handler for session start event
VoxEngine.addEventListener(AppEvents.Started, handleConferenceStarted);
functionhandleConferenceStarted(e) {
// Create 2 conferences right after session to manage audio in the right way
videoconf = VoxEngine.createConference();
pstnconf = VoxEngine.createConference();
}
/**
* Handle inbound call
*/
VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) {
// get caller's client type
clientType = e.headers["X-ClientType"];
// Add event handlers depending on the client type if (clientType == "web") {
e.call.addEventListener(CallEvents.Connected, handleParticipantConnected);
e.call.addEventListener(CallEvents.Disconnected, handleParticipantDisconnected);
} else {
pstnCalls.push(e.call);
e.call.addEventListener(CallEvents.Connected, handlePSTNParticipantConnected);
e.call.addEventListener(CallEvents.Disconnected, handlePSTNParticipantDisconnected);
}
e.call.addEventListener(CallEvents.Failed, handleConnectionFailed);
e.call.addEventListener(CallEvents.MessageReceived, handleMessageReceived);
// Answer the call
e.call.answer();
});
/**
* Message handler
*/functionhandleMessageReceived(e) {
Logger.write("Message Recevied: " + e.text);
try {
var msg = JSON.parse(e.text);
} catch (err) {
Logger.write(err);
}
if (msg.type == "ICE_FAILED") {
// P2P call failed because of ICE problems - sending notification to retryvar caller = msg.caller.substr(0, msg.caller.indexOf('@'));
caller = caller.replace("sip:", "");
Logger.write("Sending notification to " + caller);
var call = getCallById(caller);
if (call != null) call.sendMessage(JSON.stringify({
type: "ICE_FAILED",
callee: msg.callee,
displayName: msg.displayName
}));
} elseif (msg.type == "CALL_PARTICIPANT") {
// Conference participant decided to add PSTN participant (outbound call)for (var k = 0; k < calls.length; k++) calls[k].sendMessage(e.text);
Logger.write("Calling participant with number " + msg.number);
var call = VoxEngine.callPSTN(msg.number);
pstnCalls.push(call);
call.addEventListener(CallEvents.Connected, handleOutboundCallConnected);
call.addEventListener(CallEvents.Disconnected, handleOutboundCallDisconnected);
call.addEventListener(CallEvents.Failed, handleOutboundCallFailed);
}
}
/**
* PSTN participant connected
*/functionhandleOutboundCallConnected(e) {
e.call.say("You have joined a conference", Language.UK_ENGLISH_FEMALE);
e.call.addEventListener(CallEvents.PlaybackFinished, function (e) {
for (var k = 0; k < calls.length; k++) calls[k].sendMessage(JSON.stringify({
type: "CALL_PARTICIPANT_CONNECTED",
number: e.call.number()
}));
VoxEngine.sendMediaBetween(e.call, pstnconf);
e.call.sendMediaTo(videoconf);
});
}
/**
* PSTN participant disconnected
*/functionhandleOutboundCallDisconnected(e) {
Logger.write("PSTN participant disconnected " + e.call.number());
removePSTNparticipant(e.call);
for (var k = 0; k < calls.length; k++) calls[k].sendMessage(JSON.stringify({
type: "CALL_PARTICIPANT_DISCONNECTED",
number: e.call.number()
}));
}
/**
* Call to PSTN participant failed
*/functionhandleOutboundCallFailed(e) {
Logger.write("Call to PSTN participant " + e.call.number() + " failed");
removePSTNparticipant(e.call);
for (var k = 0; k < calls.length; k++) calls[k].sendMessage(JSON.stringify({
type: "CALL_PARTICIPANT_FAILED",
number: e.call.number()
}));
}
functionremovePSTNparticipant(call) {
for (var i = 0; i < pstnCalls.length; i++) {
if (pstnCalls[i].number() == call.number()) {
Logger.write("Caller with number " + call.number() + " disconnected");
pstnCalls.splice(i, 1);
}
}
}
functionhandleConnectionFailed(e) {
Logger.write("Participant couldn't join the conference");
}
functionparticipantExists(callerid) {
for (var i = 0; i < calls.length; i++) {
if (calls[i].callerid() == callerid) returntrue;
}
returnfalse;
}
functiongetCallById(callerid) {
for (var i = 0; i < calls.length; i++) {
if (calls[i].callerid() == callerid) return calls[i];
}
returnnull;
}
/**
* Web client connected
*/functionhandleParticipantConnected(e) {
if (!participantExists(e.call.callerid())) calls.push(e.call);
e.call.say("You have joined the conference.", Language.UK_ENGLISH_FEMALE);
e.call.addEventListener(CallEvents.PlaybackFinished, function (e) {
videoconf.sendMediaTo(e.call);
e.call.sendMediaTo(pstnconf);
sendCallsInfo();
});
}
functionsendCallsInfo() {
var info = {
peers: [],
pstnCalls: []
};
for (var k = 0; k < calls.length; k++) {
info.peers.push({
callerid: calls[k].callerid(),
displayName: calls[k].displayName()
});
}
for (k = 0; k < pstnCalls.length; k++) {
info.pstnCalls.push({
callerid: pstnCalls[k].number()
});
}
for (var k = 0; k < calls.length; k++) {
calls[k].sendMessage(JSON.stringify(info));
}
}
/**
* Inbound PSTN call connected
*/functionhandlePSTNParticipantConnected(e) {
e.call.say("You have joined the conference .", Language.UK_ENGLISH_FEMALE);
e.call.addEventListener(CallEvents.PlaybackFinished, function (e) {
VoxEngine.sendMediaBetween(e.call, pstnconf);
e.call.sendMediaTo(videoconf);
for (var k = 0; k < calls.length; k++) calls[k].sendMessage(JSON.stringify({
type: "CALL_PARTICIPANT_CONNECTED",
number: e.call.callerid(),
inbound: true
}));
});
}
/**
* Web client disconnected
*/functionhandleParticipantDisconnected(e) {
Logger.write("Disconnected:");
for (var i = 0; i < calls.length; i++) {
if (calls[i].callerid() == e.call.callerid()) {
/**
* Make HTTP request to delete user via HTTP API
*/var url = apiURL + "/DelUser/?account_name=" + account_name +
"&api_key=" + api_key +
"&user_name=" + e.call.callerid();
Net.httpRequest(url, function (res) {
Logger.write("HttpRequest result: " + res.text);
});
Logger.write("Caller with id " + e.call.callerid() + " disconnected");
calls.splice(i, 1);
}
}
if (calls.length == 0) VoxEngine.terminate();
}
functionhandlePSTNParticipantDisconnected(e) {
removePSTNparticipant(e.call);
for (var k = 0; k < calls.length; k++) calls[k].sendMessage(JSON.stringify({
type: "CALL_PARTICIPANT_DISCONNECTED",
number: e.call.callerid()
}));
}
To let the voximplant cloud know when to run which scenario, the scripts connect to the application using rules . We will need the following rules:
- InboundFromPSTN , in the Pattern indicate the conference phone number, in the script indicate “VideoConferencePSTNgatekeeper”
- InboundCall , in the Pattern specify the string “joinconf” (this is the number that we will dial from the Web SDK when connecting to the conference), in the script specify “VideoConferenceGatekeeper”
- Fwd , in the Pattern we specify the string “conf_ [A-Za-z0-9] +”, in the script we specify “VideoConference” - this rule will work when calling the conference through “callConference”.
- P2P , leave “. *” In the Pattern, specify in the script
- “VideoConferenceP2P”
The order of the rules is important! You can use drag'n'drop to drag and drop (change the priority) .
As a result, the rules for the application should look like this:

That’s all you need to set up in the cloud. Frontend part of the service is done using our web sdk and is quite simple. After connecting, you need to make a call to “joinconf” and pass in the title “conferenceid”. When a user becomes a participant in the conference, in the MessageReceived event, they will receive a list of web clients and you can initiate outgoing peer-to-peer calls using the “P2P” script to receive video from those clients to which there are no connections yet. To enable P2P mode, a special “X-DirectCall” header is transferred in the “call” method. Also, the Frontend part places rectangles of video broadcasts on the screen and allows you to invite a participant with an outgoing call from the conference script. The source code for all scripts and client applications is available on our GitHub account.