Skip to main content

Danamon Enhancement Mobile Development Guide - iOS

Attached data fields on the create call

Each EnvironmentCallAttribute declared for an environment becomes one input on the create call page, and its value is sent as an entry of DialCallParameters.attributes (the Attributes object of the dialex request).

Key Label Values
video_flow Video Flow ETB, NTB
video_channel Video Channel Registration, Reactivation, Activation, Saving (2.0), Credit Card
video_application Video Application DBP 2.0
video_language Video Language id, en

ECV Video Call iOS SDK — Queue Position and Estimated Wait Time

The SDK can report the caller's live queue position and estimated wait time (EWT) while the call is waiting in the queue. It is off by default; enable it with one configuration flag and listen for one event.

This document also covers the attached data fields added for the Danamon flavor (video_flow, video_channel, video_application, video_language).

What the SDK does

The SDK has three mutually exclusive ways of waiting for the queue. They are picked in a fixed order of precedence:

# Mode Enabled by Reports position / wait time
1 Queue socket useQueueSocket = true no
2 Queue info polling useQueueSocket = false and useQueueInfo = true yes
3 Legacy WaitForRoomInterval polling both flags false no

In queue info mode, VideoCall.start() sends the dial request and then polls /api/WaitForRoomQueueInterval instead of the legacy endpoint. On every poll it emits a QueueInfoUpdated event, and it still raises AgentConnected, CallEnded and Error exactly as before — nothing else in your call flow changes.

Position and wait time come from the Genesys URS statistics through the service.

Backend requirement: the service must expose GET /api/WaitForRoomQueueInterval?rtcId=…. Position and wait time are only filled in when Genesys URS returns queue statistics; otherwise they arrive as nil / -1 and the UI should fall back to a plain spinner.

Enable the feature

let config = ECVSdkConfiguration(
    name: "My Environment",
    serviceUrl: "https://your-ecv-service",
    useQueueSocket: false,          // the queue socket takes precedence, so turn it off
    useQueueInfo: true,             // queue position + estimated wait time
    queueInfoIntervalDelay: 3000    // poll interval in ms, optional (default 3000)
)

If your service exposes the endpoint under a different path, override it with queueInfoEndpoint (default /api/WaitForRoomQueueInterval).

useQueueInfo is ignored while useQueueSocket is true. If you leave the queue socket on, the call still works — you simply get no QueueInfoUpdated events.

In the demo both flags are set per environment in ECV175_iOS_DEMO/Build.swift (see the DANAMON flavor).

Per-call override

Both flags can also be set per call, on the VideoCall object. A nil override (the default) falls back to the SDK configuration; a non-nil value wins for that call only. Set them after creating the call and before start() — they are read when the queue wait begins:

let call = await sdk.createVideoCall()
call?.useQueueSocketOverride = false   // this call: no socket
call?.useQueueInfoOverride = true      // this call: queue info polling
await call?.start(dialCallParameters: params)

The same precedence applies to the effective values: socket > queue info > legacy. So to get queue info mode for a single call while the global configuration uses the socket, override both flags as above — overriding only useQueueInfoOverride is not enough, because the socket still wins.

Listen for the event

QueueInfoUpdated carries a QueueInfo in objData:

sdk.modules.onCallEvent.append { (videoCall, newEvent, lastEvent) in
    switch newEvent.type {
    case .QueueInfoUpdated:
        let info = newEvent.objData as? QueueInfo
        // update the UI with info?.displayPosition / info?.ewtMinutes

    case .AgentConnected:
        // navigate to the call screen

    case .Error, .Hangup, .CallEnded:
        // leave the queue screen

    default:
        break
    }
}

The last snapshot is also available at any time as videoCall.queueInfo, which is useful when the screen appears after the first event was already emitted:

queueInfo = call?.queueInfo

QueueInfo

Member Type Meaning
state String Raw wait state (wait, not found, agent_connected, …). The SDK already acts on it; you normally do not need it.
position Int? Raw position. nil if the service has no queue info, -1 if URS reported nothing for the interaction (see below).
ewtSeconds Double? Raw estimated wait time in seconds. Same nil / -1 rules.
displayPosition Int? position filtered for UI — nil unless it is a real position.
ewtMinutes Int? ewtSeconds rounded up to whole minutes, nil when unknown.

