Skip to content
Building a Desktop Video Calling App using the Agora Electron SDK featured

How to Build a Desktop Video-Calling App using the Agora Electron SDK

By Author: Ekaansh Arora In Developer

As a JavaScript developer, you can truly use one language to build apps for almost any platform, and the Electron framework enables you to build cross-platform desktop apps. In this tutorial, we’ll walk through building a video-calling desktop app using the Agora Electron SDK for macOS and Windows.

With Electron, we can essentially bundle a website as a standalone desktop app. This approach is… sometimes controversial. But it allows a web developer to start building desktop apps in hours. So as a prerequisite you only need to know HTML, CSS, and JavaScript.

Creating an Account with Agora

Sign up at https://console.agora.io and log in to the dashboard.

Building a Desktop Video Calling App using the Agora Electron SDK screenshot 1

Navigate to the Project List tab under the Project Management tab, and create a project by clicking the blue Create button. (When prompted to use App ID + Certificate, select only App ID.) Retrieve the App ID. It will be used to authorize your requests while you’re developing the application.

Note: This guide does not implement token authentication, which is recommended for all RTE apps running in production environments. For more information about token-based authentication in the Agora platform, see this guide: https://docs.agora.io/en/Video/token?platform=All%20Platforms.

Structure of Our Example

This is the structure of the application that we’re building:

.
├── src
│ └── index.css
│ └── index.html
│ └── index.js
│ └── render.js
├── package.json
.

Let’s Run the App

You’ll need to have the LTS version of Node.js and NPM installed:

  • Make sure you have an Agora developer account, set up a project, and generate an App ID.
  • Download and extract the ZIP file from the master branch.
  • On macOS run npm install or on Windows run npm install — arch=ia32 to install the app dependencies in the unzipped directory.
  • Navigate to ./src/render.js and enter the App ID that we generated as appId = “<YourAppId>”;
  • You can now run npm start in the project root to start your app.

That’s it. You have a video calling desktop app. The app uses "test” as the channel name. The project uses electron-forge under the hood to get started with Electron with ease.

Building a Desktop Video Calling App using the Agora Electron SDK screenshot 2

How the App Works

Our Electron app has four files: index.html is the markup for our app’s elements, index.css handles the styling for our app, render.js contains the application logic for our video call and index.js handles the bootstrapping process of setting up Electron.

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Agora Electron Quickstart</title>
  <link rel="stylesheet" href="index.css">
  <script defer src="render.js"></script>
</head>
<body>
  <h1>Agora Electron Quickstart</h1>
  <button id="start">Start Call</button>
  <button id="stop">Stop Call</button>
  <div id="video-container">
    Local Feed:
    <div id="local">
    </div>
    Remote Feed(s):
    <div id="remote">
    </div>
  </div>
</body>
</html>

We’re adding a script tag to the <head> tag, with the source set to render.js so that we can load up our application logic. The defer tag waits for the page to load before executing the JS.

We have a simple layout: two buttons for starting and ending the call, and two divs to contain the videos for local and remote users inside the video-container div.

index.css

The styling for our app looks like this:

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  margin: auto;
  max-width: 90vw;
  text-align: center;
  align-items: center;
}
h1 {
  margin-block-start: 0.4em;
  margin-block-end: 0.4em;
}
button {
  background-color: rgb(50, 153, 250); /* Green */
  border: none;
  color: white;
  padding: 8px 24px;
  text-align: center;
  text-decoration: none;
  border-radius: 4px;
  font-size: 16px;
  margin-bottom: 5px;
}
#local canvas {
  margin: 5px 0;
  height: 30vh;
}
#remote {
  flex-direction: row;
  display: flex;
  width: 100%;
  flex-wrap: wrap;
}
#remote div {
  flex: 1;
  margin: 5px;
}
#remote canvas {
  height: 27vh;
  zoom: 1.2 !important;
}

render.js

Let’s get down to business and look at our application logic:

