Floor Access Control

Floor access control is used in Lecture Mode sessions where only the moderator can publish audio and video by default. Participants who want to speak must request the floor, and the moderator decides whether to grant or deny that request. This page covers all the methods and events needed to implement a full floor access workflow for both participants and moderators.

How Floor Access Works

In Lecture Mode, the room has a single presenter (the moderator) and an audience (participants). The floor access workflow follows this flow:

  1. A participant calls requestFloor() — this signals to the moderator that they want to speak.
  2. The moderator receives a floor-requested event. The moderator can view all pending requests via room.raisedHands.
  3. The moderator calls grantFloor(clientId) to allow the participant to publish, or denyFloor(clientId) to decline.
  4. If granted, the participant receives a floor-granted event and can begin publishing audio/video.
  5. When the participant is done, they call finishFloor() to voluntarily give up the floor.
  6. Alternatively, the moderator can call releaseFloor(clientId) to take the floor back at any time.

Moderators can also invite a participant to the floor without waiting for a request — useful when the moderator wants to give a specific attendee a turn to speak.

Important: Floor Access Control is only applicable in Lecture Mode (roomMode: "lecture"). In Group Mode, all participants can publish freely. You can switch room modes at runtime using room.switchRoomMode().
Tracking Raised Hands & Approved Participants

The EnxRoom object exposes two read-only arrays that help the moderator track the state of floor requests at any time:

room.raisedHands

An array of clientId strings for all participants who have requested the floor and are waiting for a decision. Inspect this list to display a queue of pending speakers in your UI.

// Show pending floor requests in the moderator UI
console.log("Pending requests:", room.raisedHands);
// e.g. ["client-abc", "client-xyz"]

room.approvedHands

An array of clientId strings for participants who have been granted floor access and are currently allowed to publish.

// Show active speakers
console.log("Active speakers:", room.approvedHands);
Participant: Requesting the Floor

Request Floor Access

A participant calls this method to raise their hand and signal to the moderator that they want to speak. The participant cannot publish until the moderator grants the floor.

room.requestFloor(function(response) {
  if (response.result === 0) {
    console.log("Floor request sent — waiting for moderator approval");
  } else {
    console.error("Request failed:", response.error);
  }
});

When the request is received by the moderator, the moderator's room fires a floor-requested event:

// Moderator side
room.on("floor-requested", function(event) {
  console.log("Floor requested by:", event.clientId, event.name);
  // Show approval UI to moderator
});
Error CodeMeaning
4101Not applicable in current room mode (must be Lecture Mode)
1702Floor already requested — cannot send a duplicate request

Cancel a Floor Request

If a participant changes their mind after raising their hand, they can cancel the pending floor request before the moderator acts on it.

room.cancelFloor(function(response) {
  if (response.result === 0) {
    console.log("Floor request cancelled");
  }
});

The moderator's room fires a floor-cancelled event:

// Moderator side
room.on("floor-cancelled", function(event) {
  console.log("Floor request cancelled by:", event.clientId);
  // Remove from pending list in UI
});
Error CodeMeaning
4101Not applicable in current room mode
1707No active floor request to cancel

Finish Floor (Participant Gives Up the Floor)

Once a participant is done speaking, they call finishFloor() to voluntarily return the floor. This allows the next participant in the queue to be granted access.

room.finishFloor(function(response) {
  if (response.result === 0) {
    console.log("Floor returned — you can no longer publish");
    // Stop publishing the local stream
  }
});

All moderators receive a floor-finished event:

// Moderator side
room.on("floor-finished", function(event) {
  console.log("Participant finished floor:", event.clientId);
});
Participant: Responding to a Floor Invitation

When a moderator invites a participant to the floor (using inviteToFloor()), the participant receives a floor-invited event and must respond — either accepting or rejecting the invitation.

Accept a Floor Invitation

The participant calls this method to accept the moderator's invitation and begin publishing.

// Participant receives invitation
room.on("floor-invited", function(event) {
  console.log("You have been invited to speak");

  // Accept the invitation
  room.acceptInviteFloorRequest(function(response) {
    if (response.result === 0) {
      console.log("Floor invitation accepted — you can now publish");
    }
  });
});

All moderators receive a floor-accepted event confirming the participant accepted:

// Moderator side
room.on("floor-accepted", function(event) {
  console.log("Participant accepted floor invite:", event.clientId);
});

Reject a Floor Invitation

The participant can decline the floor invitation if they don't want to speak.

room.rejectInviteFloor(function(response) {
  if (response.result === 0) {
    console.log("Floor invitation declined");
  }
});

All moderators receive a floor-rejected event:

// Moderator side
room.on("floor-rejected", function(event) {
  console.log("Participant rejected floor invite:", event.clientId);
});
Moderator: Managing the Floor

Grant Floor Access

The moderator uses this method to approve a participant's floor request. Once granted, the participant can publish their audio and video into the session.

// Moderator grants floor to a specific participant
room.grantFloor(clientId, function(response) {
  if (response.result === 0) {
    console.log("Floor granted to:", clientId);
  } else {
    console.error("Grant failed:", response.error);
  }
});

The participant who was granted access receives a floor-granted event:

// Participant side
room.on("floor-granted", function(event) {
  console.log("Floor granted — you can now publish");
  // Call room.publish() to start streaming
});
Error CodeMeaning
4101Not applicable in current room mode
1711Specified client not found in session
1703Participant has not requested the floor
1173Maximum number of floor holders already active
1131Session is locked — no new publishers allowed
1710Participant already has floor access

