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.

Connection Flow

The Full Connection Flow

Before a participant can see or hear anyone in a room, six things must happen in sequence:

  1. Enumerate devices — discover available cameras, microphones, and speakers.
  2. Initialize a local stream — request media access and configure the stream you will publish.
  3. Connect to the room — authenticate with a JWT token and open the signalling channel.
  4. Publish your stream — send your audio/video to the room so others can receive it.
  5. Subscribe to remote streams — receive and play other participants' video via the active talker list.
  6. 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.

Step 1 — Enumerate Devices

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.

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"
      }
    ]
  }
}
Build a device picker. Iterate over 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 CodeDescription
1144Device access denied by user or browser policy
1146Failed to execute getUserMedia on media devices
1150Unknown device error
Step 2 — Initialize a Local Stream

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.

Stream Options (StreamOpt)

OptionTypeDescription
audioBoolean / Objecttrue = default mic. Object: { deviceId: "ID" } for a specific mic.
videoBoolean / Objecttrue = default camera. Object: { deviceId: "ID" } or { facingMode: "user" } for mobile front cam.
dataBooleanInclude a data channel. Required for chat and custom signalling.
screenBooleanSet true to share the screen instead of camera.
audioMutedBooleanJoin with audio muted. Default: false.
videoMutedBooleanJoin with video muted (camera off). Default: false.
attributesObjectCustom key/value pairs attached to this stream (e.g., participant name).
videoSizeArray[minW, minH, maxW, maxH] — e.g., [320, 180, 1280, 720] for HD.
maxVideoLayersNumber1=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 CodeDescription
1142The Device ID is invalid
1143The requested device was not found
1144Device access denied
1145Failed to start the video source
1147Video width constraint not satisfied
1148Video height constraint not satisfied
Step 3 — Connect to the Room

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.

ReConnectOpt Parameters

ParameterTypeDefaultDescription
allow_reconnectBooleantrueSet false to disable auto-reconnect on network drop.
number_of_attemptsNumber3Max reconnect attempts before giving up. Range: 1 to any.
timeout_intervalNumberMilliseconds 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);
Add listeners before calling connect()

The SDK emits events synchronously on connect. If you register listeners after calling room.connect(), you may miss the room-connected event.

Error CodeDescription
1130Waiting for the moderator to join first
1171Room is not connected
1172Failed to connect to room
Step 4 — Publish Your Stream

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.

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");
});
Step 5 — Receive Remote Streams

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:

  1. Iterate the activeList array.
  2. Look up each stream from room.remoteStreams by its streamId.
  3. Subscribe to any stream you have not yet subscribed to.
  4. 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" });
      }
    }
  });
});
Active Talker list behaviour

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();
});
Step 6 — Disconnect

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.

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 CodeDescription
2001Disconnected by the moderator
2002Unexpected disconnection
2003Conference expired
2004Network failure
2005Media handshake failed
2006Room access denied
Quick Join (Alternative)

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.

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
);
Network & Reconnection

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().

When auto-reconnect does NOT apply
  • 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 CodeDescription
1163Network disconnected
1164Network reconnected successfully
1165Reconnect attempt timed out
1167Failed to re-publish/subscribe streams after reconnect
1178Auto-reconnect unavailable — no participants remain in ad-hoc room
4118Reconnect failed — room has been deleted
Next: manage your streams in detail Stream Management →