const AgoraRtcEngine = require('agora-electron-sdk').default;
const APPID = ""; //Enter App  ID here
if (APPID === "") {
  alert('Please enter APPID in src/render.jsx (line:2)');
}
let rtcEngine = new AgoraRtcEngine();
rtcEngine.initialize(APPID);
rtcEngine.on('joinedChannel', (channel, uid, elapsed) => {
  let localVideoContainer = document.querySelector('#local');
  rtcEngine.setupLocalVideo(localVideoContainer);
})
rtcEngine.on('userJoined', (uid) => {
  let remoteVideoContainer = document.querySelector('#remote')
  rtcEngine.setupViewContentMode(uid, 1);
  rtcEngine.subscribe(uid, remoteVideoContainer)
})
rtcEngine.setChannelProfile(0)
rtcEngine.enableVideo()
document.getElementById('start').onclick = () => {
  rtcEngine.joinChannel(null, "test", null, Math.floor(new Date().getTime() / 1000))
};
document.getElementById('stop').onclick = () => {
  rtcEngine.leaveChannel();
  document.getElementById('local').innerHTML = '';
  document.getElementById('remote').innerHTML = '';
};

We’re importing the AgoraRtcEngine class from the SDK. We disable an alert if the user doesn’t input an Agora App ID. We create a new instance of the class — rtcEngine. We use the App ID to initialize our engine instance.

Agora uses an events based SDK. For example when we successfully join a video channel we get the joinedChannel event, which we can then use to execute our functions. We’re setting an event handler that calls the setupLocalVideo method on the engine, and we pass in our div localVideoContainer to render the local user’s video feed.

Next, we’re setting up an event for userJoined that is triggered whenever a user joins the channel. We use thesetupViewContentMode method to set up the remote video feed, passing in the UID from the event and 1 to use the fit mode. You can use 0 to crop the video to the size of the div. We then use the subscribe method, which subscribes to a remote user and initializes the corresponding renderer by passing in the UID and the HTML container. We’re using the remoteVideoContainer div.

We’re now using the setChannelProfile method to use the communication profile. You can also use live streaming. We’re enabling the video module using the enableVideo method.

Next, for our buttons, we’re setting onClick functions. The start button uses the joinChannel method to join a channel: it accepts the token, channel name, optional info, and a UID. We pass in null for the token. If you’re using an app in secure mode, you can use a temporary token. We have “test” for our channel name, but you can use any string. Users on the same channel can communicate with each other. We pass in null for the info parameter, and we’re generating a random UID using the Date function. All users in a channel should have unique UIDs.

The stop button calls the leaveChannel method and clears the remote videos.

index.js

Now let’s bootstrap our Electron app:

const { app, BrowserWindow } = require('electron');
const path = require('path');
app.allowRendererProcessReuse = false
if (require('electron-squirrel-startup')) { 
  app.quit();
}
const createWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
  },
  });
  mainWindow.loadFile(path.join(__dirname, 'index.html'));
};
app.on('ready', createWindow);
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

We’re importing app and BrowserWindow from Electron, and we’re using path from Node. We’re allowing the use of non-context-aware modules by setting the allowRendererProcessReuse property to false to use the Agora SDK. The electron-squirrel-startup module manages the Windows app startup logic.

We write a new function createWindow to spawn a new browser window using the BrowserWindow from Electron. We pass nodeIntegration: true and contextIsolation: false in our webPreferences to support the Agora SDK integration. We load the index.html file using the loadFile method on our main browser window.

Returning to events on our Electron app, we have the ready event, which calls the createWindow function. For macOS, we use the window-all-closed event that to quit the app when the window is closed. If no windows are open we use the activate event to create a new window using our createWindow function.

Other Resources

That’s how easy it is to build a desktop video calling app using the Agora SDK and the Electron framework. Take a look at the Agora Electron Docs and the Agora Electron API Reference to quickly add more features like muting the camera and microphone, setting video profiles, and audio mixing.

I also invite you to join the Agora Developer Slack community. If you have any questions, you can post them in the #electron-help-me channel.

More content at plainenglish.io


Want to build Real-Time Engagement apps?

Get started with 10,000 free minutes today!

If you have questions, please call us at 408-879-5885. We’d be happy to help you add voice or video chat, streaming and messaging into your apps.