This article was written in collaboration with Yinping Ge
Breakout Room is a common feature required by many customers, especially those in education. It often allows "splitting the main meeting room into separate ones", "splitting participants into these breakout rooms", and "participants to send messages to the host no matter which room they are in", etc.
With Vonage Video API, there is more than one way to implement such a Breakout Room feature for your application.
One way is to create a big Video Session with logic controlling which streams to subscribe to for each user. Another one is "implement breakout rooms as separate sessions", then "connect participants to these different sessions created for each breakout room".
This tutorial explains how to use the separate sessions to build the Breakout Room feature into our Demo Application, which uses signaling API to implement breakout room management re-uses the Publisher object when switching back and forth among rooms
Hope the following graphs can give you a general idea first. Initially, all participants connect to the main-room’s session:
After the host clicks the button to create breakout rooms, the application server calls Vonage Video API to create a session for each breakout room and returns these session IDs to each participant.
Then the application connects participants to these breakout rooms' sessions by letting participants choose a room to join or splitting participants into different rooms automatically, depending on what option the host has selected when creating the breakout rooms. (Host can choose between "Assign automatically" and "Let participants choose a room").
Prerequisites
A Vonage Video API account. Click Sign Up to create one if you don't have one already. ReactJS version >= 16.8 Node.js version >= 16.13 PostgreSQL 14 as the Database, you can choose any storage you prefer
You should be able to see all the dependencies in the GitHub repo and we’d suggest you always use the latest version of Vonage SDK. The versions listed here were the ones used when we were working on this demo app.
Application’s Server and Database Design
The application server creates rooms, creates sessions, generates tokens, maintains the rooms and participants, and sends signaling messages to rooms.
The application server acts as a "relay" by utilizing the Signaling-REST API for passing messages between different rooms/sessions for scenarios like one participant needs to raise-hand to the host who’s in a different room (i.e. connected to another session), and for the main feature: breakout rooms management. We will explain in detail later how we use the signaling messages in managing breakout rooms.
When running the application server, the room table will be created if non-exist. I understand that the room table might be a bit more complex in real life. Here we just list the basic data we need. The script to create the room table is as below:
CREATE TABLE IF NOT EXISTS rooms(
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) DEFAULT NULL,
session_id VARCHAR(255) DEFAULT NULL,
main_room_id VARCHAR(255) DEFAULT NULL,
max_participants SMALLINT DEFAULT 0
)
The session_id
stores the id of a session associated with the room. The max_participants
defines the maximum number of participants the room allows. The main_room_id
differentiates whether this is a breakout room that belongs to the main room or just a main room that can have breakout rooms: when it is set to NULL
, it is a main room; otherwise, it is a breakout room and its value should be set to the room id of its main room.
Initially, on the log-in page, all users choose to join one room, aka the main room. Upon receiving the front-end request, the application server calls Video API to create a session for this main room and adds a record to the room table with session_id
set to the id of the session created and main_room_id
set to NULL
. Then it returns the session_id
to all logged-in users for them to connect to the session.
When the meeting is on-going and a host user decides to create breakout rooms, after they submits options listed in "Breakout Room Control", such as how many breakout rooms to be created", "Let participants choose room", or "Assign automatically", etc. Front-end sends a createSession
request with the parameter breakoutRooms
carrying the above selections to the application server, which will then create a session for each breakout-room accordingly and store the session id and other information to the room table, one record for each breakout room with main_room_id
set to the main room id.
Use Signaling API to Implement Breakout Room Management
The Room object holds the references to the session, message(breakoutRoomSignal) and participants and provides the entries for creating breakout rooms and managing participants.
The application uses Signaling-REST API to send messages to clients connected to all sessions related to a main-room/breakout-room, informing of room changes, timer and raise-hand requests, etc.
For example, signaling message with below type and data is to inform application users of new breakout rooms being created and they can choose one to join:
{
"type": "signal:breakout-room",
"data": {
"message": "roomCreated (chooseroom)",
"breakoutRooms": \[], //array of available rooms
}
}
signaling message to inform "all rooms have been removed":
{
"type": "signal:breakout-room",
"data": {
"message": "allRoomRemoved",
"breakoutRooms": \[...],
}
}
inform a participant that is moved from one (breakout) room to another:
{
"type": "signal:breakout-room",
"data": {
"message": "'participantMoved'",
"breakoutRooms": \[...],
}
}
while, a signaling message with type set to
signal:count-down-timer
is to inform of a timer:
{
"type": "signal:count-down-timer",
"data": {
"period": 1,
}
}
For these breakoutRoomSignal
messages, the app takes actions accordingly, for example for participantMoved
, it moves the participant to the assigned room.
if (mMessage.breakoutRoomSignal.message === 'participantMoved' && roomAssigned && (!currentRoomAssigned || currentRoomAssigned.id !== roomAssigned.id)) {
setCurrentRoomAssigned(roomAssigned);
mNotification.openNotification("Room assigned by Host/Co-host", `You will be redirected to Room: ${roomAssigned.name} in 5 seconds.`, () => handleChangeRoom(roomAssigned.name))
}
Within handleChangeRoom
, the application will leave the current room (by disconnecting from its associated session) and join to the assigned room (by connecting to its associated session).
async function handleChangeRoom(publisher, roomName) {
const newRooms = \ [...mMessage.breakoutRooms];
let targetRoom = newRooms.find((room) => room.name === roomName);
await mSession.session.unpublish(publisher);
await mSession.session.disconnect();
const connectionSuccess = await connect(mSession.user, targetRoom ? targetRoom.id : '');
if (!connectionSuccess) {
// Force connect to main room;
targetRoom = null;
roomName = '';
await connect(mSession.user);
}
let data = {
fromRoom: currentRoom.name,
toRoom: roomName ? roomName : mainRoom.name,
participant: mSession.user.name
}
setInBreakoutRoom(targetRoom && targetRoom.name !== mainRoom.name ? targetRoom : null);
}
Re-Use the Publisher Object When Switching Back and Forth Among Rooms
When a participant leaves the main room and joins a breakout room (or otherwise), it is recommended to re-use the Publisher object to save resources.
For each type": "signal:breakout-room
message that can lead a client to leave a room and join another room, eg. roomCreated (automatic)
, what the application does is to disconnect from a session and then connect to another session. Within the process, the stream published to the previous session will be destroyed and the event streamDestroyed will be dispatched to the publisher client. In order to retain the Publisher object for reuse, the method preventDefault of the streamDestroyed event should be called.
function handleStreamDestroyed(e) {
if (e.stream.name !== "sharescreen") e.preventDefault();
if (e.reason === 'forceUnpublished') {
console.log('You are forceUnpublished');
setStream({
...e.stream
})
setPublisher({
...e.stream.publisher
})
}
}
The demo application re-uses this Publisher object to publish to the session associated with the breakout room.
async function publish(
user,
extraData
) {
try {
if (!mSession.session) throw new Error("You are not connected to session");
if (!publisher || publisherOptions.publishVideo !== hasVideo || publisherOptions.publishAudio !== hasAudio) {
if (publisher) resetPublisher();
const isScreenShare = extraData && extraData.videoSource === 'screen' ? true : false;
const options = {
insertMode: "append",
name: user.name,
publishAudio: isScreenShare ? true : hasVideo,
publishVideo: isScreenShare ? true : hasAudio,
style: {
buttonDisplayMode: "off",
nameDisplayMode: displayName ? "on" : "off"
}
};
const finalOptions = Object.assign({}, options, extraData);
setPublisherOptions(finalOptions);
const newPublisher = OT.initPublisher(containerId, finalOptions);
publishAttempt(newPublisher, 1, isScreenShare);
} else {
publishAttempt(publisher);
}
} catch (err) {
console.log(err.stack);
}
}
Conclusion
Using the way of creating separate sessions for the breakout room feature, you only need to worry about connecting participants to the right session. Take a look at the code for more details and hopefully, you find this insightful for your breakout room application.