Stream Management
Stream management is the heart of any EnableX video session. There are two kinds of streams you work with: a local stream — the audio and video your device captures and publishes to the room — and remote streams — the audio and video sent by other participants that you subscribe to and render on screen. This page walks through the full lifecycle of both: creating and publishing your own stream, subscribing to others, controlling media (mute, camera switching), managing active talkers, and tuning receive quality.
Before you can publish anything to the room, you need a local stream — an
EnxStream object that captures audio and/or video from the device's microphone and
camera. You create this by calling getLocalStream() on your EnxRoom
instance and passing a dictionary that describes which tracks to include and how the stream
should behave at join time.
The typical place to call this is inside your room:didConnect: delegate, immediately
after the connection is confirmed and before you call publish(). The SDK validates
the options, requests any required device permissions, and returns the stream object — which you
then pass to publish().
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(EnxStream *)getlocalStream:(NSDictionary *)publishStreamInfo |
| Returns | EnxStream — the local stream object, ready to publish |
publishStreamInfo Options
The publishStreamInfo dictionary controls which media tracks are included and the
initial mute state. You can also attach custom metadata to the stream using the
attributes object — this metadata is visible to all other participants.
| Key | Type | Description |
|---|---|---|
audio |
Boolean | Include the microphone audio track. |
video |
Boolean | Include the camera video track. |
data |
Boolean | Include a data channel for in-stream messaging. |
audioMuted |
Boolean | Join the room with audio muted from the start. |
videoMuted |
Boolean | Join the room with video muted from the start. |
attributes |
NSDictionary | Custom key-value metadata attached to the stream (visible to all participants). |
func room(_ room: EnxRoom?, didConnect roomMetadata: [String: Any]?) {
let streamInfo: [String: Any] = [
"audio" : true,
"video" : true,
"data" : true,
"audioMuted" : false,
"videoMuted" : false,
"attributes" : ["custom1": "value"]
]
let localStream = room?.getlocalStream(streamInfo)
room?.publish(localStream)
}
Error Codes
| Code | Meaning |
|---|---|
| 5015 | No media tracks — both audio and video are set to false. |
| 5016 | Invalid or malformed attributes object. |
| 5017 | Camera and microphone access denied by the user. |
| 5018 | Camera access failed; stream will be audio-only. |
| 5019 | Microphone access denied. |
| 5084 | Invalid video layer specified — maximum allowed is 3. |
| 5088 | Invalid frame rate — maximum allowed is 30 fps. |
Once you have a local stream, you publish it to the room so that other participants can see and
hear you. Calling publish() sends the stream to the EnableX media server, which
then distributes it to all connected subscribers. Until you publish, your audio and video exist
only on your device — no one else in the room receives them.
Publishing generates two delegate callbacks: one delivered privately to you as an acknowledgement, and one broadcast to everyone in the room (including yourself) announcing that a new stream is available.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)publish:(EnxStream *)stream |
Delegate Methods
-
room:didPublishStream:(EnxStream *)stream— ACK sent only to the publisher. Confirms your stream was accepted by the server. This is a good place to attach your local player view. -
room:didAddedStream:(EnxStream *)stream— Broadcast to all participants, including yourself. Every participant (including the publisher) receives this event each time any stream becomes available in the room. Other participants should callsubscribe()on this stream in this callback.
// Publish the local stream after room connects
func room(_ room: EnxRoom?, didConnect roomMetadata: [String: Any]?) {
let streamInfo: [String: Any] = [
"audio": true,
"video": true,
"data" : true
]
let localStream = room?.getlocalStream(streamInfo)
room?.publish(localStream)
}
// ACK — only you receive this; attach your local preview here
func room(_ room: EnxRoom?, didPublishStream stream: EnxStream?) {
let playerView = EnxPlayerView(frame: localView.bounds)
stream?.attachRenderer(playerView)
localView.addSubview(playerView!)
}
// Broadcast — all participants receive this for every stream
func room(_ room: EnxRoom?, didAddedStream stream: EnxStream?) {
// If this is not your own stream, subscribe to it
if stream?.streamId != room?.localStream?.streamId {
room?.subscribe(stream)
}
}
Error Codes
| Code | Meaning |
|---|---|
| 5013 | Room is not connected. |
| 5015 | Stream has no media tracks. |
| 5016 | Invalid stream attributes. |
| 5022 | No floor access — participant cannot publish in lecture mode without moderator grant. |
| 5023 | Stream is already published. |
| 5024 | Publish is already in progress. |
| 5025 | Cannot call publish on a remote stream. |
| 1170 | General publish failure. |
Unpublishing removes your stream from the room without disconnecting you from the session. Other participants stop receiving your audio and video, and the room notifies everyone that the stream has been removed. You remain connected and can publish again later if needed.
Call unpublish on the EnxStream object (not on the room). The room
instance does not need to be referenced directly for this operation.
| Detail | Value |
|---|---|
| Class | EnxStream |
| Method | -(void)unpublish |
| Delegate | room:didRemovedStream: — broadcast to all participants |
// Stop publishing your local stream
localStream.unpublish()
// All participants (including you) receive this when any stream is removed
func room(_ room: EnxRoom?, didRemovedStream stream: EnxStream?) {
// Remove the corresponding player view from your UI
print("Stream removed: \(stream?.streamId ?? "")")
}
Error Codes
| Code | Meaning |
|---|---|
| 5029 | Unpublish already in progress. |
| 5030 | Stream has not been published yet. |
During a live session you may need to change which camera is active, switch to a different
microphone input, or change audio output routing. The SDK provides dedicated methods on both
EnxStream and EnxRoom for each of these operations.
Switch Camera Preview
Use switchCameraPreview to toggle the camera view between front-facing and
rear-facing without interrupting the published stream. This is useful when you want the user to
preview the rear camera before switching the live feed.
| Detail | Value |
|---|---|
| Class | EnxStream |
| Method | -(void)switchCameraPreview |
// Switch the local camera preview (does not affect published stream)
localStream.switchCameraPreview()
Switch Front / Rear Camera
switchCamera flips the active camera between front-facing and rear-facing and
immediately updates the published stream. All subscribers in the room see the new camera angle
without re-subscribing.
| Detail | Value |
|---|---|
| Class | EnxStream |
| Method | -(NSException *)switchCamera |
let exception = localStream.switchCamera()
if let exception = exception {
print("Camera switch failed: \(exception.reason ?? "")")
}
| Error Code | Meaning |
|---|---|
| 5021 | Cannot switch camera — session is in audio-only mode. |
| 5097 | Cannot switch camera — room is in chat-only mode. |
Switch Microphone / Audio Output
Use switchMediaDevice on the room to route audio through a different input or
output device — for example switching from the speakerphone to a connected Bluetooth headset, or
from the earpiece to the speaker. Pass the device name string as returned by
getDevices.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)switchMediaDevice:(NSString *)mediaName |
| Delegate | didNotifyDeviceUpdate:(NSString *)updates — fires with the new device name |
// Switch audio output to the speakerphone
room.switchMediaDevice("Speaker")
// Delegate fires after the switch completes
func didNotifyDeviceUpdate(_ updates: String?) {
print("Audio device changed to: \(updates ?? "")")
// Refresh your device selection UI to highlight the new active device
}
Participants can mute their own audio or video at any time without unpublishing their stream. When you mute yourself, the track is silenced or blacked out on all subscribers' screens. Unmuting restores the track. The SDK fires delegate events both to you (as an acknowledgement) and to the rest of the room (as a notification), so every participant's UI can reflect the change.
Mute / Unmute Audio
Call muteSelfAudio: with YES to mute or NO to unmute your
microphone. Pass the boolean based on the desired end-state, not a toggle.
| Detail | Value |
|---|---|
| Class | EnxStream |
| Method | -(void)muteSelfAudio:(BOOL)isMuted |
Delegate methods:
didAudioEvent:(NSDictionary *)data— ACK to the local participant.stream:didRemoteStreamAudioMute:(NSArray *)data— broadcast when muted.stream:didRemoteStreamAudioUnMute:(NSArray *)data— broadcast when unmuted.
// Mute your microphone
localStream.muteSelfAudio(true)
// Unmute your microphone
localStream.muteSelfAudio(false)
// ACK for the local user
func didAudioEvent(_ data: [String: Any]?) {
print("Audio mute state changed: \(data ?? [:])")
}
// Room-wide notifications
func stream(_ stream: EnxStream?, didRemoteStreamAudioMute data: [Any]?) {
// Update the mute indicator for this participant in your UI
}
func stream(_ stream: EnxStream?, didRemoteStreamAudioUnMute data: [Any]?) {
// Remove the mute indicator for this participant in your UI
}
Mute / Unmute Video
muteSelfVideo: follows the same pattern as audio muting. Subscribers see a
blacked-out frame while video is muted. The stream remains subscribed and active; only the video
track is suppressed.
| Detail | Value |
|---|---|
| Class | EnxStream |
| Method | -(void)muteSelfVideo:(BOOL)isMuted |
Delegate methods:
didVideoEvent:(NSDictionary *)data— ACK to the local participant.stream:didRemoteStreamVideoMute:(NSArray *)data— broadcast when muted.stream:didRemoteStreamVideoUnMute:(NSArray *)data— broadcast when unmuted.
// Mute your camera video
localStream.muteSelfVideo(true)
// Unmute your camera video
localStream.muteSelfVideo(false)
func didVideoEvent(_ data: [String: Any]?) {
print("Video mute state changed: \(data ?? [:])")
}
func stream(_ stream: EnxStream?, didRemoteStreamVideoMute data: [Any]?) {
// Show a camera-off placeholder in the participant tile
}
func stream(_ stream: EnxStream?, didRemoteStreamVideoUnMute data: [Any]?) {
// Restore the video frame in the participant tile
}
When a participant publishes a stream, the room notifies all connected clients via
room:didAddedStream:. At this point the stream exists on the server, but you are
not yet receiving media for it. To start receiving audio and video from that participant, you
must explicitly call subscribe() on the stream object. This is a deliberate design
— it lets you choose which streams to receive, which is important for bandwidth management in
larger rooms.
After subscribing, the SDK fires room:didSubscribeStream: with the fully
initialised stream. This is where you create an EnxPlayerView, attach the stream
to it, and add it to your view hierarchy.
subscribe() individually for each remote stream. There is no
"subscribe all" shortcut. Screen share streams arrive with StreamID 101 and canvas
streams with StreamID 102 — handle these separately if your app supports them.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)subscribe:(EnxStream *)stream |
| Delegate | room:didSubscribeStream: — fires when subscription is confirmed |
// Subscribe when the room notifies you of a new stream
func room(_ room: EnxRoom?, didAddedStream stream: EnxStream?) {
room?.subscribe(stream)
}
// Once subscribed, attach a player view and render the stream
func room(_ room: EnxRoom?, didSubscribeStream stream: EnxStream?) {
let frame = CGRect(x: 0, y: 0, width: 160, height: 120)
let playerView = EnxPlayerView(frame: frame)
stream?.attachRenderer(playerView)
view.addSubview(playerView!)
}
Error Codes
| Code | Meaning |
|---|---|
| 5014 | Room is not in a connected state. |
| 5026 | Already subscribed to this stream. |
| 5027 | Subscription is already in progress. |
| 5028 | Cannot call subscribe on a local stream. |
In a room with many participants, it is not practical (or efficient) to render every stream simultaneously. EnableX addresses this with the Active Talker List — the SDK continuously monitors audio levels and maintains a ranked list of up to 12 participants who are currently speaking. Your app receives updates to this list automatically and should refresh its layout in response.
The way you receive and display active talkers depends on the activeviews setting
you passed when connecting:
-
activeviews: "list"— The SDK gives you a stream list and you control the layout entirely. Implementroom:didActiveTalkerList:to receive updates and rebuild your video grid. -
activeviews: "view"— The SDK builds and manages aUIViewgrid internally. You receive it once viaroom:didActiveTalkerView:and add it to your view hierarchy. The SDK handles all subsequent updates.
Active Talker Delegates
-
room:didActiveTalkerList:(NSArray *)streams— Fires whenever the ranked list changes (only whenactiveviews: "list"). Rebuild your video grid using the streams in this array. -
room:didActiveTalkerView:(UIView *)view— Fires once (only whenactiveviews: "view"). Add this view to your layout; the SDK manages it from there. -
room:didAvailable:(NSDictionary *)data— Fires whenever the count of available (connected) participants changes.
// activeviews: "list" — you manage the layout
func room(_ room: EnxRoom?, didActiveTalkerList streams: [Any]?) {
// Clear current video tiles and rebuild from the updated stream list
rebuildVideoGrid(streams)
}
// activeviews: "view" — add the SDK-managed view once
func room(_ room: EnxRoom?, didActiveTalkerView view: UIView?) {
view?.frame = videoContainerView.bounds
videoContainerView.addSubview(view!)
}
// Available participant count changed
func room(_ room: EnxRoom?, didAvailable data: [String: Any]?) {
print("Participants available: \(data ?? [:])")
}
Get Maximum Talker Count
getMaxTalkers queries the server for the maximum number of active video streams the
room configuration allows. This is a room-level setting and does not change during a session.
Use this value to cap your UI layout or inform the user of the room's maximum concurrent video
feeds.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)getMaxTalkers |
| Delegate | room:didGetMaxTalkers:(NSDictionary *)data |
room.getMaxTalkers()
func room(_ room: EnxRoom?, didGetMaxTalkers data: [String: Any]?) {
// data = { "result": 0, "maxTalkers": 4 }
let maxTalkers = data?["maxTalkers"] as? Int ?? 0
print("Room max talkers: \(maxTalkers)")
}
Get Current Talker Count
getTalkerCount queries how many active talker slots are currently configured for
your session. This is the number you (or the room settings) have chosen to display, which may be
less than the room maximum.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)getTalkerCount |
| Delegate | room:didGetTalkerCount:(NSDictionary *)data |
room.getTalkerCount()
func room(_ room: EnxRoom?, didGetTalkerCount data: [String: Any]?) {
// data = { "result": 0, "numTalkers": 4 }
let numTalkers = data?["numTalkers"] as? Int ?? 0
print("Current talker count: \(numTalkers)")
}
Set Talker Count
Use setTalkerCount: to change how many simultaneous video streams the active talker
list includes. The value can be between 0 and 12. Setting it to 0 is a special case — it means
the SDK will maintain 3 audio-only streams instead of video.
This is particularly useful in response to bandwidth alerts: when available bandwidth drops, you can reduce the talker count to lower the total video bitrate in the room.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)setTalkerCount:(NSInteger)numTalkers |
| Valid Range | 0–12 (0 = 3 audio-only streams) |
| Delegate | room:didSetTalkerCount:(NSDictionary *)data |
| Error | 5055 — invalid talker count value |
// Reduce active video streams to 4 (e.g. in response to a bandwidth alert)
room.setTalkerCount(4)
func room(_ room: EnxRoom?, didSetTalkerCount data: [String: Any]?) {
print("Talker count updated: \(data ?? [:])")
}
When activeviews: "view" is active, the SDK manages the video grid layout
internally. These utilities let you interact with that managed layout — highlight specific
participants, change background colours, force a refresh, or switch between layout modes. All
methods in this section are available from iOS SDK 2.1.3 unless noted otherwise.
activeviews: "view" is set during
connect(). They have no effect in list mode, where you control the layout yourself.
getPlayer
Retrieve the EnxPlayerView for a specific participant by their
clientID. Use this when you need to apply custom styling or overlay to a single
participant's tile without affecting others.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(EnxPlayerView *)getPlayer:(NSString *)clientID |
| Returns | EnxPlayerView for the given client, or nil if not found |
let playerView = room.getPlayer("CLIENT_ID")
if let playerView = playerView {
// Apply custom styling, border, or overlay to this participant's tile
}
highlightBorderForClient
Highlight the border of one or more participant tiles — useful for indicating who is speaking, who has raised their hand, or who has been selected as the spotlight speaker. Pass an array of client ID strings.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)highlightBorderForClient:(NSArray *)clientIDs |
// Highlight the speaking indicator for specific participants
room.highlightBorderForClient(["CLIENT_ID_1", "CLIENT_ID_2"])
changeBgColorForClients
Change the background colour of specific participant tiles — for example to visually distinguish the moderator, indicate a raised hand, or apply role-based colour coding.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)changeBgColorForClients:(NSArray *)clientIDs withColor:(UIColor *)color |
// Highlight the moderator's tile with a distinct background colour
room.changeBgColorForClients(
["MODERATOR_CLIENT_ID"],
withColor: UIColor(red: 0.2, green: 0.6, blue: 1.0, alpha: 0.4)
)
forceUpdateATList
Force the SDK to reload and re-render the active talker view immediately. Call this after making programmatic changes to the layout or after returning from a different view controller to ensure the grid is up to date.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)forceUpdateATList |
// Force a refresh of the active talker grid
room.forceUpdateATList()
switchATView
Switch the active talker grid between two layout modes: "leader" — which shows one
prominent speaker tile with smaller tiles for others — and "gallery" — which shows
all active participants at equal size. Available from iOS SDK 2.1.2.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)switchATView:(NSString *)viewString |
| Valid Values | "leader" or "gallery" |
// Switch to leader layout (one prominent + smaller tiles)
room.switchATView("leader")
// Switch to gallery layout (equal-size tiles)
room.switchATView("gallery")
By default, EnableX automatically selects the best video quality for incoming streams based on available bandwidth. However, you can override this and pin the receive quality to a specific level — useful when you know the device or network conditions warrant a fixed setting, or when building a user-facing quality selector in your settings UI.
Call setReceiveVideoQuality: on the room object with one of the four quality
levels. The change applies to all incoming remote streams. The SDK confirms the change via
delegate.
| Detail | Value |
|---|---|
| Class | EnxRoom |
| Method | -(void)setReceiveVideoQuality:(NSString *)videoQuality |
| Delegate | room:didSetVideoQuality:(NSDictionary *)data |
| Error | 5057 — the requested quality level is already active |
Quality Levels
| Value | Description |
|---|---|
Auto |
SDK selects quality automatically based on bandwidth conditions (default). |
HD |
High-definition video. Requires strong network conditions. |
SD |
Standard-definition video. Balanced quality and bandwidth use. |
LD |
Low-definition video. Minimal bandwidth consumption. |
// Pin incoming video quality to standard-definition
room.setReceiveVideoQuality("SD")
func room(_ room: EnxRoom?, didSetVideoQuality data: [String: Any]?) {
print("Receive video quality updated: \(data ?? [:])")
}
LD when responding to a bandwidth alert
(room:didRoomBandwidthAlert:), and restore it to Auto once bandwidth
recovers. This keeps call quality as high as conditions allow without manually tracking
thresholds.