Floor Access Control
Floor access control is used in moderated sessions (lecture or webinar mode) where participants must request permission before they can speak or publish their camera. The moderator grants or denies each request and can revoke access at any time. The Flutter SDK provides methods and callbacks for both the participant and moderator sides of this flow.
In a moderated (lecture mode) session, participants join without the ability to publish audio or video. Floor access is the permission gate that controls who may speak at any given time. The moderator is the sole decision-maker — they approve or reject each request, and can reclaim the floor at any point. Understanding the full flow before wiring up individual methods will save you significant debugging time.
From the participant's side
- The participant taps "Raise Hand" → calls
EnxRtc.requestFloor(). - The moderator receives the
onFloorRequestReceivedcallback, which carries the requester'sclientIdandname. - The moderator either grants or denies the request. The participant receives
onGrantedFloorRequestoronDeniedFloorRequestaccordingly. - If granted, the participant can now call
EnxRtc.publish()to stream their audio and video. - When finished speaking, the participant calls
EnxRtc.finishFloor()to voluntarily release the floor. - Alternatively, the participant can retract their pending request before the moderator acts on it by calling
EnxRtc.cancelFloor().
From the moderator's side
- Incoming hand-raise requests arrive via the
onFloorRequestReceivedcallback. - The moderator calls
EnxRtc.grantFloor(clientId)to approve orEnxRtc.denyFloor(clientId)to reject each request. - To cut off an active speaker, the moderator calls
EnxRtc.releaseFloor(clientId)— this revokes floor access without waiting for the participant to finish.
Only one participant can hold floor access at a time. The moderator must release the current speaker before granting the floor to another participant.
When a moderator reconnects after a network drop, the current floor state is available in the room metadata via two fields:
raisedHands— participants with pending floor requests.approvedHands— participants currently holding active floor access.
A participant calls EnxRtc.requestFloor() to raise their hand and notify the moderator that they would like permission to speak. This method does not grant access directly — it sends a request that the moderator must act on. The participant's UI should move to a "waiting for approval" state after calling this method.
Method
EnxRtc.requestFloor();
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onFloorRequestReceived |
Moderator | A participant's floor request arrived. Payload includes the requester's clientId and name. |
On the moderator's side, handle onFloorRequestReceived to display an approval or denial UI to the moderator:
// On the moderator's side — a participant has raised their hand
EnxRtc.onFloorRequestReceived = (Map<dynamic, dynamic> map) {
print('Floor request from: ${map['name']} | ID: ${map['clientId']}');
// Show approve/deny UI to the moderator
};
After raising their hand, a participant may decide to withdraw the request before the moderator has responded. EnxRtc.cancelFloor() retracts the pending request and notifies the moderator's SDK so their queue is updated. Use this when the participant changes their mind or no longer needs the floor.
Method
EnxRtc.cancelFloor();
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onCancelledFloorRequest |
Moderator | The participant cancelled their pending request. The moderator's queue should be updated. |
onFloorCancelled |
Requesting participant | Acknowledgement that the cancellation was successfully received by the server. |
Handle onFloorCancelled on the participant's side to reset the hand-raise UI back to its default state:
// On the participant's side — cancellation confirmed
EnxRtc.onFloorCancelled = (Map<dynamic, dynamic> map) {
setState(() {
floorStatus = 'idle';
});
};
When the moderator receives an onFloorRequestReceived callback, they can approve the request by calling EnxRtc.grantFloor(clientId) with the requesting participant's client ID. This gives that participant permission to publish their audio and video streams. Remember that only one participant can hold floor access at a time — if another participant currently holds it, you must call releaseFloor() on that participant first.
Method
EnxRtc.grantFloor(String clientId);
Example
// Grant floor access from inside the onFloorRequestReceived callback
EnxRtc.onFloorRequestReceived = (Map<dynamic, dynamic> map) {
// Moderator taps "Approve" — grant the floor to the requester
EnxRtc.grantFloor(map['clientId'].toString());
};
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onGrantedFloorRequest |
Approved participant | Floor access has been granted. The participant may now publish their audio and video stream. |
Handle onGrantedFloorRequest on the participant's side to trigger stream publishing. Do not begin publishing before this callback fires — the server enforces the permission gate.
// On the approved participant's side
EnxRtc.onGrantedFloorRequest = (Map<dynamic, dynamic> map) {
print('Floor granted — publishing now');
EnxRtc.publish();
};
The moderator calls EnxRtc.denyFloor(clientId) to reject a participant's floor request. The denied participant is notified via the onDeniedFloorRequest callback so their UI can return to the default state — hand lowered, no pending request.
Method
EnxRtc.denyFloor(String clientId);
Example
// Moderator rejects the floor request
EnxRtc.denyFloor(clientId);
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onDeniedFloorRequest |
Denied participant | The floor request was rejected. Payload includes clientId and a reason code in msg. |
Handle onDeniedFloorRequest on the participant's side to clear the hand-raise state and optionally surface the reason to the user:
// On the denied participant's side
EnxRtc.onDeniedFloorRequest = (Map<dynamic, dynamic> map) {
print('Floor denied: ${map['msg']}');
setState(() {
floorStatus = 'denied';
});
};
Once a participant has been granted the floor and has finished speaking, they should call EnxRtc.finishFloor() to voluntarily release the floor back to the moderator. This is the participant-initiated way to end their speaking turn — as opposed to the moderator forcibly revoking access with releaseFloor(). Calling this method is good practice and keeps the moderator's queue in sync.
Method
EnxRtc.finishFloor();
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onFinishedFloorRequest |
Moderator | The participant finished speaking and voluntarily released the floor. |
onFloorFinished |
Participant | Acknowledgement that the floor was successfully released by the server. |
Handle onFloorFinished on the participant's side to stop publishing and reset the speaking UI:
// On the participant's side — floor release confirmed
EnxRtc.onFloorFinished = (Map<dynamic, dynamic> map) {
print('You have finished your floor access');
// Optionally unpublish your stream
};
The moderator calls EnxRtc.releaseFloor(clientId) to forcibly revoke a participant's currently active floor access. This is different from a participant voluntarily finishing their turn — here, the moderator is cutting off an active speaker. Use this when a participant is speaking for too long, the session needs to move on, or the moderator wants to grant the floor to a different participant.
When releaseFloor() is called, the affected participant receives onReleasedFloorRequest and should stop publishing their stream in response.
Method
EnxRtc.releaseFloor(String clientId);
Example
// Moderator revokes an active speaker's floor access
EnxRtc.releaseFloor(clientId);
Callbacks
| Callback | Who receives it | When it fires |
|---|---|---|
onReleasedFloorRequest |
Affected participant | Floor access was revoked by the moderator. The participant's stream should be unpublished. |
Handle onReleasedFloorRequest on the participant's side to stop publishing and update the speaking UI accordingly:
// On the participant's side — moderator revoked floor access
EnxRtc.onReleasedFloorRequest = (Map<dynamic, dynamic> map) {
print('Your floor access was released by the moderator');
// Stop publishing your stream and reset UI
};
If a participant or moderator reconnects mid-session, the floor access state may have changed while they were disconnected — new hand-raises may have come in, grants may have been issued, or speakers may have finished. Without restoring this state, the moderator's queue could appear empty or stale, leading to a broken experience.
The SDK solves this by including two fields in the onRoomConnected payload after a reconnect:
raisedHands— an array of participants who currently have pending floor requests.approvedHands— an array of participants who currently hold active floor access.
Read both fields in your onRoomConnected handler to rebuild the floor queue UI correctly as soon as the connection is restored:
EnxRtc.onRoomConnected = (Map<dynamic, dynamic> map) {
// Restore floor state after reconnect
List<dynamic> raisedHands = map['raisedHands'] ?? [];
List<dynamic> approvedHands = map['approvedHands'] ?? [];
print('Pending requests: ${raisedHands.length}');
print('Active speakers: ${approvedHands.length}');
setState(() {
pendingRequests = raisedHands;
activeFloorUsers = approvedHands;
});
};
raisedHands and approvedHands on reconnect to restore the floor queue UI. Without this, the moderator's view will be out of sync with the actual session state.