Connecting to a Session

This guide walks through the complete flow for joining an EnableX video session from an Android app — from querying available audio devices through publishing your stream and finally disconnecting cleanly. Follow these steps in order; each step depends on the previous one succeeding.

Connection Flow

Joining an EnableX session from Android involves these steps in sequence:

  1. Query audio devices — get the list of available microphones and speakers.
  2. Create the room object — instantiate EnxRoom with your observer implementations.
  3. Initialize the room — call room.init(context) to prepare the Android media stack.
  4. Register observers — set up all callbacks before connecting.
  5. Connect to the room — call room.connect(token, roomInfo, advancedOptions).
  6. Initialize and publish your stream — inside onRoomConnected, get the local stream and publish it.
  7. Subscribe to remote streams — inside onStreamAdded, subscribe and attach to your UI.
  8. Disconnect — call room.disconnect() when the session ends.

If you want a one-call shortcut that wraps steps 2–6, use EnxRtc.joinRoom().

Step 1 — Query Audio Devices

Get Available Audio Devices

EnxRoom.getDevices() returns a list of audio output devices currently connected to the Android device. Use this to let the user choose their preferred audio route — earpiece, speaker, wired headset, or Bluetooth — before or during a session.

val devices: List<String> = room.devices
// Returns a list such as: ["SPEAKER_PHONE", "WIRED_HEADSET", "EARPIECE", "BLUETOOTH", "NONE"]

To get the device currently in use:

val currentDevice: String = room.selectedDevice
// e.g. "SPEAKER_PHONE"

Device Change Callbacks

If a headset is plugged in or unplugged, or a Bluetooth device connects/disconnects, the SDK notifies your observer:

override fun onDeviceAdded(deviceName: String?) {
    // A new audio device was connected (e.g. Bluetooth headset paired)
    Log.d("ENX", "Device added: $deviceName")
}

override fun onDeviceRemoved(deviceName: String?) {
    // An audio device was disconnected
    Log.d("ENX", "Device removed: $deviceName")
}

override fun onNotifyDeviceUpdate(deviceName: String?) {
    // The active audio device changed
    Log.d("ENX", "Now using: $deviceName")
}
Steps 2–4 — Create Room, Init, Register Observers

Create and Initialise the Room Object

The EnxRoom constructor takes two observer instances — one for room-level callbacks (EnxRoomObserver) and one for stream-level callbacks (EnxStreamObserver). Your Activity or Fragment typically implements both interfaces.

After instantiating, call room.init(context) to prepare the Android media stack. This step is mandatory before connect().

class VideoActivity : AppCompatActivity(), EnxRoomObserver, EnxStreamObserver {

    private var room: EnxRoom? = null
    private var localStream: EnxStream? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Step 2: Create the room object
        room = EnxRoom(
            this,   // EnxRoomObserver
            this    // EnxStreamObserver
        )

        // Step 3: Init with Android context
        room?.init(this)

        // Step 4: Register feature observers before connecting
        room?.setRecordingObserver(this)
        room?.setTalkerObserver(this)

        // Step 5: Connect (see below)
        connectToRoom(YOUR_JWT_TOKEN)
    }
}
Register all observers before calling connect(). Callbacks like onRoomConnected and onStreamAdded fire immediately on connection. If observers are registered after connecting, those initial callbacks will be missed.
Step 5 — Connect to the Room

room.connect()

EnxRoom.connect() authenticates with the JWT token and establishes the signalling channel to the EnableX server. It accepts optional reconnection settings and advanced options.

roomInfo Parameters

ParameterTypeDescription
allow_reconnectBooleanEnable auto-reconnect on network drop. Default: true.
number_of_attemptsNumberMax reconnect attempts. Default: 3. Minimum: 1.
timeout_intervalNumberMilliseconds to wait between reconnect attempts.
audio_onlyBooleanSet true to join as audio-only (no video).

advancedOptions Parameters

Option IDTypeDescription
battery_updatesBooleanEnable battery level update notifications.
notify-video-resolution-changeBooleanEnable video resolution change notifications.
fun connectToRoom(token: String) {
    val roomInfo = JSONObject().apply {
        put("allow_reconnect", true)
        put("number_of_attempts", 3)
        put("timeout_interval", 10000)
        put("audio_only", false)
    }

    val advancedOptions = JSONArray().apply {
        put(JSONObject().apply {
            put("id", "notify-video-resolution-change")
            put("enable", true)
        })
    }

    room?.connect(token, roomInfo, advancedOptions)
}

// ── Callbacks ───────────────────────────────────────────────────────────────

override fun onRoomConnected(room: EnxRoom?, roomMetaData: JSONObject?) {
    // Successfully connected — proceed to init and publish local stream
    initAndPublishStream()
}

override fun onRoomError(errorData: JSONObject?) {
    // Connection failed
    Log.e("ENX", "Room error: ${errorData?.toString()}")
}

override fun onUserConnected(userData: JSONObject?) {
    // A new participant joined the room — update your participant list UI
    Log.d("ENX", "User joined: ${userData?.optString("name")}")
}

override fun onRoomAwaited(data: JSONObject?) {
    // You are waiting for moderator approval (knock room) or
    // waiting for moderator to arrive (wait-for-moderator room).
    // data: { "event_type": "knock" } or { "event_type": "wait_for_moderator" }
}

override fun onUserAwaited(userData: JSONObject?) {
    // Moderator only: a participant is waiting at the door
    // Show approve/deny UI
}

Network Disconnection & Reconnection Callbacks

If the network drops during a session, the SDK fires these callbacks. With auto-reconnect enabled, it attempts to recover silently:

