Back to Blog

Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK

Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK

This blog was written by Akshat Gupta an Agora Superstar. The Agora Superstar program empowers developers around the world to share their passion and technical expertise, and create innovative real-time communications apps and projects using Agora’s customizable SDKs.


Introduction

In today’s era, every person and company has gone digital to move ahead with the trends.
Live streaming has become a vital feature in social media apps as more users look to interact and share real-life moments with their family and friends in real-time, grow their followings, or even establish themselves as creators.

In this tutorial, we will develop a web application that supports live streaming supporting multiple hosts as well as multiple audience members using Agora’s SDK.

Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK - Screenshot #1
Screenshot of the simple live streaming application we will be developing.

Prerequisites:

Project Setup

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, and lastly a way to leave the chat. I’ve also imported the Bootstrap, JQuery, Font Awesome and Agora SDK CDNs and linked the custom CSS and JS files.

<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta Tags For SEO -->
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Many to many, live video streaming using the Agora Web NG SDK.">
<title>Many to Many Live Streaming || Agora Web NG SDK</title>
<!-- CSS only -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css"
integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
<link rel="stylesheet" href="assets/css/m2m-live.css">
<link rel="icon" href="assets/img/favicon.png" type="image/png">
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon.png" type="image/png">
</head>
<body>
<!-- Title -->
<div class="container-fluid banner">
<p class="banner-text">Live Broadcast</p>
</div>
<div class="container">
<form id="join-form" name="join-form" class="mt-4">
<!-- Input Field -->
<div class="row join-info-group">
<div class="col-sm">
<p class="join-info-text">Channel</p>
<input id="channel" type="text" placeholder="Enter Channel Name" required class="form-control">
</div>
</div>
<!-- UI Controls -->
<div class="button-group mt-3">
<button id="host-join" type="submit" class="btn btn-live btn-sm">Join as Host</button>
<button id="audience-join" type="submit" class="btn btn-live btn-sm">Join as Audience</button>
<button id="mic-btn" type="button" class="btn btn-live btn-sm">
<i id="mic-icon" class="fas fa-microphone"></i>
</button>
<button id="video-btn" type="button" class="btn btn-live btn-sm">
<i id="video-icon" class="fas fa-video"></i>
</button>
<button id="leave" type="button" class="btn btn-live btn-sm" disabled>Leave</button>
</div>
</form>
<!-- Streams -->
<div class="row video-group">
<div class="col">
<p id="local-player-name" class="player-name"></p>
<div id="local-player" class="player"></div>
</div>
<div class="w-100"></div>
<div class="col">
<div id="remote-playerlist"></div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
crossorigin="anonymous"></script>
<script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script>
<script src="assets/js/o2o-voice.js"></script>
</body>
</html>
view raw m2m-live.html hosted with ❤ by GitHub
Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK - Screenshot #2
Without any CSS or color, just plain HTML.

Adding CSS

Now that our base has been laid out, it’s time to add some CSS.
I’ve already added basic Bootstrap classes to the HTML to make the site presentable, but we’ll use custom CSS to match the site with a blue Agora-based theme.

.banner {
padding: 10px;
background-color: #2F3FB0;
color: white;
}
.banner-text {
padding: 8px 20px;
margin: 0;
}
#join-form {
margin-top: 10px;
}
.tips {
font-size: 12px;
margin-bottom: 2px;
color: gray;
}
.join-info-text {
margin-bottom: 2px;
}
input {
width: 100%;
margin-bottom: 2px;
}
.player {
width: 480px;
height: 320px;
}
.player-name {
margin: 8px 0;
}
@media (max-width: 640px) {
.player {
width: 320px;
height: 240px;
}
}
.btn-live {
background-color: #2F3FB0;
color: white;
border: 1px solid #2F3FB0;
}
.btn-live:hover {
color: #2F3FB0;
background-color: white;
border: 1px solid #2F3FB0;
}
view raw m2m-live.css hosted with ❤ by GitHub
Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK - Screenshot #3
With CSS.

Muting and Unmuting the Video and Audio

Let’s add some functionality to our beautiful website. We will begin with the UI controls (muting and unmuting the video as well as audio for the hosts). A little JS here and a little JS there does the job:

// Mute audio click
$("#mic-btn").click(function (e) {
if (localTrackState.audioTrackEnabled) {
muteAudio();
} else {
unmuteAudio();
}
});
// Mute video click
$("#video-btn").click(function (e) {
if (localTrackState.videoTrackEnabled) {
muteVideo();
} else {
unmuteVideo();
}
})
// Hide mute buttons
function hideMuteButton() {
$("#video-btn").css("display", "none");
$("#mic-btn").css("display", "none");
}
// Show mute buttons
function showMuteButton() {
$("#video-btn").css("display", "inline-block");
$("#mic-btn").css("display", "inline-block");
}
// Mute audio function
async function muteAudio() {
if (!localTracks.audioTrack) return;
await localTracks.audioTrack.setEnabled(false);
localTrackState.audioTrackEnabled = false;
$("#mic-btn").text("Unmute Audio");
}
// Mute video function
async function muteVideo() {
if (!localTracks.videoTrack) return;
await localTracks.videoTrack.setEnabled(false);
localTrackState.videoTrackEnabled = false;
$("#video-btn").text("Unmute Video");
}
// Unmute audio function
async function unmuteAudio() {
if (!localTracks.audioTrack) return;
await localTracks.audioTrack.setEnabled(true);
localTrackState.audioTrackEnabled = true;
$("#mic-btn").text("Mute Audio");
}
// Unmute video function
async function unmuteVideo() {
if (!localTracks.videoTrack) return;
await localTracks.videoTrack.setEnabled(true);
localTrackState.videoTrackEnabled = true;
$("#video-btn").text("Mute Video");
}
view raw m2m-live-ui.js hosted with ❤ by GitHub

So far so good?

Core Structure (JS)

Now that we have the HTML/DOM structure laid out, we can add the JS, which uses the Agora Web SDK. It may look intimidating at first, but if you follow Agora’s official docs & demos and put in a little bit of practice, it’ll be a piece of cake.

We first create a client and specify the audio as well as video tracks. You can use a .env file or directly hardcode the App ID into the application and take in the channel name and token (optional) from the frontend.
If you don’t use tokens, specify the tokens as null.

When a host or audience joins a channel by clicking the buttons, you can set the user’s role and begin playing the tracks specified while creating the client. The user’s stream is then subscribed and published which can be toggled using the UI controls we wrote above.

Finally, we give the user an option to end the stream and leave the channel.

