DEV Community

Cover image for Building a Raise-Your-Hand Feature for Live Streams Using the Agora Web SDK
Akshat Gupta
Akshat Gupta

Posted on • Edited on

3 1

Building a Raise-Your-Hand Feature for Live Streams Using the Agora Web SDK

Being a community-driven developer, I conduct educational webinars on live streams. At the end of each session I leave time for doubt-solving. These sessions are very hard to execute smoothly, because often several participants text on the meeting chat asking to clear their doubts at the same time. This makes it difficult to keep track of issues that any one person is facing and to ask follow-up questions.

What's needed is a way to briefly talk directly to a participant, outside of the chat.

In this tutorial, we will develop a web application where a user can send a request for a role promotion, giving the host the option to accept or decline the request. On approval, the user would be called onto the live stream as a speaker. We will use the Agora Web SDK and the Agora RTM SDK to build this sample app:

Screenshot of the project we will be developing in this tutorial

Prerequisites

  • Basic knowledge of how to work with JavaScript, JQuery, Bootstrap, and Font Awesome
  • Agora Developer Account - Sign up here
  • Know how to use the Agora Web SDK and the Agora RTM SDK

Project Setup

We will build on our existing project: Build Your Own Many To Many, Live Video Streaming Using the Agora Web SDK. You can begin by cloning this project’s GitHub repository. You will now have a project that looks like this:

Screenshot of the Agora Many to Many, Live Video Streaming UI

If you have difficulty understanding what the above code does, see this tutorial.

I have added code for entering a user’s name to the UI and the JavaScript file.

You now have a fully functional, video live streaming application, with muting and unmuting capabilities.

What do I do when someone raises their hand?

We will send channel messages using the Agora RTM SDK whenever someone raises or lowers their hand. This way, even if there are multiple hosts, all hosts will be notified and any host can promote the user or deny their request.

We listen for a button click event by an audience member. Whenever someone clicks the Raise Hand button, we toggle the text as well as the functions connected to the button. We also update the state of the global variable that we created to track whether a user’s hand is raised or not.

// Hand raise state
var handRaiseState = false;
...
// Append a raise hand button for audience (hosts cannot raise their hand)
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);
$("#raise-hand-div").append(`<button id="raise-hand" type="button" class="btn btn-live btn-sm" disabled>Raise Hand</button>`);
// Event listeners
client.on("user-published", handleUserPublished);
client.on("user-joined", handleUserJoined);
client.on("user-left", handleUserLeft);
}
...
}
...
// RTM Channel Join
var channelName = $('#channel').val();
channel = clientRTM.createChannel(channelName);
channel.join().then(() => {
console.log('AgoraRTM client channel join success.');
// Send channel message for raising hand
$(document).on('click', '#raise-hand', async function () {
fullDivId = $(this).attr('id');
if (handRaiseState === false) {
$("#raise-hand").text("Lower Hand");
handRaiseState = true;
console.log("Hand raised.");
// Inform channel that rand was raised
await channel.sendMessage({ text: "raised" }).then(() => {
console.log("Message sent successfully.");
console.log("Your message was: raised" + " sent by: " + accountName);
}).catch((err) => {
console.error("Message sending failed: " + err);
})
}
else if (handRaiseState === true) {
$("#raise-hand").text("Raise Hand");
handRaiseState = false;
console.log("Hand lowered.");
// Inform channel that rand was raised
await channel.sendMessage({ text: "lowered" }).then(() => {
console.log("Message sent successfully.");
console.log("Your message was: lowered" + " sent by: " + accountName);
}).catch((err) => {
console.error("Message sending failed: " + err);
})
}
});
// Get channel message when someone raises hand
channel.on('ChannelMessage', async function (text, peerId) {
console.log(peerId + " changed their hand raise state to " + text.text);
if (options.role === "host") {
if (text.text == "raised") {
// Ask host if user who raised their hand should be called onto stage or not
if (confirm(peerId + " raised their hand. Do you want to make them a host?")) {
// Call user onto stage
console.log("The host accepted " + peerId + "'s request.");
// Send approval message
} else {
// Inform the user that they were not made a host
console.log("The host rejected " + peerId + "'s request.");
// Send rejection message
}
} else if (text.text == "lowered") {
console.log("Hand lowered so host can ignore this.");
}
}
})
}).catch(error => {
console.log('AgoraRTM client channel join failed: ', error);
}).catch(err => {
console.log('AgoraRTM client login failure: ', err);
});
});
...
view raw informAll.js hosted with ❤ by GitHub

