Hey everyone, today I want to walk through how to build a simple group video chat web app, very similar to Google Hangouts, Skype or whichever other video chat platform you prefer. Given today’s fragmented JS landscape, I wanted to write this tutorial using the most basic versions of HTML, CSS and JS. Before you say it, I know I know, JQuery isn’t vanilla JS but Vanilla JS can be a bit verbose for certain DOM tasks, I chose to use JQuery to simplify a few things. We are going to cut a few corners and use Bootstrap so we don’t have to worry about writing too much custom CSS.
For the TLDR crowd: theres a Heroku demo at the bottom of the post.
Updated: April 29, 2020
This post and project has been updated to use the latest version of Agora.io’s Web SDK (v3.0.2)
Core Structure (HTML)
Let’s start by laying out our basic html structure. There are a few UI elements we must have, such as the local video stream, the remote video streams, a toolbar that will contain buttons for toggling audio/video streams, a button to share our screen with the group, and lastly a way to leave the chat (we’ll add the buttons a little later).
Adding in CSS and JS
Now that we have our base we can start expanding. Using Bootstrap for our CSS we can quickly style our html with a few simple classes. In the above code, let’s add the CSS links _(shown below)_ into the code where we see the comment block
<!-- CSS includes go here -->.
While Boostrap is great but it isn’t a holistic solution, so I threw in a few extras CSS blocks within a custom CSS file (we’ll get to this a little later). This will help adjust a few elements that we won’t get perfect out of the box with Bootstrap. I also added the Font Awesome CSS framework because we are going to need to incorporate icons for the various buttons and FA makes it really simple.
As I mentioned, Bootstrap is great, but sometimes you still need a little bit of custom CSS. Here are the styling blocks for the above referenced
Adding UI Elements
Now let’s add some buttons to control toggling the mic, video or leaving the channel and finish off the last remaining bits of our UI. This is where font awesome and bootstrap really make things simple. We will use a
<button /> element and some FontAwesome icons.
The sections below fits in with the code above by replacing the comments
<!-- insert button to share screen --> and
<!-- insert buttons to toggle audio/video and leave/end call -->
We need to add some JS to control the buttons. JQuery will really help us here by simplifying the code for the various DOM operations which will allow the UI to feel dynamic for the user.
As you can see there is some added logic for keyboard controls. During testing I found having keyboard shortcuts made things move quicker. In the snippet above we have support for
m, v, s, q to toggle mic, video, and screen-share and to leave the call (respectively).
I saved the above code into a file
ui.js to keep it separate from the core video chat logic that we will write. Also let’s make sure to include the
ui.js file within our html file (using the snippet below).
Core Structure (JS)
Now that we the HTML/DOM structure laid out we can add in the JS. I chose to use Agora.io to simplify the heavy task of the WebRTC interface. I wrote a short post on how to get setup with Agora.io for anyone new to the Agora.io platform. In the code below we start by declaring and initializing the Client object. Once we have the Client object we can join/leave the channel but also we will add listeners for the various engine events.
Below I included some of the initial object declarations for the screen sharing. I will expand upon that implementation later on, as we add in the rest of the logic.
One thing to note, all the Agora.io SDK event listeners should be at the top level, please don’t make the mistake of nesting them into the channel join callback. I made this mistake and it caused me to only have access to streams that joined the channel after me.
As you can see within the code above we have the
‘stream-added’ callback, this is where we will add logic to handle setting the first remote stream to the full screen video and every subsequent stream into a new div container within the remote-streams div which will give us the group functionality beyond just 1 to 1 video. Below is the function we would call every time a new remote stream is added and we want to have it add itself dynamically to the DOM.
One last note for this section, we have buttons that toggle the mic and video streams but we need to provide feedback to the remote users subscribed to the muted streams. Don’t worry Agora’s SDK provides some callbacks specially for these situations. Above you can see these cases are handled by the events such as
mute-video</code (as well as their inverses for enabling the respective streams).
Enhancing the UI by Handling Remote Stream Actions
First let’s start by adding some extra divs with icons for a muted mic and a user icon when the video feed is disabled. I will use the local container as a reference as the remote stream containers will have similar structure.
The new divs will hold some Font Awesome icons that we can hide/show whenever the event callbacks are executed for on the local and corresponding remote streams. Now that we have some names for our elements we can easily control them within our event listeners.
There’s a few effects that we can add to really enhance the user experience. First let’s consider what happens when the user wants a different stream to be the full screen. We’ll add a double click listener to each remote stream so when the user double clicks a remote stream it swaps the mini view with the full screen view.
the above snippet fits into
addRemoteStreamMiniView() just before the closing bracket
Lastly let’s make sure that there is always a full screen stream as long as at least one stream is connected. We can use some similar methods as we did above.
I added some randomization so when the the full screen remote stream leaves the channel, one of the other remote streams is randomly selected and set to play in the full screen div.
Putting it All Together
Now that we have all these snippets lets put them together and fill in the rest of the logic for how the web app should react to each event.
Please note: video sharing is not supported by Safari and though Chrome supports screen sharing it does require a special plugin. Thankfully FireFox does not have such restrictions. Yes you read that correctly I said let’s use Firefox to test screen sharing.
Let’s drop our JS includes into our html page to make the final connections. The below snippet fits into the main html (above) by replacing the comment
with the snippet below.
Testing Setup (webserver/https)
There are a few different ways to implement the various libraries (Agora, Jquery and Bootstrap), I chose to use the CDN versions. Since using a CDN with an https connection requires a secure connection, before we can test our video chat app we must spin up a simple web server (I like to use Live Server). The web server requirement is due to browser restricts when loading files from a
https source. For local testing purposes
localhost:// is white-listed by browsers but to share with friends we’ll need a way to tunnel our from out our local machine. Ngrok, is a freemium service that creates this tunnel out from your local machine and provides an
https url for use. In my experience this is one of the simplest ways to run an
https secured web server on your local machine.
Once the server is ready we can run our test.
NOTE: for testing we will be using two (or more) browser tabs to simulate a local host and a single/multiple remote host(s).
And just like that we are done! In-case you weren’t coding along or want to see the finished product all together, I have uploaded all the code to GitHub.
If you would like to see the demo in action, check it out on Heroku.
If you enjoyed this post, I would recommend my How to Build a Live Broadcasting Web App where we’ll build a YouTube Live clone with a few extra features.
Thanks for taking the time to read my tutorial and if you have any questions please let me know with a comment. If you see any room for improvement feel free to fork the repo and make a pull request!