// create Agora client
var client = AgoraRTC.createClient({
mode: "live",
codec: "vp8"
});
var localTracks = {
videoTrack: null,
audioTrack: null
};
var localTrackState = {
videoTrackEnabled: true,
audioTrackEnabled: true
}
var remoteUsers = {};
// Agora client options
var options = {
appid: <>,
channel: null,
uid: null,
token: null,
role: "audience" // host or audience
};
$("#host-join").click(function (e) {
options.role = "host";
})
$("#audience-join").click(function (e) {
options.role = "audience";
})
$("#join-form").submit(async function (e) {
e.preventDefault();
$("#host-join").attr("disabled", true);
$("#audience-join").attr("disabled", true);
try {
options.appid = <>;
options.channel = $("#channel").val();
await join();
} catch (error) {
console.error(error);
} finally {
$("#leave").attr("disabled", false);
}
})
$("#leave").click(function (e) {
leave();
})
async function join() {
// create Agora client
client.setClientRole(options.role);
$("#mic-btn").prop("disabled", false);
$("#video-btn").prop("disabled", false);
if (options.role === "audience") {
$("#mic-btn").prop("disabled", true);
$("#video-btn").prop("disabled", true);
// add event listener to play remote tracks when remote user publishs.
client.on("user-published", handleUserPublished);
client.on("user-joined", handleUserJoined);
client.on("user-left", handleUserLeft);
}
// join the channel
options.uid = await client.join(options.appid, options.channel, options.token || null);
if (options.role === "host") {
$('#mic-btn').prop('disabled', false);
$('#video-btn').prop('disabled', false);
client.on("user-published", handleUserPublished);
client.on("user-joined", handleUserJoined);
client.on("user-left", handleUserLeft);
// create local audio and video tracks
localTracks.audioTrack = await AgoraRTC.createMicrophoneAudioTrack();
localTracks.videoTrack = await AgoraRTC.createCameraVideoTrack();
showMuteButton();
// play local video track
localTracks.videoTrack.play("local-player");
$("#local-player-name").text(`localTrack(${options.uid})`);
// publish local tracks to channel
await client.publish(Object.values(localTracks));
console.log("Successfully published.");
}
}
async function leave() {
for (trackName in localTracks) {
var track = localTracks[trackName];
if (track) {
track.stop();
track.close();
$('#mic-btn').prop('disabled', true);
$('#video-btn').prop('disabled', true);
localTracks[trackName] = undefined;
}
}
// remove remote users and player views
remoteUsers = {};
$("#remote-playerlist").html("");
// leave the channel
await client.leave();
$("#local-player-name").text("");
$("#host-join").attr("disabled", false);
$("#audience-join").attr("disabled", false);
$("#leave").attr("disabled", true);
hideMuteButton();
console.log("Client successfully left channel.");
}
async function subscribe(user, mediaType) {
const uid = user.uid;
// subscribe to a remote user
await client.subscribe(user, mediaType);
console.log("Successfully subscribed.");
if (mediaType === 'video') {
const player = $(`
<div id="player-wrapper-${uid}">
<p class="player-name">remoteUser(${uid})</p>
<div id="player-${uid}" class="player"></div>
</div>
`);
$("#remote-playerlist").append(player);
user.videoTrack.play(`player-${uid}`);
}
if (mediaType === 'audio') {
user.audioTrack.play();
}
}
// Handle user published
function handleUserPublished(user, mediaType) {
const id = user.uid;
remoteUsers[id] = user;
subscribe(user, mediaType);
}
// Handle user joined
function handleUserJoined(user, mediaType) {
const id = user.uid;
remoteUsers[id] = user;
subscribe(user, mediaType);
}
// Handle user left
function handleUserLeft(user) {
const id = user.uid;
delete remoteUsers[id];
$(`#player-wrapper-${id}`).remove();
}
view raw m2m-live.js hosted with ❤ by GitHub

Note: You need to enter your own App ID in the JS code above. I replaced my App ID by <> to avoid unnecessary charges.

You can now run and test the application.

Note: For testing, you can use two (or more) browser tabs to simulate single/multiple host(s) and single/multiple remote audience.

Conclusion

You did it!

We have successfully made our very own live streaming web application. In-case you weren’t coding along or want to see the finished product all together, I have uploaded all the code to GitHub:

Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK - Screenshot #4

If you would like to see the demo in action, check out the demo of the code in action on:

Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK - Screenshot #5

You can also learn how to combine video streams using Agora's Web SDK. 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!

Other Resources:

To learn more about Agora’s Web NG SDK and other use cases you can refer to the developer guide given over here:

You can also have a look at the complete documentation for the functions discussed above and many more over here.

You can also join the Agora Developer Slack Community:

RTE Telehealth 2023
Join us for RTE Telehealth - a virtual webinar where we’ll explore how AI and AR/VR technologies are shaping the future of healthcare delivery.

Learn more about Agora's video and voice solutions

Ready to chat through your real-time video and voice needs? We're here to help! Current Twilio customers get up to 2 months FREE.

Complete the form, and one of our experts will be in touch.

Try Agora for Free

Sign up and start building! You don’t pay until you scale.
Try for Free