Prefer displayPosition and ewtMinutes in the UI.

What -1 means

position = -1 together with ewtSeconds = -1 is the service's "no queue data" answer. It is not a call state — read state (or the SDK's own events) for that. It is returned whenever URS has nothing to report for the interaction, which covers several different situations:

  • the interaction left the queue and is already routed and ringing at an agent — URS no longer tracks it as queued, but the call is perfectly alive
  • the interaction never reached URS, or the service could not resolve its connection id
  • the queue statistics mode configured on the service does not match the queue type, so the URS lookup fails
  • the call really did end on the Genesys side

Because these are indistinguishable from the client, never treat -1 as "the call ended" and never end the call because of it. Termination always arrives through the state field and the SDK's CallEnded / Hangup / Error events.

displayPosition and ewtMinutes both return nil for -1, so UI code that uses them needs no special handling.

Render it

Queue figures are not guaranteed to arrive, so the demo keeps its original waiting screen and only switches to the position layout once there is something real to show. From ECV175_iOS_DEMO/Views/Screens/QueueScreen.swift:

@State private var queueInfo: QueueInfo? = nil

// Only switch layouts once the backend actually reported something to show.
private var hasQueueData: Bool {
    queueInfo?.displayPosition != nil || queueInfo?.ewtMinutes != nil
}
if hasQueueData {
    Text("Please Wait...").font(.title2).bold()
    Text("You are in the queue")

    if let position = queueInfo?.displayPosition {
        Text("\(position)").font(.system(size: 64, weight: .bold))
    }

    if let minutes = queueInfo?.ewtMinutes {
        Text("Estimated wait time about \(minutes) minutes")
    }
}
else {
    ProgressView()
    Text("Please wait, your call will be connected shortly.")
}

Inside the position layout each figure is rendered only when it exists, since a queue can publish a position without an EWT or the other way round. Design the screen so it works with no figures at all: the first polls happen before the interaction reaches Genesys, some routing strategies never publish an EWT, and a caller who gets routed mid-wait goes back to -1 (see above).

Attached data on the create call page (Danamon)

Each EnvironmentCallAttribute declared for an environment becomes one input on the main screen, and its value is sent as an entry of DialCallParameters.attributes (the Attributes object of the DialEx request).

They are declared per environment in ECV175_iOS_DEMO/Build.swift:

EnvironmentCallAttribute(key: "video_flow", displayName: "Video Flow", values: ["ETB", "NTB"])

EnvironmentCallAttribute(key: "NikNumber", displayName: "Nik Number (encrypted)",
                         defaultValue: "…", type: "text")
Field Meaning
key Attribute key sent to the backend
displayName Label shown on the main screen
values Dropdown values for type = "select". The first entry is the preselected one.
defaultValue Prefilled value for type = "text"
type select (default) or text

The Danamon flavor declares these four attached data fields, shared by both of its environments through the danamonCallAttributes list:

Key Label Values
video_flow Video Flow ETB, NTB
video_channel Video Channel Registration, Reactivation, Activation, Saving (2.0), Credit Card
video_application Video Application DBP 2.0
video_language Video Language id, en

video_flow and video_channel are independent dropdowns, so make sure the selected combination is valid for your routing: Registration / Reactivation / Activation belong to ETB, while Saving (2.0) and Credit Card belong to NTB.

Backward compatibility. Older clients do not send these attributes at all. The service fills in its own defaults for them (video_flow = NTB, video_channel = Saving (1.0), video_application = DBP 1.0, video_language = id). The 1.0 values exist only as those service side defaults, which is why they are not offered on the create call page — a current client always sends the values explicitly.

6. Troubleshooting

Symptom Cause Fix
No QueueInfoUpdated events at all Feature not enabled, or the queue socket is still on Check useQueueInfo = true and useQueueSocket = false — in the configuration, or as per-call overrides set before start()
Events arrive with state=wait but position=-1 ewt=-1.0 for the whole call URS reported nothing for this interaction Check the service log for the urs/call/… request, and whether the queue statistics mode configured on the service matches the queue type. The screen stays on the plain waiting layout, which is the intended fallback.
position / ewtSeconds flip to -1 mid-wait The interaction left the queue — usually routed and now ringing at an agent Expected, and not a call end. Keep waiting for AgentConnected.
Queue never progresses rtcId mismatch or the dial failed Compare the rtcId in the app log with the DialEx request on the service

