Connecting to a Video Session
This guide walks you through the complete flow of joining an EnableX video room from a web browser — from checking device access all the way to cleanly disconnecting. Follow the steps in order; each step depends on the previous one succeeding.
The Full Connection Flow
Before a participant can see or hear anyone in a room, six things must happen in sequence:
- Enumerate devices — discover available cameras, microphones, and speakers.
- Initialize a local stream — request media access and configure the stream you will publish.
- Connect to the room — authenticate with a JWT token and open the signalling channel.
- Publish your stream — send your audio/video to the room so others can receive it.
- Subscribe to remote streams — receive and play other participants' video via the active talker list.
- Disconnect — cleanly close media and signalling when done.
If you want a one-call shortcut that wraps steps 2–4, use EnxRtc.joinRoom(). For full control over each step, follow the sections below in order.
Get Available Media Devices
EnxRtc.getDevices() asks the browser for camera, microphone, and speaker access, then returns a list of all connected devices. Call this before initializing any stream so you can let the user choose which camera and microphone to use — or just confirm devices are available before proceeding.
- Method:
EnxRtc.getDevices(Callback) - Event on stream:
media-access-allowed— fired when the user grants device permission - Event on stream:
media-access-denied— fired when the user denies permission
The callback returns the full list of devices on success. In parallel, the browser emits permission events on the stream object. Here is the complete pattern:
// Step 1: Create a stream instance to bind permission events on
var localStream = EnxRtc.EnxStream({ audio: true, video: true, data: true });
// Step 2: Bind permission events BEFORE calling getDevices
localStream.addEventListener("media-access-allowed", function(event) {
/*
event = {
stream: EnxStreamObject, // the stream instance that was granted access
type: "media-access-allowed"
}
*/
console.log("Media access granted — stream ready:", event.stream);
});
localStream.addEventListener("media-access-denied", function(event) {
/*
event = {
type: "media-access-denied",
msg: Error // the underlying browser permission error
}
*/
console.error("Media access denied:", event.msg);
showPermissionErrorUI();
});
// Step 3: Enumerate all connected devices
EnxRtc.getDevices(function(response) {
if (response.result === 0) {
var cameras = response.devices.cam; // Array of camera objects
var mics = response.devices.mic; // Array of microphone objects
var speakers = response.devices.speaker; // Array of speaker objects
// Populate your device picker dropdowns
populateDeviceDropdowns(cameras, mics, speakers);
} else {
// response.result === 1144 → access denied by user or policy
// response.result === 1146 → getUserMedia execution failed
console.error("Device enumeration failed, code:", response.result);
}
});
Success Response — Device List
When result is 0, the callback payload contains three arrays — one for cameras, one for microphones, one for speakers. Each entry is a device descriptor:
{
"result": 0,
"devices": {
"cam": [
{
"deviceId": "abc123",
"groupId": "group456",
"label": "FaceTime HD Camera",
"kind": "videoinput"
}
],
"mic": [
{
"deviceId": "def789",
"groupId": "group456",
"label": "Built-in Microphone",
"kind": "audioinput"
}
],
"speaker": [
{
"deviceId": "ghi012",
"groupId": "group789",
"label": "Built-in Speakers",
"kind": "audiooutput"
}
]
}
}
response.devices.cam and response.devices.mic to populate dropdown menus. Store the selected deviceId and pass it to the stream options in Step 2.
Permissions inside an iframe
If your application runs inside an <iframe>, the browser requires explicit permission on the iframe element itself:
<iframe src="https://your-app.example.com/room" allow="camera; microphone"></iframe>
| Error Code | Description |
|---|---|
| 1144 | Device access denied by user or browser policy |
| 1146 | Failed to execute getUserMedia on media devices |
| 1150 | Unknown device error |
Create and Initialize a Local Stream
A local stream represents the audio and video that you will publish into the room. Create it by instantiating EnxStream with a configuration object, then calling .init() to request media access and acquire the actual device tracks.
- Method:
EnxRtc.EnxStream(StreamOpt).init()
Stream Options (StreamOpt)
| Option | Type | Description |
|---|---|---|
audio | Boolean / Object | true = default mic. Object: { deviceId: "ID" } for a specific mic. |
video | Boolean / Object | true = default camera. Object: { deviceId: "ID" } or { facingMode: "user" } for mobile front cam. |
data | Boolean | Include a data channel. Required for chat and custom signalling. |
screen | Boolean | Set true to share the screen instead of camera. |
audioMuted | Boolean | Join with audio muted. Default: false. |
videoMuted | Boolean | Join with video muted (camera off). Default: false. |
attributes | Object | Custom key/value pairs attached to this stream (e.g., participant name). |
videoSize | Array | [minW, minH, maxW, maxH] — e.g., [320, 180, 1280, 720] for HD. |
maxVideoLayers | Number | 1=HD only, 2=HD+SD, 3=HD+SD+LD. More layers = better adaptive quality for receivers. |
var streamOpt = {
audio: true, // Use default microphone
video: true, // Use default camera
data: true, // Enable data channel (needed for chat)
audioMuted: false, // Join with mic on
videoMuted: false, // Join with camera on
attributes: {
name: "Alice"
},
videoSize: [320, 180, 1280, 720], // HD
maxVideoLayers: 3 // Publish HD + SD + LD layers
};
var localStream = EnxRtc.EnxStream(streamOpt).init();
Using a Specific Device
// After getDevices() you have deviceId values
var streamOpt = {
audio: { deviceId: selectedMicId },
video: { deviceId: selectedCamId },
data: true,
attributes: { name: "Bob" }
};
var localStream = EnxRtc.EnxStream(streamOpt).init();
Mobile: Front or Rear Camera
var streamOpt = {
audio: true,
video: { facingMode: "user" }, // "user" = front, "environment" = rear
data: true
};
var localStream = EnxRtc.EnxStream(streamOpt).init();
| Error Code | Description |
|---|---|
| 1142 | The Device ID is invalid |
| 1143 | The requested device was not found |
| 1144 | Device access denied |
| 1145 | Failed to start the video source |
| 1147 | Video width constraint not satisfied |
| 1148 | Video height constraint not satisfied |
Connect to a Room
With your local stream initialized, create a room object and connect to it using the JWT token you received from your application server. The SDK establishes a WebSocket connection to the EnableX signalling server. Once connected, you will receive the room-connected event with complete room metadata — who is already present, what streams are in the room, and the room configuration.
- Initialize room:
EnxRtc.EnxRoom({ token, speakerId }) - Connect:
room.connect(ReConnectOpt)
ReConnectOpt Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
allow_reconnect | Boolean | true | Set false to disable auto-reconnect on network drop. |
number_of_attempts | Number | 3 | Max reconnect attempts before giving up. Range: 1 to any. |
timeout_interval | Number | — | Milliseconds to wait between reconnect attempts. |
var reConnectOpt = {
allow_reconnect: true,
number_of_attempts: 3,
timeout_interval: 10000 // 10 seconds between attempts
};
// Create the room object (pass the token + optional speaker)
var room = EnxRtc.EnxRoom({ token: YOUR_JWT_TOKEN });
// Set up event listeners BEFORE calling connect()
room.addEventListener("room-connected", function(event) {
// event contains full room metadata:
// event.streams — streams already in the room
// event.room.me — your own user info
// event.room.waitRoom — true if you must wait for moderator
console.log("Connected to room:", event.room.roomId);
// If it's a "wait for moderator" room and you're a participant, hold off
if (event.room.waitRoom && event.room.me.role !== "moderator") {
showWaitingUI();
} else {
// Proceed to publish (Step 4)
publishStream();
}
});
room.addEventListener("room-error", function(error) {
console.error("Failed to connect:", error);
});
room.addEventListener("room-allowed", function(event) {
// Fired when you were awaiting and the moderator lets you in
publishStream();
});
room.addEventListener("user-connected", function(event, user) {
// A new participant joined — update your participant list UI
});
room.addEventListener("user-awaited", function(event, user) {
// Someone is waiting at the door (moderator only) — show approve/deny UI
});
// Now connect
room.connect(reConnectOpt);
The SDK emits events synchronously on connect. If you register listeners after calling room.connect(), you may miss the room-connected event.
| Error Code | Description |
|---|---|
| 1130 | Waiting for the moderator to join first |
| 1171 | Room is not connected |
| 1172 | Failed to connect to room |
Publish Your Local Stream
Publish the local stream you initialized in Step 2 into the connected room. This makes your audio and video available to all other participants. Do this inside the room-connected handler.
- Method:
EnxRoom.publish(LocalStream, PublishOpt, Callback) - Event:
stream-added— broadcast to everyone in the room when a new stream is published - Event:
stream-failed— fired to the publisher if publishing fails
function publishStream() {
var publishOpt = {
minVideoBW: 150, // Minimum video bitrate (kbps)
maxVideoBW: 1200 // Maximum video bitrate (kbps)
};
room.publish(localStream, publishOpt, function(streamId) {
console.log("Stream published, ID:", streamId);
});
}
// Notification to everyone — including yourself
room.addEventListener("stream-added", function(event) {
if (localStream.getID() === event.stream.getID()) {
// Your own stream was confirmed published
} else {
// Someone else published — subscribe to their stream (Step 5)
subscribeToStream(event.stream);
}
});
room.addEventListener("stream-failed", function(event) {
console.error("Failed to publish stream");
});
Receiving Remote Streams via Active Talkers
EnableX does not send every participant's stream to every other participant simultaneously. Instead, it maintains an Active Talker list — up to 12 of the most recently speaking users — and notifies all endpoints whenever the list changes via the active-talkers-updated event.
Your job on receiving this event is to:
- Iterate the
activeListarray. - Look up each stream from
room.remoteStreamsby itsstreamId. - Subscribe to any stream you have not yet subscribed to.
- Call
stream.play(domElementId)to render it in a video element.
// Subscribe when a new stream enters the room
function subscribeToStream(stream) {
var subsOpt = { audio: true, video: true, data: true };
room.subscribe(stream, subsOpt, function(err) {
if (err) {
console.error("Subscribe failed:", err);
}
});
}
// Acknowledgment when subscription succeeds — now you can play
room.addEventListener("stream-subscribed", function(event) {
event.stream.play("remote-video-container", { fit: "cover" });
});
// Handle the active talker list updates
room.addEventListener("active-talkers-updated", function(event) {
var talkerList = event.message.activeList;
// talkerList is an array of:
// { clientId, name, streamId, mediatype, videomuted, pinned }
talkerList.forEach(function(talker) {
if (talker.streamId) {
var stream = room.remoteStreams.get(talker.streamId);
if (stream) {
// Play in a grid slot identified by streamId
stream.play("slot-" + talker.streamId, { fit: "cover" });
}
}
});
});
The list updates frequently in noisy rooms — any time a new person starts speaking, the list is re-sorted. The most recently active speaker moves to the top. Subscribe to the active-talkers-updated event for every update and refresh your video grid accordingly. Special stream IDs: 101 = screen share, 102 = canvas stream.
Subscribe to Streams Already in the Room
When you first connect, other participants may already be publishing. Their streams are available in the room metadata returned with the room-connected event:
room.addEventListener("room-connected", function(event) {
var subsOpt = { audio: true, video: true, data: true };
// event.streams contains streams already in the room
if (event.streams) {
event.streams.forEach(function(stream) {
room.subscribe(stream, subsOpt);
});
}
// Then publish your own stream
publishStream();
});
Disconnect from the Room
EnxRoom.disconnect() cleanly closes both the media connection and the signalling WebSocket, releases all device tracks, and unpublishes any active streams. Call it when the user leaves the session.
- Method:
EnxRoom.disconnect() - Event:
room-disconnected— acknowledgment to your own endpoint - Event:
user-disconnected— broadcast to all other participants
room.disconnect();
room.addEventListener("room-disconnected", function(event) {
// You are now fully disconnected — clean up UI, redirect, etc.
showEndScreen();
});
room.addEventListener("user-disconnected", function(event) {
// Another participant left — remove their video tile from the grid
removeVideoTile(event.stream);
});
| Event Cause Code | Description |
|---|---|
| 2001 | Disconnected by the moderator |
| 2002 | Unexpected disconnection |
| 2003 | Conference expired |
| 2004 | Network failure |
| 2005 | Media handshake failed |
| 2006 | Room access denied |
EnxRtc.joinRoom() — One-Call Setup
EnxRtc.joinRoom() combines stream initialization, room connection, and initial subscription into a single call. It is ideal when you want to get into a room quickly without managing each step separately.
- Method:
EnxRtc.joinRoom(Token, StreamOpt, Callback, ReConnectOpt) - Returns: The published local stream object
var streamOpt = {
audio: true,
video: true,
data: true,
videoSize: [320, 180, 1280, 720],
attributes: { name: "Alice" }
};
var reConnectOpt = {
allow_reconnect: true,
number_of_attempts: 3,
timeout_interval: 10000
};
var localStream = EnxRtc.joinRoom(
YOUR_JWT_TOKEN,
streamOpt,
function(success, error) {
if (error) {
// error.type e.g. "media-access-denied"
// error.msg.name for exception details
console.error("Join failed:", error.type, error.msg);
return;
}
if (success) {
room = success.room;
if (room.waitRoom && room.me.role !== "moderator") {
// Wait for moderator to allow you in
showWaitingUI();
} else {
// Subscribe to streams already in the room
var remoteStreams = success.room.streams;
remoteStreams.forEach(function(stream) {
room.subscribe(stream, { audio: true, video: true, data: true });
});
}
}
},
reConnectOpt
);
Handling Network Disconnection
A WebSocket connection can be interrupted by transient network issues. EnableX's auto-reconnect feature silently re-establishes the connection without requiring the user to rejoin manually. Configure it via ReConnectOpt when calling connect() or joinRoom().
- The last participant disconnects from an ad-hoc room (room is destroyed).
- The moderator force-drops this participant.
- The participant explicitly calls
room.disconnect().
room.addEventListener("network-disconnected", function(event) {
// Network dropped — show reconnecting indicator
showReconnectingUI();
});
room.addEventListener("network-reconnected", function(event) {
// Successfully reconnected — restore normal UI
hideReconnectingUI();
});
room.addEventListener("network-reconnect-timeout", function(event) {
// A single reconnect attempt timed out — SDK will retry if attempts remain
});
room.addEventListener("network-reconnect-failed", function(event) {
// All reconnect attempts exhausted — prompt user to rejoin manually
showRejoinPrompt();
});
| Error Code | Description |
|---|---|
| 1163 | Network disconnected |
| 1164 | Network reconnected successfully |
| 1165 | Reconnect attempt timed out |
| 1167 | Failed to re-publish/subscribe streams after reconnect |
| 1178 | Auto-reconnect unavailable — no participants remain in ad-hoc room |
| 4118 | Reconnect failed — room has been deleted |