Deny a Floor Request

The moderator calls this to decline a participant's request to speak. The participant remains in the audience.

room.denyFloor(clientId, function(response) {
  if (response.result === 0) {
    console.log("Floor request denied for:", clientId);
  }
});

The participant receives a floor-denied event:

// Participant side
room.on("floor-denied", function(event) {
  console.log("Floor request was denied by the moderator");
});
Error CodeMeaning
4101Not applicable in current room mode
1711Specified client not found in session

Release a Floor (Take Back Access)

The moderator can revoke floor access from a participant at any time, even if the participant has not finished speaking. This is useful for moderating sessions with a strict time allocation per speaker.

room.releaseFloor(clientId, function(response) {
  if (response.result === 0) {
    console.log("Floor released from:", clientId);
  }
});

The participant whose floor was released receives a release-floor event:

// Participant side
room.on("release-floor", function(event) {
  console.log("The moderator has ended your floor access");
  // Stop publishing
});

Invite a Participant to the Floor

Instead of waiting for a participant to raise their hand, the moderator can proactively invite any participant to speak. The participant will receive the invitation and must explicitly accept or reject it.

room.inviteToFloor(clientId, function(response) {
  if (response.result === 0) {
    console.log("Floor invitation sent to:", clientId);
  } else {
    console.error("Invitation failed:", response.error);
  }
});

The invited participant receives a floor-invited event:

// Participant side
room.on("floor-invited", function(event) {
  console.log("You are invited to speak by the moderator");
  // Show Accept / Decline UI
});
Error CodeMeaning
5086Specified client not found
5097Client already has floor access
5006Maximum floor holders already active
5045An invitation is already pending for this client

Cancel a Floor Invitation

If the moderator changes their mind before the participant responds, they can cancel the pending invitation.

room.cancelFloorInvite(clientId, function(response) {
  if (response.result === 0) {
    console.log("Floor invitation cancelled for:", clientId);
  }
});

The participant receives a floor-invite-cancelled event:

// Participant side
room.on("floor-invite-cancelled", function(event) {
  console.log("The floor invitation was cancelled by the moderator");
  // Hide the Accept / Decline UI
});
Floor Event Reference

The following events are emitted on the EnxRoom object during floor access workflows. Some events fire on the participant's room, others on the moderator's room, and some on both.

Event Received By Description
floor-requested Moderator A participant has requested the floor. event.clientId identifies who.
floor-cancelled Moderator A participant has cancelled their floor request before receiving a decision.
floor-granted Participant The moderator has granted floor access — participant may now publish.
floor-denied Participant The moderator has denied the floor request.
floor-finished Moderator The participant voluntarily returned the floor.
release-floor Participant The moderator has revoked the participant's floor access.
floor-invited Participant The moderator has invited this participant to speak.
floor-invite-cancelled Participant The moderator cancelled a pending floor invitation.
floor-accepted Moderator An invited participant accepted the floor invitation.
floor-rejected Moderator An invited participant declined the floor invitation.
Full Workflow Example

Here is a combined example showing how a moderator and a participant would wire up the full floor access flow together:

// ── PARTICIPANT SIDE ────────────────────────────────────────────────────────

// Step 1: Request the floor
document.getElementById("raise-hand-btn").addEventListener("click", function() {
  room.requestFloor(function(response) {
    if (response.result === 0) {
      console.log("Hand raised — waiting for moderator approval");
      document.getElementById("status").innerText = "Waiting for approval...";
    }
  });
});

// Step 2: Listen for floor grant or denial
room.on("floor-granted", function(event) {
  console.log("Floor granted! Publishing now...");
  room.publish(localStream, {}, function(res, error) {
    if (!error) console.log("Publishing started");
  });
});

room.on("floor-denied", function(event) {
  console.log("Floor request was denied");
  document.getElementById("status").innerText = "Request denied";
});

// Step 3: Finish speaking
document.getElementById("done-btn").addEventListener("click", function() {
  room.finishFloor(function(response) {
    if (response.result === 0) {
      console.log("Floor returned");
      room.unpublish(localStream, function() {
        console.log("Unpublished");
      });
    }
  });
});

// Listen for moderator revoking the floor
room.on("release-floor", function(event) {
  console.log("Floor revoked by moderator");
  room.unpublish(localStream, function() {
    console.log("Unpublished");
  });
});

// ── MODERATOR SIDE ──────────────────────────────────────────────────────────

// Listen for incoming requests
room.on("floor-requested", function(event) {
  console.log("Floor requested by:", event.clientId, event.name);
  // Add to moderator's queue UI
  addToQueue(event.clientId, event.name);
});

// Grant the floor
function grantSpeaker(clientId) {
  room.grantFloor(clientId, function(response) {
    if (response.result === 0) {
      console.log("Granted:", clientId);
    }
  });
}

// Deny the floor
function denySpeaker(clientId) {
  room.denyFloor(clientId, function(response) {
    if (response.result === 0) {
      console.log("Denied:", clientId);
    }
  });
}

// Take back the floor mid-speech
function revokeFloor(clientId) {
  room.releaseFloor(clientId, function(response) {
    if (response.result === 0) {
      console.log("Floor released from:", clientId);
    }
  });
}

// Proactively invite someone to speak
function inviteSpeaker(clientId) {
  room.inviteToFloor(clientId, function(response) {
    if (response.result === 0) {
      console.log("Invitation sent to:", clientId);
    }
  });
}