In this first article dedicated to CPaaS solutions, I wanted to focus on Jitsi Meet.
Why ? On one hand because it is the solution I chose for a project that requires a conference server and on the other hand because it was the opportunity for me to finally discover this product and more precisely its CPaaS side. I never had the opportunity to use it as a user nor as a developer.
Note: webRTCH4cKS already shared an excellent article on Jitsi called The ultimate guide to Jitsi Meet and JaaS that describes completely the solution. The Jitsi Architecture scheme really helped me to understand the CPaaS possibilities.
This article focuses on the reasons why I used Jitsi and then my development experiences.
Several needs were expressed for this project:
Data protection: This is due to the specific context of the project. Without going into details, a self-hosted solution was preferred over using an existing global cloud solution as a service. And while sovereign CPaaS platforms exist in Europe that meet these requirements, they are too expensive for a startup. So only hosting will have to be considered here.
Time to market: The solution must be available ‘now’ without having to spend weeks or months on the development part.
Convenient conferencing solution: No need to be lost by the features: Just the essentials: Video + chat + screen-sharing.
Scalability? There will not be many meetings with a high number of persons and at the beginning there will be few parallel meetings. Simple monitoring will be used to check health, usage and system resources used.
The low cost was part of the needs (as usual…).
But there is no specific need for call quality, specific devices or environments. Just a way to connect people.
When I looked at the possible solution, I came up with 3 (open-source) possibilities:
Janus: A SFU conference server that comes with a JS library. I’ve already developed with Janus before but my experience was limited to using it as a WebRTC gateway between web users and Freeswitch conferences.
BigBlueButton : A complete conferencing and collaboration solution: My experience was limited to connecting the collaboration part (desktop sharing) to enhance an existing SIP web client.
Jitsi Meet: The open-source conferencing server from 8x8. I had never experienced or used it. Here, it is interesting because it is Java and the dev guy has developed the backend in Java. I thought that for him, having another Java component should be less scary…
Jitsi comes with the possibility to use it as a service or to host it in your infrastructure. Then, for the frontend, Jitsi can be used from an IFrame (no user interface to develop) or from a low-level JavaScrip library (design you own conference experience).
And that’s it for the candidates (sorry for the others…).
Actually, the choice was easy because I had to choose between 2 different kinds of solutions:
All-in-one solution: Jitsi Meet (IFrame) and BBB come with a client side part that already proposes all the features needed. With these kind of solutions, efforts are more or less equivalent to DevOps tasks only. The minimal part to do is to integrate and customize the client side.
Server solution only: Janus is different. This is a pure server side component. A client side JavaScript library is used to dialog with the server but, there is no GUI part. Everything has to be developed, existing samples could help.
Depending on your goals, each can be used for sure. In my case, I have preferred to use an all-in-one solution to bring the conference features in a first step without having to spend time developing the UI part and then if necessary in a second step, to use the SDK to rebuild the frontend experience.
I didn’t choose BigBlueButton. Even if it is an equivalent solution to Jitsi, I felt that BBB is more complex to support because it is composed of different open-source servers such as Kurento and Freeswitch put together. I was more worried about supporting them.
But I didn’t spend too much time on BigBlueButton… Perhaps for a next project!
So, my choice was to use Jitsi Meet and to integrate the IFrame into the existing Web application. For the mobile part, I will rely on the existing Jitsi Meet IOS and Android applications available on the store and connect them to the hosted instance.
To assess my choice, I installed Jitsi locally and tried to adapt it to my need: Using a simple HTML web page to load the Jitsi IFrame that connects to the hosted instance and then to try to see what can be done next.
The first step was to understand how to install my own Jitsi Meet instance. Having a Mac will not help this time as a Linux distribution is required.
I decided to use Parallels Desktop and to install the Ubuntu 22.04 Linux distribution.
Once done, I followed the guide Self-Hosting Guide - Debian/Ubuntu server provided by Jitsi.
And less than an hour, the Jitsi Meet instance was available in my local domain meet.localdomain.com
To test it, I used a browser and opened the url https://meet.localdomain.com
. By default, Jitsi provides a self-signed certificate but all is already set to use your own certificate or to rely on Let’s Encrypt to generate and renew one.
In my case, the self-signed certificate was enough.
Note: I tested with Jitsi version 2.0.7648
When I first launched Jitsi Meet, I noticed some labels, features and popup I don’t want to have in my application: I just need to have the video of the participants and allow them to share content if needed.
Jitsi Meet was too rich and distracting for my case :-)
Now is the time to look at the configuration files because as described in the documentation, the GUI part can be customized.
At that time of writing, Jitsi Meet uses two main configuration files:
/etc/jitsi/meet/<your-domain>-config.js
/var/share/jitsi-meet/interface_config.js
Making changes in these files allows to
1) Enable, disable or change the behavior of features,
2) Customize the text, icons and the positioning & sizing of some elements,
3) Change some technical (advanced) parameters linked to WebRTC.
Don’t be surprised, there is really a huge set of parameters. The documentation helps to understand their functions, but I was a bit lost: the parameters are like spaghetti in these files: everything is mixed up, or I did not understand the logical order :-)
Here are the links for these default files in GitHub:
Note: interface_config.js
is noted as deprecated so only one file should stay at the end (?).
When using the IFrame, some parameters can be customized directly from the JavaScript application (overridden). This is good if you know which ones can be overridden and which ones cannot.
In my case, it won’t be enough because the UX should be homogenous between mobile users and web users. So I preferred to change the parameters on the hosted platform directly to keep a unified solution.
Here are the parameters I used to customize the application
I modified the following parameters (from the config)
Parameters | Value | Details |
---|---|---|
enableWelcomePage | false | Don’t display the welcome page. Enter directly a given conference |
prejoin.enabled | false | Don’t display the prejoin page. Enter directly a given conference |
gravatar.disabled | true | Don’t use the Gravatar integration |
p2p.enabled | true | Always start by a P2P call to avoid consuming resources on the server |
stunServers | [urls] | Used when in P2P in case direct call is not possible |
disableDeepLinking | true | Avoid blocking tablet/phone when using a browser to enter the conference |
disableInviteFunctions | true | Disable all invite functions (invite, dial out) |
hideParticipantsStats | true | Don’t display the participants stats |
logging.defaultLogLevel | warn | Avoid to much logs from the Iframe |
toolbarButtons | [..] | Removed embedmeeting, etherpad, invite, linktosalesforce, livestreaming, profile, recording, security, stats, shortcuts |
breakoutRooms.hideAddRoomButton | true | No room inside room |
disableShortcuts | true | Disable Jitsi shortcuts |
disableProfile | true | Disable user profile as managed by the external application |
readOnlyName | true | Don’t allow to edit the user name inside Jitsi as name is fixed |
analytics.disabled | true | Don’t use external analytics |
Note: For testing purpose, I let the mobile devices to connect using the Web application instead of forcing them to use the native application (eg: disableDeepLinking=false
).
Note: Some parameters can be redundant but this is not straightforward to test each individually.
Regarding on the interface configuration file, I modified the following parameters
Parameters | Value | Details |
---|---|---|
APP_NAME | ”**” | Update the application name (labels) |
BRAND_WATERMARK_LINK | ” | No link |
DEFAULT_LOGO_URL | ” | No logo displayed |
DEFAULT_WELCOME_PAGE_LOGO_URL | ” | No welcome page so no logo |
JITSI_WATERMARK_LINK | ” | No link to the existing platform |
PROVIDER_NAME | ’**’ | Same name as for the application |
SHOW_BRAND_WATERMARK | false | Don’t display the brand |
SHOW_CHROME_EXTENSION_BANNER | false | Don’t display the extension banner |
SHOW_DEEP_LINKING_IMAGE | false | Don’t display image for native |
SHOW_JITSI_WATERMARK | false | Don’t display Jitsi logo |
HIDE_DEEP_LINKING_LOGO | true | Don’t display Jitsi logo (native) |
SHOW_POWERED_BY | false | Don’t display the Jitsi link |
VERTICAL_FILMSTRIP | false | I wanted to have horizontal thumbnails and not of vertical |
ENABLE_DIAL_OUT | false | Don’t show the contacts |
HIDE_INVITE_MORE_HEADER | true | Don’t show the invite when alone |
Note: be careful to not be puzzled with ‘enabled’, ‘disabled’ and ‘hide’ :-)
The result is a clean interface with the minimum of features.
I didn’t change any advanced WebRTC parameters. For my basic requirements, I hope there is no need to change them. I don’t know if performance could be better by changing some of them…
Now that the functionality is ok, I can integrate Jitsi inside the existing web application.
As mentioned, Jitsi provides an IFrame to embed the user interface directly without having to develop the GUI.
You need to do the following
Load the appropriate Jitsi JavaScript library from your hosted environment.
Create an HTML node element to contain the IFrame.
Instantiate the JitsiMeetExternalAPI
interface.
Here, I was limited to get the JitsiMeetExternalAPI
from the window
global object. It is not really nice, but I couldn’t find a way to properly integrate the Jitsi library within the existing Angular application (for example using an import).
If you don’t bundle the library within the existing code base, you will have to target the right hosted server depending on the environment you’re using: Staging and production can be two different domains.
For testing purposes, I injected the correct script tag based on the platform by inserting the script at runtime.
To summarize, you should have something similar to the following
// Loading the library depending on the environmentconst scriptEl = window.document.createElement('script');const domain = 'staging.myDomain.com'; // Got from an env variablescriptEl.src = `https://${domain}/external_api.js`;window.document.body.appendChild(scriptEl);// Jitsi IFrame container somewhere in your HTML stuff<div id="jitsi-container" class="jitsi-container"></div>// Function to connect to the conference (create the IFrame)const connect = () => {const options = {roomName: `a_room_name`,parentNode: document.querySelector('#jitsi-container'),lang: 'fr',configOverwrite: {...}, // overridden config parameters,interfaceConfigOverwrite: {...}, // overridden interface parameters,userInfo: {displayName: 'bob'},};const api = new window["JitsiMeetExternalAPI"]('staging.myDomain.com', options);}
Once done, the conference will appear within you defined tag.
Displaying the conference in a “black box” may not be enough
In my case, I needed a way to:
Detach the IFrame in a separate popup. The user needs continue to navigate inside the application (desktop web)
Indicate to others that the user is busy when connected to the conference
Stop the conference from the application
Capture errors for logging purpose
For doing that, Jitsi offers events to listen and commands to execute.
Inside the IFrame, the two buttons can be displayed in the toolbars:
undock-iframe
dock-iframe
Clicking on these buttons does nothing except trigger the iframeDockStateChanged
event.
In my case, when in the main window, I chose to display only the undock-iframe and when in the external window to display the other option.
Here is the implementation I made to handle the dock/undock function.
// Code for undocking / docking the Iframeapi.addListener("iframeDockStateChanged", () => {let endedFromExternalWindow = false; // Detect when conference is ended from the external Windowlet state = "disconnected";// Create the external windowconst externalWindow = window.open(`about:config?width=${640}&height=${480}&?ontop`, 'newWindow', `height=600, width=800, popup=yes, scrollbars=auto, resizable=no, location=no, status=no`);externalWindow.document.open();externalWindow.document.write("<!DOCTYPE html><html style='overflow:hidden;height: 100%; width: 100%;margin: 0'><head></head><body id='container' style='height: 100%; width: 100%;margin: 0'><div id='jitsi-container' style='height: 100%; width: 100%;margin: 0'></div></body></html>");const scriptEl = externalWindow.document.createElement('script');scriptEl.src = `https://${domain}/external_api.js`;externalWindow.document.body.appendChild(scriptEl);externalWindow.document.close();externalWindow.document.title = "My Application";// Listen to the window load event (external window)externalWindow.onload = () => {// External window loaded, load the conference (options could be different from the main window)const externalApi = new externalWindow["JitsiMeetExternalAPI"]('staging.myDomain.com', options);externalApi.addListener("videoConferenceJoined", () => {state = "connected_external";});// Listen when the dock option is clicked from the external windowexternalApi.addListener("iframeDockStateChanged", () => {externalWindow.close();});// Listen when the conference is ended from the external windowexternalApi.addListener("readyToClose", () => {// close the external window when the conference is endedendedFromExternalWindow = true; // Avoid to reload the conference from the main windowexternalWindow.close();});// Dispose the conference in the main window when in the external oneapi.dispose();};// Listen to the window unload event (external window)externalWindow.onbeforeunload = () => {// When the external window is closed, connect again the application to the conferenceif(!endedFromExternalWindow) {connect();return;}endedFromExternalWindow = false;}});
The counterpart of this approach is that the user is briefly disconnected from the conference and then reconnected each time he undocks or docks his conference.
There are 3 events to listen to know the connection status of the user: videoConferenceJoined
, videoConferenceLeft
and readyToClose
Here is the code to subscribe and react when the user is connected.
let state = "disconnected";api.addListener("videoConferenceJoined", () => {state = "connected";});api.addListener("videoConferenceLeft", () => {state = "disconnected";});api.addListener("readyToClose", () => {// You can remove the IFrameapi.dispose();});
From the existing application, you can add a button to leave the conference directly or rely on the IFrame toolbar button.
To send a command to the IFrame, you need to use the function executeCommand
.
// Call the JS API from the external window if connected externallyif(state === "connected_external") {externalApi.executeCommand('endConference');} else {api.executeCommand('endConference');}
To capture errors, the following event could be subscribed
cameraError
: To detect problem when accessing the cameramicError
: To detect problem when accessing the microphonebrowserSupport
: To detect is the browser is supportederrorOccurred
: Any occurred errorsThis can be a way to react and display a popup message into the main application.
Note: The complete list of events can be found here: Jitsi IFrame Events
Jitsi offers a private REST API that can be used to have the minimal health-check metrics and usage data.
For that, you need to edit some configuration files.
As for other points, it is complicated to really know what is mandatory to do, what is obsolete. I did the following, and it worked for me…
I edited the JVC configuration file located here /etc/jitsi/videobridge/jvb.conf
. Here is the complete file content
videobridge {http-servers {public {port = 9090}}websockets {enabled = truedomain = "xxx.xxx.xxx:443"tls = true}apis {rest {enabled = true}jvb-api {enabled = true}}stats {enabled = true}}
Then, I edited and modified the SIP Communicator properties file located here /etc/jitsi/videobridge/sip-communicator.properties
and added these lines
org.jitsi.videobridge.ENABLE_STATISTICS=trueorg.jitsi.videobridge.ENABLE_REST_COLIBRI=trueorg.jitsi.videobridge.STATISTICS_TRANSPORT=muc,colibri,restorg.jitsi.videobridge.STATISTICS_INTERVAL=2000
Finally, I updated the file /etc/jitsi/videobridge/config
to pass an extra property to jvb
JVB_OPTS="--apis=rest,xmpp"
With these changes, I can have access to the following APIs.
The endpoint /about/health
has to be used to have a status.
curl http://localhost:8080/about/health
HTTP 200OK is received if everything works great.
The endpoint colibri/stats
can be used to have the conferences stats
curl http://localhost:8080/colibri/stats | jq .
Associated to that, there is the following end-point, but I didn’t succeed to make it works.
curl http://localhost:8080/colibri/conferences | jq .
Surprisingly, I find this API available that gives additional information on conferences. Don’t know from which part it comes.
curl http://localhost:8888/stats
Using these API, I was able to make regular health-check of my Jitsi instance as well as to grab some usage statistics.
And here I am. I sent this integration prototype to the person in charge to see how far I’m from his initial request.
But for the time spent, the result is very good. Except the disconnect/reconnect effect when using the external window, I didn’t find any problems when testing with multiple devices.
Note: With my configuration, Jitsi manages the conference when there are 3 participants. The conference is not managed by the server when I’m alone or in P2P.
Here are some other development feedbacks
To conclude, I really enjoyed this journey using Jitsi Meet IFrame. Super easy to set up the server even for me who is not a devops guy. IFrame integration not more complicated to do. And the result was as expected: simple and efficient!