override fun onConnectionLost(json: JSONObject?) {
    // Network connection lost — show "Reconnecting..." UI
}

override fun onConnectionInterrupted(json: JSONObject?) {
    // Connection interrupted (e.g. switched from WiFi to 4G)
}

override fun onUserReconnectSuccess(room: EnxRoom?, roomMetaData: JSONObject?) {
    // Auto-reconnect succeeded — restore UI
}

override fun onReconnect(message: String?) {
    // Reconnect attempt in progress
}
Error CodeDescription
5073Connection switched to alternate network
5074Network disconnected; reconnection timed out
5086Method called while room is not connected
5087Reconnection failed
Step 6 — Initialize and Publish Your Stream

Initialize a Local Stream

EnxRoom.getLocalStream() creates a local media stream with the audio, video, and data tracks you configure. Call this inside onRoomConnected, then immediately publish it.

publishStreamInfo Options

OptionTypeDescription
audioBooleanInclude audio track. Default: true.
videoBooleanInclude video track. Default: true.
dataBooleanInclude data channel (required for chat/signalling).
audioMutedBooleanJoin with microphone muted.
videoMutedBooleanJoin with camera off.
attributesJSONObjectCustom key/value metadata attached to this stream.
maxVideoLayersNumber1=HD only, 2=HD+SD, 3=HD+SD+LD. More layers = better adaptive quality for receivers.

Publish the Stream

fun initAndPublishStream() {
    val streamInfo = JSONObject().apply {
        put("audio", true)
        put("video", true)
        put("data", true)
        put("audioMuted", false)
        put("videoMuted", false)
        put("maxVideoLayers", 3) // publish HD + SD + LD layers
        put("attributes", JSONObject().apply { put("name", "Alice") })
    }

    // Get the local stream
    localStream = room?.getLocalStream(streamInfo)

    // Attach local stream to your preview SurfaceView/TextureView
    localStream?.attachRenderer(localPreviewView)

    // Publish into the room
    room?.publish(localStream)
}

// Callback: your stream is now live in the room
override fun onPublishedStream(stream: EnxStream?) {
    Log.d("ENX", "Stream published, ID: ${stream?.id}")
}

// Callback: a stream was published — everyone receives this
override fun onStreamAdded(stream: EnxStream?) {
    // Subscribe to remote streams (Step 7)
    if (stream?.isLocal == false) {
        room?.subscribe(stream)
    }
}
Error CodeDescription
5013Failed to publish stream
5015Cannot publish without audio, video, or data track
5022Cannot publish — floor access not yet granted (Lecture mode)
5023Stream is already published
5017Failed to access camera and microphone
Step 7 — Subscribe to Remote Streams

Subscribe and Render Remote Streams

When a remote stream becomes available, onStreamAdded fires. Subscribe to it with room.subscribe() and then attach it to an EnxPlayerView in your layout once onSubscribedStream confirms the subscription.

override fun onStreamAdded(stream: EnxStream?) {
    if (stream?.isLocal == false) {
        room?.subscribe(stream)
    }
}

override fun onSubscribedStream(stream: EnxStream?) {
    // Successfully subscribed — attach the stream to a player view
    runOnUiThread {
        val playerView = stream?.mEnxPlayerView
        remoteVideoContainer.addView(playerView)
    }
}

Active Talker Updates

As participants speak, the server updates the active talker list and delivers it through either onActiveTalkerList (when using custom view mode) or onActiveTalkerView (predefined RecyclerView mode). See Stream Management — Active Talkers for full details.

Alternative — Quick Join with joinRoom()

EnxRtc.joinRoom()

EnxRtc.joinRoom() is a convenience method that collapses steps 2–6 into a single call. It creates the room, connects, initializes a local stream, and publishes it automatically. Use it when you want to get into a session as fast as possible with minimal boilerplate.

val enxRtc = EnxRtc(
    this,   // Context
    this,   // EnxRoomObserver
    this    // EnxStreamObserver
)

val streamInfo = JSONObject().apply {
    put("audio", true)
    put("video", true)
    put("data", true)
}

val roomInfo = JSONObject().apply {
    put("allow_reconnect", true)
    put("number_of_attempts", 3)
    put("timeout_interval", 10000)
    put("activeviews", "view")  // "view" = predefined RecyclerView, "list" = manual list
}

val localStream: EnxStream? = enxRtc.joinRoom(token, streamInfo, roomInfo, null)

// onRoomConnected and onStreamAdded fire as before
override fun onRoomConnected(room: EnxRoom?, roomMetaData: JSONObject?) {
    Log.d("ENX", "Joined room: ${roomMetaData?.optString("roomId")}")
}
activeviews setting matters. Set activeviews: "view" for the SDK to provide a ready-made RecyclerView of participant videos. Set activeviews: "list" to receive individual streams and build your own layout. You cannot change this after joining.
Step 8 — Disconnect

room.disconnect()

EnxRoom.disconnect() closes the session, releases media tracks, and tears down the signalling connection. Call this when the user leaves the session — in onBackPressed(), a "Leave" button handler, or onDestroy().

room?.disconnect()

// Callback: you are now disconnected
override fun onRoomDisConnected(json: JSONObject?) {
    // Clean up UI, navigate back to lobby
    runOnUiThread { finish() }
}

// Callback: delivered to everyone else in the room
override fun onUserDisConnected(userData: JSONObject?) {
    // Remove the departed user from your participant list
    Log.d("ENX", "User left: ${userData?.optString("clientId")}")
}
Error CodeDescription
5031Repeated disconnect() call while previous disconnection is in progress
5032disconnect() called after already disconnected