ECV Video Call iOS SDK — Hold and Resume

When the agent puts the call on hold from the agent desktop, the SDK reports it to your application so you can cover the screen with a "please wait" overlay, and reports the resume so you can take it away again.

No configuration is needed — the events are always emitted.

How it works

Hold is driven entirely by the agent side. The agent desktop sends a control message over the WebRTC data channel, and the SDK turns it into an event:

Agent action Data channel message SDK event
Puts the call on hold HOLD|… .StartHold
Takes the call back PICKUP|… .EndHold

There is no client-side API to place a call on hold — the caller cannot hold the agent. Your job is to react to the two events.

Automatic media side effects

On top of the events, the SDK can mute the caller's own media for the duration of the hold. This is controlled by one configuration flag:

let config = ECVSdkConfiguration(
    name: "My Environment",
    serviceUrl: "https://your-ecv-service",
    autoHandleHoldSideEffects: true   // default
)

With the flag on (the default), receiving HOLD disables the local audio and video tracks, and PICKUP enables them again — the agent neither hears nor sees the caller while the call is on hold. With the flag off, the SDK only emits the events and leaves the local media untouched; mute it yourself if your flow requires it.

The events are emitted either way.

Implement the overlay

Three pieces: a flag, two event branches, and a view rendered on top.

Keep a flag

In the call screen (the demo's is ECV175_iOS_DEMO/Views/Screens/CallScreen.swift):

@State private var isOnHold = false

Drive it from the events

Extend the onCallEvent handler the call screen already registers:

appState.sdk?.modules.onCallEvent.append { (videoCall, newEvent, lastEvent) in
    if self.videoCall?.rtcId != videoCall.rtcId {
        return // only react to the current call's events
    }

    switch newEvent.type {
    case .StartHold:
        isOnHold = true
    case .EndHold:
        isOnHold = false
    // … other events …
    default:
        break
    }
}

Render the overlay last

Inside the ZStack that holds the video views and the call controls, render the overlay after everything else so it paints on top:

ZStack {
    // remote video, local video preview, call controls …

    // --- OVERLAYS ---
    // Rendered last to appear on top of the UI elements above.
    if isOnHold {
        HoldOverlayView()
    }
}
struct HoldOverlayView: View {
    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()
            Image(systemName: "hourglass")
                .font(.system(size: 64))
                .foregroundColor(.white)
        }
        .zIndex(4) // above the controls
    }
}

Things to get right

Make it opaque. The overlay is a solid black view, not a translucent scrim. The agent side stops sending video for the duration of the hold, so what sits behind the overlay is a dead surface — a frozen last frame or a blank view, depending on the device. An opaque overlay replaces that with a deliberate "please wait" state; a translucent scrim would just show the caller a dimmed frozen picture of the agent.

Render it above the controls. zIndex(4) puts it over the call control row, so the caller cannot hang up while on hold. If your flow needs an escape hatch, either lower the overlay's zIndex below the controls or place a hangup button inside the overlay itself.

The state is per call. isOnHold is @State on the call screen, so a call that ends while on hold simply disposes it when the screen is dismissed. There is nothing to reset.

Don't fight the automatic mute. With autoHandleHoldSideEffects on, the local tracks are already disabled during the hold — do not also toggle them from your event handler, or the resume can leave the two out of sync. Either let the SDK do it (default) or turn the flag off and own the muting entirely.

Troubleshooting

Symptom Cause Fix
No .StartHold / .EndHold events The data channel is not up, or the agent desktop does not send hold messages Check for TextChannelConnected in the log; verify hold works from the agent desktop for other clients
Overlay stays after the agent resumes .EndHold handled by a handler that was removed Keep the onCallEvent handler registered for as long as the call screen is shown
Hold does not mute/unmute the caller automatically autoHandleHoldSideEffects is off, or the events never arrive Verify the flag in the configuration and check the log for HOLD received / PICKUP received