Jitsi IFrame in Use — A Shody Guide to Embeding Jitsi into Your Website

What even is the external API?

Do you want to embed Jitsi into your website page, and have it run through a UI of its own? Do you want the meeting function to be a small, secondary tidbit and have the rest of your website take the main stage, or, well, exist, at all? Meet Jitsi IFrame API. It’s a small little API that turns a Jitsi meeting into an iFrame element, so you can use it in the html. It is the “go to” way of embeding Jitsi into your website and making it look like that was your plan all along. Mustache twirling will be examined in another article.With the Jitsi IFrame API, you can embed a Jitsi meeting into your website design like you would any other IFrame.

What is the guide about?

Chance would have it, recently I’ve got a request from a customer to design a Jitsi UI with rather distinct specifications. I’ve got to say, it is a nifty tool, but it has some shortcomings and oddities in the strangest of places. Mid-way through the project, I’ve decided to put together all the hurdles I’ve had to jump over into a neat little article. Now, without all the trademarked bells and whistles, the result looks startlingly close to a carrot you’ve purchased and forgot in the refrigerator. It is, to put it eloquently, veritably ugly. But it works without a hitch, and you can scavenge what wisdom you want without fear and flex your designer muscles on it. Go you.

Getting started with this Guide

You can find the entirety of the code on GitHub. I’ve tried to write it as openly as I could. If you’re in a hurry, you can download the html file and be done. But I would still read the rest, just in case.

Importing the script

All the scripting here takes place after the tag, but before, obviously, the tag. You should start by importing the external api script into the page;

<script src=""></script>

Defining some constants

After importing the script, open a new <script> tag and define two constants. The API’s function will use them as parameters later. Your code should look like this;

<script src=""></script>
const domain = "";
const options = {
roomName: "ROOM_NAME",
width: 800,
height: 480,
parentNode: document.querySelector('#ELEMENT_ID'),
configOverwrite: {},
interfaceConfigOverwrite: { TOOLBAR_BUTTONS: [ ] },
jwt: 'yourtokenhere'
var isSteamOn = false;

What is going on in here?

The “domain” constant keeps track of which jitsi installation to stream from. That one is easy.
The options object however, contains some not-so-human-friendly properties. Let’s explain each of them, one by one.
roomName is the name of the room that this iframe joins.
width is the width of the iframe, while similarly height is its height. Dazzling.
parentNode is the bit that decides where to show the iframe. You can create an empty <div>, give it an ID, and pass that ID in here.
configOverwrite and interfaceConfigOverwrite can be used to overwrite the settings contained in the config and interface_config files, respectively. You can hide buttons, enable a prejoin screen and anything else you can normally do. For detailed information, take a look at the official guide.
jwt is the jwt token you pass for authorization. If you are using jitsi-token-moderation be careful that the token you pass has the ability to start a recording.

Okay. What’s next?

Now that we’ve defined the parameters of our iFrame, let’s go ahead and actually construct it;

const api = new JitsiMeetExternalAPI(domain, options);
<button onclick="api.executeCommand('toggleAudio')">Mute/Unmute Mic</button>
<button onclick="api.executeCommand('muteEveryone')">Mute All</button>
<button onclick="api.executeCommand('toggleVideo')">Stop/Start Cam</button>
<button onclick="alert('This one is tricky and should be customized according to need')">Cam/Mic</button>
<button onclick="api.executeCommand('toggleShareScreen')">Share Screen</button>
<button id="stream-btn" onclick="streamHandler()">Start Stream</button>

Starting the stream

We’re once again in the <script> section of our page. We have to write a wrapper function for our streamHandling button.

function streamHandler() {        try {            if (!isStreamOn) {                document.getElementById("streamingResponseMsg").innerHTML = "Starting streaming...";                //The function below starts the stream or recording, according to its "mode"                api.executeCommand('startRecording', {                    mode: 'stream', //recording mode, either `file` or `stream`.                    rtmpStreamKey: '', //This where you *should* put your favoured rtmp stream server along with your key, like "rtmp:\/\/some.address/norecord/stream-key"                    youtubeStreamKey: 'rtmp:\/\/some.address/norecord/stream-key', //the youtube stream key.                });            } else {                document.getElementById("streamingResponseMsg").innerHTML = "Stopping streaming...";                //The function below stops the stream or recording, according to the string you pass. Official guide shows an object, while it should be a string                api.executeCommand('stopRecording', 'stream');            }        }        catch (e){            if (isStreamOn){                document.getElementById("streamingResponseMsg").innerHTML = "Error while stopping stream.";                console.log("Exception while stopping stream.", e);            }else{                document.getElementById("streamingResponseMsg").innerHTML = "Error while starting stream.";                console.log("Exception while starting stream.", e);            }                this.isStreamOn = false;         }    };

Recording the state of the stream

To prevent unsavoury bugs, we should check first if the stream has actually started. Unfortunately, the iFrame API does not have a listener for that specific situation. You can find a solution here. Currently, it has been committed, but not yet approved and integrated into the API. After following the changes they’ve made and adding an event listener, you can use it in the following fashion;

api.addEventListener("recordingStarted", () => {        document.getElementById("stream-btn").innerHTML="Stop Streaming";        document.getElementById("streamingResponseMsg").innerHTML = "Stream is on";        this.isStreamOn = true;        console.log("Example Stream On", this.isStreamOn);        });        api.addEventListener("recordingStopped", () => {        document.getElementById("stream-btn").innerHTML="Start Streaming";        document.getElementById("streamingResponseMsg").innerHTML = "Stream is off";        console.log("Example Stream Off", this.isStreamOn);        this.isStreamOn = false;    });

Alright. I got it. But what’s this bit?

Oh. You must be referring to this monstrosity;

api.addEventListener(`videoConferenceJoined`, () => {        const listener = ({ enabled }) => {            api.removeEventListener(`tileViewChanged`, listener);            if (!enabled) {                api.executeCommand(`toggleTileView`);            }        };    });

Too Long, Didn’t Read

The code in the GitHub largely works. But you have to make some changes to Jitsi so you can catch when a stream starts and stops. Once again, you can find those here. If you have issues with rtmp key, that is probably because the workaround I’ve used is now deprecated. You can find the shiny new way of doing things in the official guide.