How do I promote or reject someone after I receive their request?

Since we want to promote or reject only the user who sends a request, we send the user acceptance and rejection updates through a peer message.

If the peer message received asks the user to change their role to a host, the user rejoins the channel as a host.

...
// Get channel message when someone raises hand
channel.on('ChannelMessage', async function (text, peerId) {
console.log(peerId + " changed their hand raise state to " + text.text);
if (options.role === "host") {
if (text.text == "raised") {
// Ask host if user who raised their hand should be called onto stage or not
$('#confirm').modal('show');
$('#modal-body').text(peerId + " raised their hand. Do you want to make them a host?");
$('#promoteAccept').click(async function () {
// Call user onto stage
console.log("The host accepted " + peerId + "'s request.");
await clientRTM.sendMessageToPeer({
text: "host"
},
peerId,
).then(sendResult => {
if (sendResult.hasPeerReceived) {
console.log("Message has been received by: " + peerId + " Message: host");
} else {
console.log("Message sent to: " + peerId + " Message: host");
}
}).catch(error => {
console.log("Error sending peer message: " + error);
});
$('#confirm').modal('hide');
});
$("#cancel").click(async function () {
// Inform the user that they were not made a host
console.log("The host rejected " + peerId + "'s request.");
await clientRTM.sendMessageToPeer({
text: "audience"
},
peerId,
).then(sendResult => {
if (sendResult.hasPeerReceived) {
console.log("Message has been received by: " + peerId + " Message: audience");
} else {
console.log("Message sent to: " + peerId + " Message: audience");
}
}).catch((error) => {
console.log("Error sending peer message: " + error);
});
$('#confirm').modal('hide');
});
} else if (text.text == "lowered") {
$('#confirm').modal('hide');
console.log("Hand lowered so host can ignore this.");
}
}
})
// Display messages from host when they approve the request
clientRTM.on('MessageFromPeer', async function ({
text
}, peerId) {
console.log(peerId + " changed your role to " + text);
if (text === "host") {
await leave();
options.role = "host";
console.log("Role changed to host.");
await client.setClientRole("host");
await join();
$("#host-join").attr("disabled", true);
$("#audience-join").attr("disabled", true);
$("#raise-hand").attr("disabled", false);
$("#leave").attr("disabled", false);
} else if (text === "audience" && options.role !== "audience") {
alert("The host rejected your proposal to be called onto stage.");
$("#raise-hand").attr("disabled", false);
}
})
}).catch(error => {
console.log('AgoraRTM client channel join failed: ', error);
}).catch(err => {
console.log('AgoraRTM client login failure: ', err);
});
});
...

We have the application’s structure laid out and can now test the application.

Note: For testing, you can use two or more browser tabs to simulate multiple users on the call.

Conclusion

You did it!

You have successfully made a request-based role promotion service inside a web live streaming application. In case you weren’t coding along or want to see the complete, finished product, I have uploaded all the code to GitHub:

https://github.com/akshatvg/Agora-Invite-User-To-Stage

You can check out the demo of the code in action:

https://handraise.akshatvg.com

Thanks for taking the time to read my tutorial. If you have questions, please let me know with a comment. If you see room for improvement, feel free to fork the repo and make a pull request!

Other Resources

To learn more about the Agora Web SDK and other use cases, see the developer guide here.

https://agoraio-community.github.io/AgoraWebSDK-NG/api/en/index.html

https://docs.agora.io/en/Real-time-Messaging/API%20Reference/RTM_web/v1.0.0/index.html

https://docs.agora.io/en/Real-time-Messaging/messaging_web?platform=Web

https://www.agora.io

https://www.agora.io/en/blog/build-your-own-many-to-many-live-video-streaming-using-the-agora-web-sdk

You can also join our Slack channel:

https://www.agora.io/en/join-slack

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay