Skip to main content

ECV Video Call iOS SDK [EN]

image-20240102-113213.pngimage-20240102-113321.png

ECV Video Call iOS SDK

Table of Contents

Introduction

Welcome to the ECV Video Call iOS SDK! This SDK enables you to seamlessly integrate real-time video call functionality into your iOS applications. It handles the complexities of signaling, WebRTC peer connections, media streams, and call state management, allowing you to focus on your application's user interface and business logic.

This repository contains:

  1. The SDK: Provided as a pre-compiled iOS Framework (.framework file). You will add this as a dependency to your project.

  2. Demo Application: Full source code for a demonstration app (ECV175_iOS_DEMO) showcasing how to use the SDK features. This demo is intended as a reference and example, not production-ready UI code.

Key Features

  • Establishes connection to ECV backend services (v1.5 and v1.75+)

  • Handles WebRTC setup and peer-to-peer connection

  • Manages local and remote video/audio streams

  • Provides controls for muting/unmuting audio and video

  • Supports camera switching

  • Manages call lifecycle events (dialing, queuing, connected, hold, ended)

  • Configurable environments and call parameters

Getting Started

Prerequisites

  • Xcode (Latest Stable Version Recommended)

  • Basic knowledge of iOS development (Swift)

  • Minimum iOS Version: 13.0

  • Target iOS Version: 17.0

1. Add Required Frameworks

Your application needs to include the ECV SDK framework and its required dependencies: WebRTC. You will receive these as .framework files.

  1. Required Framework Files:

    • ECV SDK: (e.g., ECV175_iOS_SDK.xcframework)

    • WebRTC: WebRTC.xcframework

  2. Add the frameworks to your project:

    • Drag and drop the framework files into your Xcode project

    • Make sure "Copy items if needed" is checked

    • Add to your target's "Frameworks, Libraries, and Embedded Content" section

    • Set "Embed & Sign" for each framework

  3. In your project's Build Settings:

    • Set "Enable Bitcode" to No

    • Add the frameworks to "Framework Search Paths"

    • Ensure "Build Active Architecture Only" is set appropriately for your needs

2. Explore the Demo Application

The included demo application (ECV175_iOS_DEMO) provides a practical example of SDK integration.

  1. Open the ECV175_iOS_DEMO.xcodeproj in Xcode

  2. Build and run the demo app on a simulator or physical device

  3. Examine the source code, particularly the files in the UI directory and the AppState.swift to understand how SDK components are initialized and used

3. Basic SDK Initialization (Conceptual)

// Somewhere in your AppDelegate or a central management object
import ECV175_iOS_SDK

// 1. Define the SDK Configuration (See Configuration for details)
let sdkConfig = ECVSdkConfiguration(
    name: "Environment_Name" // Give it any name that refers to your app or backend
    serviceUrl: "YOUR_SERVICE_URL", // Replace with your actual service URL
    useQueueSocket: true // or false, Make sure this matches your backend version (true for 1.75+, false for 1.5)
    // Optionally override webrtcUrl, signalingUrl, etc.
)

// 2. Initialize the SDK Instance
// It's recommended to have a single instance managed centrally
let ecvSdk = ECVSdk(config: sdkConfig)

// Store this instance for access throughout your app (e.g., in AppDelegate, DI, Singleton)
// Example: MyApp.sdkInstance = ecvSdk

Core Concepts

  • ECVSdk: The main entry point for interacting with the SDK. Used to create calls and access global settings/modules.

  • ECVSdkConfiguration: Defines the backend URLs and connection parameters for the SDK.

  • VideoCall: Represents a single video call session. Manages the call state, events, and connection details.

  • WebRtcConnection: Handles the underlying WebRTC peer connection, media streams, and renderers. Accessed via VideoCall.connect().

  • Events: The SDK uses a delegate system to notify your application about call state changes, errors, and media events (VideoCallEvent).

  • Renderers (RTCMTLVideoView): Standard WebRTC views used to display local and remote video. You need to manage their lifecycle and attach/detach them using WebRtcConnection.

Demo Application Flow

The demo app follows this basic flow:

  1. EnvironmentsScreen: Allows selecting or defining backend connection configurations (Service URL, etc.). This is primarily for demonstration purposes; your app will likely have a fixed configuration or fetch it dynamically.

  2. MainScreen: Collects necessary call information (e.g., caller name, attributes), requests permissions (Camera, Mic), and initiates the call using sdk.createCall().

  3. QueueScreen: Displays a waiting state while the call connects to an agent. Listens for AgentConnected or Error/Hangup events.

  4. CallScreen: The main video call interface. Manages RTCMTLVideoViews for local and remote video, displays call controls (mute, switch camera, hangup), and reacts to SDK events to update the UI.


Next Steps

  • Follow the steps in Getting Started to set up the SDK and demo app.

  • Read Usage Guide for a detailed understanding of the SDK's core components.

  • Consult Demo Application Guide while examining the demo app source code.

Usage Guide

This document provides detailed information about using the ECV Video Call iOS SDK in your application.

SDK Initialization

Basic Initialization

import ECV175_iOS_SDK

// Create SDK configuration
let config = ECVSdkConfiguration(
    name: "Environment_Name" // Give it any name that refers to your app or backend
    serviceUrl: "YOUR_SERVICE_URL",
    useQueueSocket: true // or false based on your backend version (false: 1.5 or true or false: 1.75)
)

// Initialize SDK instance
let sdk = ECVSdk(config: config)

Configuration Options

The ECVSdkConfiguration class supports various configuration options:

let config = ECVSdkConfiguration(
    name: "Environment_Name",
    serviceUrl: "YOUR_SERVICE_URL",
    useQueueSocket: true,
    webrtcUrl: "YOUR_WEBRTC_URL", // Optional, default: serviceUrl
    signalingUrl: "YOUR_SIGNALING_URL", // Optional, default: serviceUrl
    waitForRoomIntervalDelay: Int, // Optional ( For 1.5 only ), default: 3000
    waitForRoomIntervalEndpoint: String // Optional ( For 1.5 only ), default: "serviceUrl + API.waitForRoomInterval + "?rtcId=""
)

Extra Configs

Logger.setLevel(logLevel: .debug) // .debug To enable debugging everything in SDK (.debug, .info, .warn, .error) // default: .info

Creating and Managing Calls

Creating a Call

// Create and start the video call
let call = await sdk.createVideoCall() 

If you already have an rtcId, you can provide it to the createVideoCall function, otherwise it will be generated from the backend automatically (default)

let call = await sdk.createVideoCall(rtcId: "your_rtcId")

VideoCall Events

Now before we start the call we can add event listener to the call

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
    }

    if(newEvent.type == .AgentConnected) {
        Logger.info("From sdk.modules.onCallEvent: \(newEvent.type)")
    }
    else if(newEvent.type == .Error){
        Logger.error("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")
    }
    else {
        Logger.info("From sdk.modules.onCallEvent: \(newEvent)")
    }
}

VideoCall Parameters

To start the call, we need to prepare DialCallParameters object

let dialCallParameters = DialCallParameters(
    callerName: "Caller_Name",      // Optional but should be provided, otherwise a random name will be generated
    attributes: [String: String],   // Optional, Attach data for the call
    queue: String?,                 // Optional, Queue name, if not provided, will use system's default queue
    
    // Advanced Optional Params
    acdAttributes: [String: String],
    customPayload: [String: String],
    additionalHeaders: [String: String],
)

Example:

let dialCallParameters = DialCallParameters(
    callerName: "John Doe",
    attributes: [
        "department": "support",
        "priority": "high"
    ],
    queue: "TechSupport",
)

Starting the VideoCall

If we want to start a VideoCall, we use call.start

await call.start(dialCallParameters: dialCallParameters)

Wait for an Agent to connect (inQueue)

There are two simple ways to wait for an agent to connect

1- (1.5 and 1.75+) Using VideoCall events we already implemented above if(newEvent.type == .AgentConnected)

2- (1.75+ Only) Using awaited promisified task:

let isAgentConnected = await call.agentConnected?.value
Logger.info("isAgentConnected: \(String(describing: isAgentConnected))")

if(isAgentConnected == true) {
    // Openning the call in a saperate web browser maybe? // useful for Development
    if let urlString = call.getUrl(), let url = URL(string: urlString) {
        UIApplication.shared.open(url, options: [:], completionHandler: nil)
    }
    else {
        Logger.error("Failed to open URL: invalid URL string")
    }
}
else {
    Logger.warn("Agent not connected!")
}

Connect the VideoCall

1- Using the videoCall.connect()

In your CallScreen.swift

func setup() {
  Task {
      guard let rtcId = self.rtcId else {
          self.callError = "RtcId not initialized"
          Logger.error("Setup failed: RtcId is nil")
          return
      }

      if let videoCall = appState.sdk?.getVideoCall(rtcId: rtcId){
          self.videoCall = videoCall
      }
      else {
          self.videoCall = await appState.sdk?.createVideoCall(rtcId: rtcId)
      }
      
      // Handle call events
      let ECVModules = appState.sdk?.modules
      ECVModules?.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
          if(self.videoCall?.rtcId != videoCall.rtcId){
              return // Making sure we only catch the events related to the current call. // comment this line to catch all calls' events
          }
          
          if(newEvent.type == .CallEnded){
              ECVModules?.onCallEvent = [] // reset events to prevent overflow of events
              path.append(NavigationRoute.mainScreen)
          }
      }

      guard let webRtcConnection = self.videoCall?.connect() else {
          self.callError = "WebRtcConnection failed to connect"
          Logger.error("Setup failed: WebRtcConnection is nil")
          return
      }
      self.webRtcConnection = webRtcConnection

      Logger.debug("Instantiating and assigning renderers...")
      let local = Renderer()
      let remote = Renderer()

      webRtcConnection.localVideoRenderer = local
      webRtcConnection.remoteVideoRenderer = remote

      self.localRenderer = local
      self.remoteRenderer = remote

      Logger.info("Starting WebRTC room...")
      await webRtcConnection.startRoom()
      Logger.info("Call setup complete.")
  }
}

2- Or the caller can join the video call simply using web browser (not recommended / useful for dev)

if let urlString = call.getUrl(), let url = URL(string: urlString) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
else {
    Logger.error("Failed to open URL: invalid URL string")
}

Video Call Interface

Setting Up Video Views

import Foundation
import SwiftUI
import Combine
import ECV175_iOS_SDK
import WebRTC

struct CallScreen: View {
    let rtcId: String?
    @EnvironmentObject var appState: AppState

    @Binding var path: NavigationPath

    @State private var videoCall: VideoCall?
    @State private var webRtcConnection: WebRtcConnection?

    @State private var localRenderer: Renderer?
    @State private var remoteRenderer: Renderer?

    let localStreamWidth: CGFloat = 80
    let localStreamHeight: CGFloat = 120
    let margin: CGFloat = 10
    let pipBottomPadding: CGFloat = 70
    
    var body: some View {
        ZStack {
            Color.black.ignoresSafeArea()

            WebRtcVideoView(renderer: remoteRenderer).ignoresSafeArea()

            WebRtcVideoView(renderer: localRenderer)
                .frame(width: localStreamWidth, height: localStreamHeight)
                .cornerRadius(8)
                .overlay(
                    RoundedRectangle(cornerRadius: 8)
                        .stroke(Color.white.opacity(0.5), lineWidth: 1)
                )
                .padding(.trailing, margin)
                .padding(.bottom, margin + pipBottomPadding)

            // ... Call Buttons
        }
        .navigationBarHidden(true)
        .statusBar(hidden: true)
        .onAppear(perform: setup)
        .onDisappear {
            hangup(by: "onDisappear")
        }
    }

    // MARK: - Video Renderer SwiftUI Wrapper

    struct WebRtcVideoView: UIViewRepresentable {
        let renderer: (any RTCVideoRenderer)?

        func makeUIView(context: Context) -> UIView {
            if let renderer = renderer as? Renderer {
                return renderer.view
            } else if let renderer = renderer as? Renderer {
                return renderer.view
            } else {
                return UIView()
            }
        }

        func updateUIView(_ uiView: UIView, context: Context) {}
    }

    // MARK: - Custom Renderer Classes

    class Renderer: NSObject, RTCVideoRenderer {
        let view: RTCMTLVideoView

        override init() {
            self.view = RTCMTLVideoView()
            self.view.videoContentMode = .scaleAspectFill
            super.init()
            Logger.debug("Renderer created")
        }

        deinit { Logger.debug("Renderer deallocated") }

        func setSize(_ size: CGSize) {
            view.setSize(size)
        }

        func renderFrame(_ frame: RTCVideoFrame?) {
            view.renderFrame(frame)
        }
    }

    func hangup(by: String = "HangUpButton") {
        Logger.debug("Hangup requested by: \(by)")

        videoCall?.hangup()

        videoCall = nil
        webRtcConnection = nil
        localRenderer = nil
        remoteRenderer = nil
    }

    // func setup(){
    // ... paste it here
    //}
}

Call Controls

// Mute/Unmute Audio
webRtcConnection.setAudioEnabled(state: false) // Mute
webRtcConnection.setAudioEnabled(state: true)  // Unmute

// Enable/Disable Video
webRtcConnection.setVideoEnabled(state: false) // Disable video
webRtcConnection.setVideoEnabled(state: true)  // Enable video

// Switch Camera
try await webRtcConnection.switchCamera()

// Enable/Disable Flashlight
webRtcConnection.setFlashlightEnabled(state: true)
webRtcConnection.setFlashlightEnabled(state: false)

// End Call
videoCall.hangup()

Video Call Event Handling

When an event gets fired in sdk.modules.onCallEvent, the newEvent is VideoCallEvent and newEvent.type is VideoCallEventType

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
    }

    if(newEvent.type == .DialSuccess){
        Logger.debug("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")
    }
    else if(newEvent.type == .CallEnded){
        // handle it
    }
    //else if(){

    //}
}

each type is self explanatory:

public enum VideoCallEventType {
    case None
    case Init
    case GotRtcId
    case DialSuccess
    case Error
    case OnQueue
    case AgentConnected
    case CallEnded
    case SignalingConnected
    
    case GotLocalStream
    case GotRemoteStream
    
    case JoinedRoom
    case SignalingRegistered
    case RtcPeerConnectionCreated
    
    case GotOffer
    case SentOffer
    case GotAnswer
    case SentAnswer
    
    case AudioMuted
    case AudioUnmuted
    case VideoMuted
    case VideoUnmuted
    
    case RemoteAudioMuted
    case RemoteAudioUnmuted
    case RemoteVideoMuted
    case RemoteVideoUnmuted
    
    case StartHold
    case EndHold
    
    case TakePhotoOn
    case TakePhotoOff
    
    case WebRtcOnDataChannel
    case TextChannelConnected
    case TextChannelMessage
    case ConnectionTimeoutStart
    case ConnectionTimeoutEnd
    case WebRtcConnected
    case WebRtcRenegotiationNeeded

    case WebRtcDisconnected
    case WebRtcFailed
    case Hangup

    case FlashOn
    case FlashOff
    
    case TransferPending
    case TransferCancelled
    case TransferCompleted
}

Error Handling

To handle errors more precisely, we can check videoCall.lastError when an error happens under .onCallEvent

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
    }

    if(newEvent.type == .Error){
        Logger.error("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")

        Logger.error(videoCall.lastError)
    }
}

videoCall.lastError's type is ErrorObject and inside it there are:

public class ErrorObject {
    public var name: ErrorName
    public var message: String
    
    public init(name: ErrorName = ErrorName.NO_ERROR, message: String = "No error.") {
        self.name = name
        self.message = message
    }
}

public enum ErrorName {
    case NO_ERROR
    case SERVICE_URL_REQUIRED
    case CALL_NOT_INITIALIZED
    case CALL_ALREADY_DIALED
    case QUEUE_SOCKET_CLOSED
    case ERROR_DIALING
    case CALL_NOT_DIALED
    case ERROR_WAITING_FOR_ROOM
    case NO_ICE_SERVERS_FOUND
    case HANGUP_FAILED
}

So we can basically check for a specific error:

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call.rtcId){
        return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
    }

    if(newEvent.type == .Error){
        Logger.error("From sdk.modules.onCallEvent: \(newEvent.detail ?? "")")
        Logger.error(videoCall.lastError)

        switch videoCall.lastError.name {
            case .SERVICE_URL_REQUIRED
                break
            case .CALL_NOT_DIALED
                break
            case .NO_ICE_SERVERS_FOUND
                break
            // etc..
        }
    }
}

Permission Handling

// Request camera and microphone permissions
AVCaptureDevice.requestAccess(for: .video) { granted in
    if granted {
        // Camera permission granted
    } else {
        // Camera permission denied
    }
}

AVCaptureDevice.requestAccess(for: .audio) { granted in
    if granted {
        // Microphone permission granted
    } else {
        // Microphone permission denied
    }
}

Best Practices

  1. Single SDK Instance: Maintain a single instance of ECVSdk throughout your application's lifecycle.

  2. Proper Cleanup: Always clean up resources when the call ends:

  3. Error Recovery: Implement proper error recovery mechanisms:

  4. Memory Management: Be careful with retain cycles:


weak var call: VideoCall?
weak var webRtcConnection: WebRtcConnection?

Configuration Guide

This document details the configuration options available in the ECV Video Call iOS SDK.

SDK Configuration

Basic Configuration

The ECVSdkConfiguration class is used to configure the video call instance:

let config = ECVSdkConfiguration(
    name: "Environment_Name" 
    serviceUrl: "YOUR_SERVICE_URL",
)

Required Parameters

Parameter

Type

Description

name

String

Give it any name that refers to your app or backend

serviceUrl

String

The base URL of your ECV backend service

Optional Parameters

Parameter

Type

Description

webrtcUrl

String

Custom WebRTC server URL, default: same as serviceUrl

signalingUrl

String

Custom signaling server URL, default: same as serviceUrl

useQueueSocket

Bool

Whether to use the queue socket (true for v1.75+, false for v1.5), default: true

waitForRoomIntervalDelay

Integer

It will be active/used only if useQueueSocket: false, in ECV 1.5, It is the interval time to check if agent connected using http request, default: 3000

waitForRoomIntervalEndpoint

String

It will be active/used only if useQueueSocket: false, in ECV 1.5, It is "the waiting for agent to connect" http endpoint, default: serviceUrl + API.waitForRoomInterval + "?rtcId="

Example Usages:

let configV175 = ECVSdkConfiguration(
    name: "Some ECV 1.75 App" 
    serviceUrl: "https://prod-v175.ecv.example.com"
)

let configV15 = ECVSdkConfiguration(
    name: "Some ECV 1.5 App" 
    serviceUrl: "https://prod-v15.ecv.example.com",
    useQueueSocket: false
)

let configSeparateV15 = ECVSdkConfiguration(
    name: "Some custom ECV 1.5 App",
    serviceUrl: "https://api.example.com",
    webrtcUrl: "https://webrtc-v15.example.com", // Separate endpoint for /join
    signalingUrl: "https://websocket-v15.example.com", // Separate WebSocket server (if applicable for v1.5 signaling)
    useQueueSocket: false, // Required for v1.5
    waitForRoomIntervalDelay: 3000, // Poll every 5 seconds
    waitForRoomIntervalEndpoint: "/custom/pollstatus?callid=" // Custom polling endpoint
)

Call Configuration

Basic Call Configuration

The DialCallParameters class is used to configure individual calls:

let dialCallParameters = DialCallParameters() // No required parameters

Optional DialCallParameters Parameters

Parameter

Type

Description

callerName

String

Name of the caller, Optional but should be provided, otherwise a random name will be generated

attributes

[String: String]

Attach data for the call

queue

String

if not provided, will use system's default queue

acdAttributes

[String: String]

Extra custom acdAttributes for the call (Advanced Backend Settings)

customPayload

[String: String]

Extra custom payload for the call (Advanced Backend Settings)

additionalHeaders

[String: String]

Extra http headers can be applied to the dialRequest

Example Usages:

let dialCallParameters = DialCallParameters(
    callerName: "Jane Doe",
    attributes: [
        "accountNumber" to "ACC9876",
        "lastInteraction" to "2023-10-26"
    ],
    queue: "SupportQueue",
    additionalHeaders: ["X-Auth-Token": "user_session_token"]
)

await call.start(dialCallParameters: dialCallParameters)

WebRTC Connection Parameters

These parameters are optionally provided when you call videoCall.connect() and fine-tune the WebRTC peer connection behavior. Most parameters have sensible defaults.

  • Connection & Signaling:

    • connectionTimeout: Int = 10000: Timeout (ms) for the overall WebRTC connection attempt (used by a manual timer in WebRtcConnection).

    • joinHttpConnectTimeout: Int = 3000: HTTP connect timeout (ms) for the /join request.

    • joinHttpReadTimeout: Int = 3000: HTTP read timeout (ms) for the /join request.

    • additionalHeaders: [String: String]: Custom HTTP headers added to the /join request and WebSocket signaling connection handshake. Useful for authentication tokens that need to reach the WebRTC/Signaling infrastructure.

  • Media Control & Behavior:

    • videoCallEnabled: Boolean = true: Whether video tracks should be set up. Set to false for audio-only calls.

    • useSpeakerPhone: Boolean = true: Start the call using the speakerphone.

    • autoHandleCameraSwitch: Boolean = true: Allows the SDK to automatically switch cameras when certain events occur (e.g., agent requests photo with back camera).

    • autoHandleHoldSideEffects: Boolean = true: Automatically mutes/unmutes local audio/video when the agent puts the call on hold or resumes it.

  • Video Configuration:

    • videoWidth: Int = 480 / videoHeight: Int = 640: Requested video resolution.

    • videoFps: Int = 0 (Defaults to ~30): Requested video frame rate.

    • videoMaxBitrate: Int = 0 (WebRTC default): Maximum bitrate (kbps) for video encoding.

    • videoCodec: String = "VP8": Preferred video codec ("VP8", "VP9", "H264").

    • videoCodecHwAcceleration: Boolean = true: Enable hardware acceleration if available.

  • Audio Configuration:

    • audioCodec: String = "OPUS": Preferred audio codec ("OPUS", "ISAC").

    • audioStartBitrate: Int = 0 (WebRTC default): Initial audio bitrate (kbps).

    • noAudioProcessing: Boolean = false: Disable platform audio processing (AEC, AGC, NS).

    • disableBuiltInAEC: Boolean = false / disableBuiltInNS: Boolean = false: Disable hardware AEC/NS if available.

  • Debugging:

    • tracing: Boolean = false: Enable WebRTC internal tracing.

  • Data Channel (textChannelConfig):

    • textChannelConfig: DataChannelConfig? = DataChannelConfig(...): Configuration for the built-in text data channel used for commands like FLASH_ON/OFF, HOLD/PICKUP, etc. Usually left at default. See DataChannelConfig class for details.

Example Usage:

let customConnectParams = ConnectParameters(
    videoWidth: 1280,
    videoHeight: 720,
    videoCodec: "H264",
    additionalHeaders: ["X-WebRTC-Auth": "webrtc_token"]
)

// Get the WebRtcConnection using the custom parameters
let webRtcConnection = videoCall.connect(parameters: customConnectParams)
webRtcConnection.startRoom()

DataChannelConfig

DataChannelConfig is a configuration model used to initialize a data channel in a peer-to-peer communication environment.

Properties

Property

Type

Description

enabled

Bool

Whether the data channel is enabled.

label

String

A human-readable label for the data channel.

id

Int

ID for the data channel (-1 if not set).

negotiated

Bool

Whether the channel is pre-negotiated (manual setup).

protocol

String

Subprotocol name (renamed to avoid Swift keyword conflict).

ordered

Bool

Whether messages must arrive in order.

maxRetransmits

Int

Max number of retransmits (-1 if unset).

maxRetransmitTimeMs

Int

Max time for retransmissions in milliseconds (-1 if unset).

Initializer

init(
    enabled: Bool,
    label: String,
    id: Int = -1,
    negotiated: Bool = false,
    protocol: String = "",
    ordered: Bool = true,
    maxRetransmits: Int = -1,
    maxRetransmitTimeMs: Int = -1
)

Demo Application Guide

Overview

The ECV175_iOS_DEMO application serves as a practical example of how to integrate and utilize the ECV Video Call SDK in an iOS application. This guide will walk you through the application's architecture, key components, and how to use it effectively.

Application Architecture

Core Components

  1. App Structure

    • Built using SwiftUI framework with modern iOS development practices

    • Follows MVVM (Model-View-ViewModel) architecture pattern

    • Uses SwiftUI's navigation system for screen management

    • Implements type-safe navigation using NavigationRoute enum

    • Utilizes environment objects for dependency injection

  2. State Management

    • AppState: Central state management class that handles:

      • Environment configuration persistence using UserDefaults

      • SDK instance lifecycle management

      • Environment selection and storage

      • Reactive UI updates using @Published properties

    • ConfigurationManager: Manages environment configurations

      • Handles predefined and custom environments

      • Provides CRUD operations for environment configurations

      • Persists environment settings

  3. Navigation System

    • Type-safe navigation using NavigationRoute enum with three main routes:

      • mainScreen: Entry point after environment selection

      • queueScreen(rtcId:name:queue:): Queue management interface

      • callScreen(rtcId:): Video call interface

    • Implements proper back navigation and deep linking support

    • Uses SwiftUI's NavigationStack for modern navigation

Key Features

  1. Environment Selection

    • Supports multiple environment configurations:

      • Predefined environments

      • Custom environments with configurable settings

    • Environment configuration includes:

      • Service URL

      • WebRTC URL (optional)

      • Signaling URL (optional)

      • Queue socket configuration

      • Room interval settings

    • Persists selected environment using UserDefaults

    • Automatic SDK initialization with selected environment

  2. Video Call Integration

    • Seamless integration with ECV Video Call SDK

    • Features include:

      • Real-time video streaming

      • Audio controls (mute/unmute)

      • Video controls (enable/disable)

      • Camera switching

      • Flashlight control

      • Picture-in-Picture support

    • Queue management system:

      • Join existing queues

      • Create new queues

      • Monitor queue status

    • Call event handling:

      • Call state management

      • Error handling

      • Connection status monitoring

Getting Started

Prerequisites

  • Xcode 14.0 or later

  • iOS 15.0 or later

  • ECV175_iOS_SDK.xcframework

  • WebRTC.xcframework
  • Camera and microphone permissions

  • Internet connectivity

Setup Process

  1. Project Configuration

    • Open ECV175_iOS_DEMO.xcodeproj

    • Ensure both demo app target is properly configured

    • Check that all dependencies are correctly linked

    • Configure necessary permissions in Info.plist:

      • Camera usage description

      • Microphone usage description

  2. Environment Configuration

    • Launch the application

    • Select your desired environment from the environment selection screen

    • Configure environment settings:

      • Service URL (required)

      • WebRTC URL (optional)

      • Signaling URL (optional)

      • Queue socket settings

    • The app will automatically initialize the SDK with the selected configuration

Application Flow

  1. Initial Launch

    • App checks for previously saved environment configuration

    • If found, automatically navigates to main screen

    • If not, presents environment selection screen

    • Requests necessary permissions (camera, microphone)

  2. Main Screen

    • Displays available options for video calls

    • Shows current environment configuration

    • Provides access to:

      • Queue selection

      • Call initiation

      • Environment settings

    • Handles permission management

    • Initializes video call instance

  3. Queue Management

    • Join existing queues

    • Create new queues

    • Monitor queue status

    • Configure queue settings

    • Handle queue events

  4. Video Call Interface

    • Real-time video streaming with:

      • Local video preview

      • Remote video display

      • Picture-in-Picture support

    • Call controls:

      • Audio mute/unmute

      • Video enable/disable

      • Camera switching

      • Flashlight control

    • Call management:

      • Hang up functionality

      • Error handling

      • Connection status monitoring

Code Structure

Key Files

  1. ECV175_iOS_DEMOApp.swift

    • Application entry point

    • Sets up main navigation structure

    • Initializes core state objects:

      • AppState

      • ConfigurationManager

    • Configures environment objects

  2. AppState.swift

    • Manages application state

    • Handles environment persistence

    • Controls SDK lifecycle

    • Implements state restoration

    • Manages SDK instance

  3. Views Directory

    • Screens/:

      • EnvironmentsScreen.swift: Environment selection and configuration

      • MainScreen.swift: Main interface and call initiation

      • CallScreen.swift: Video call interface

      • QueueScreen.swift: Queue management

    • Components/: Reusable UI components

    • Implements SwiftUI views with proper state management

Best Practices

  1. State Management

    • Use @EnvironmentObject for dependency injection

    • Maintain clean separation of concerns

    • Implement proper error handling

    • Use @Published properties for reactive updates

    • Handle state persistence appropriately

  2. Navigation

    • Use type-safe navigation routes

    • Implement proper back navigation

    • Handle deep linking scenarios

    • Manage navigation state

    • Handle navigation errors

  3. SDK Integration

    • Initialize SDK only when needed

    • Properly handle SDK lifecycle

    • Implement error handling for SDK operations

    • Manage resources efficiently

    • Handle SDK events properly

Troubleshooting

  1. Common Issues

    • SDK initialization failures:

      • Check environment configuration

      • Verify network connectivity

      • Ensure proper permissions

    • Navigation state inconsistencies:

      • Verify navigation routes

      • Check state management

      • Handle navigation errors

    • Environment configuration problems:

      • Validate configuration settings

      • Check URL formats

      • Verify required fields

  2. Debug Tips

    • Check console logs for detailed error messages

    • Verify environment configuration

    • Ensure proper SDK initialization

    • Monitor network connectivity

    • Check permission status

    • Use Xcode debugging tools

SDK Integration and Technical Details

Core Configuration and Build System

  1. Build.swift

    • Manages different build flavors and environments

    • Supports multiple bundle identifiers:

      • ccr.ECV175-iOS-DEMO.CORE: Core demo environment

    • Each environment configuration includes:

      • Service URL

      • Queue configurations

      • Predefined settings

        • such as callAttributes
  2. AppState.swift

    • Central state management for SDK integration

    • Key responsibilities:

      • SDK instance lifecycle management

      • Environment configuration persistence

      • State restoration

      • Event handling coordination

    • Uses @Published properties for reactive updates

    • Implements UserDefaults persistence for environment selection

Screen-by-Screen SDK Integration

  1. EnvironmentsScreen

    • Environment selection and configuration

    • SDK Integration:

      • Manages environment configurations

      • Handles predefined and custom environments

      • Persists environment selection

      • Initializes SDK with selected configuration

  2. MainScreen

    • Primary interface for call initiation

    • SDK Integration:

      • Initializes video call instance

      • Manages permissions (camera, microphone)

      • Handles queue selection

      • Sets up call event listeners

      • Key features:

        // SDK initialization
        let call = await appState.sdk?.createVideoCall()
        
        // Event handling
        ECVModules?.onCallEvent.append { (videoCall, newEvent, lastEvent) in
            // Handle call events
        }
        
  3. QueueScreen

    • Queue management and agent connection

    • SDK Integration:

      • Handles queue joining

      • Manages agent connection

      • Supports both ECV 1.75 and 1.5 versions

      • Key features:


        // Queue joining
        let dialCallParameters = DialCallParameters(
            callerName: name,
            queue: queue,
            attributes: [queueKey: queueValue]
        )
        
        // Event listening
        let ECVModules = appState.sdk?.modules
        ECVModules?.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
           if(videoCall.rtcId != call?.rtcId){
               return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
           }
        
           // Agent connection handling
           if(newEvent.type == .AgentConnected) {
               // AGENT IS CONNECTED
               // GO TO CALL SCREEN
           }
           else if(newEvent.type == .Error){
               // showError = true
           }
        }
        
        // Start the call
        await call?.start(dialCallParameters: dialCallParameters)

  4. CallScreen

    • Video call interface

    • SDK Integration:

      • Manages WebRTC connection

      • Handles video/audio streams

      • Controls call features

      • Key features:

        // WebRTC setup
        let webRtcConnection = videoCall?.connect()
        
        // Stream management
        localRenderer = webRtcConnection?.localStream
        remoteRenderer = webRtcConnection?.remoteStream
        
        // Call controls
        func toggleLocalAudio()
        func toggleLocalVideo()
        func toggleFlashlight()
        func switchCamera()
        func switchViewRenderers()
        

SDK Version Compatibility

  1. ECV 1.75 Features

    • Queue socket support

    • Enhanced event handling

    • Improved agent connection management

    • Better state management


       

  2. ECV 1.5 Features

    • Traditional queue management

    • Interval-based agent connection

    • Basic event handling

    • Legacy support

Event Handling System

  1. Call Events

    • Event types:

      • AgentConnected

      • CallEnded

      • Error

    • Event handling:

      ECVModules?.onCallEvent.append { (videoCall, newEvent, lastEvent) in
          switch newEvent.type {
          case .AgentConnected:
              // Handle agent connection
          case .CallEnded:
              // Handle call end
          case .Error:
              // Handle errors
          }
      }
      
  2. State Management

    • Call state tracking

    • Connection state monitoring

    • Error state handling

    • Queue state management

Best Practices for SDK Integration

  1. Initialization

    • Initialize SDK only when needed

    • Properly handle configuration

    • Manage SDK lifecycle

    • Handle initialization errors

  2. Event Handling

    • Use proper event filtering

    • Handle all event types

    • Implement error handling

    • Clean up event listeners

  3. Resource Management

    • Properly dispose of resources

    • Handle cleanup on app termination

    • Manage memory efficiently

    • Handle background/foreground transitions

  4. Error Handling

    • Implement comprehensive error handling

    • Provide user feedback

    • Handle network issues

    • Manage SDK-specific errors

Conclusion

The ECV175_iOS_DEMO application provides a solid foundation for implementing video call functionality using the ECV Video Call SDK. By following this guide and understanding the application's architecture, you can effectively integrate video calling features into your own iOS applications.

For more detailed information about specific features or implementation details, please refer to the SDK documentation and API reference.

Events and State Management

Introduction

The ECV iOS SDK operates asynchronously. It uses an event-driven architecture to communicate status changes, errors, and other important occurrences to your application. Understanding these events is crucial for building a responsive and robust UI.

This guide details the structure of SDK events, lists the key event types, and mentions relevant state properties.

Subscribing to Events

You listen for events on a sdk.modules.onCallEvent instance:

sdk.modules.onCallEvent.append { (videoCall: VideoCall, newEvent: VideoCallEvent, lastEvent: VideoCallEvent) in
    if(videoCall.rtcId != call?.rtcId){
        return // Making sure we only catch the events related to the current call. // comment this code to catch all calls' events
    }
    
    if(newEvent.type == .Error){
        // Handle Error event
    }
    else if(newEvent.type == .AgentConnected) {
        // Handle AgentConnected event
    }
    else {
        print("From ECVModules?.onCallEvent: \(newEvent)")
    }
}

Refer to the "Video Call Event Handling" section in Usage Guide for detailed code examples of both subscription methods.

Event Structure (VideoCallEvent)

Key Fields:

  • type: VideoCallEventType: (Required) An enum value indicating the kind of event that occurred. This is the primary field you'll check to determine how to react.

  • detail: String?: (Optional) Provides additional string information specific to the event (e.g., the rtcId for GotRtcId, an interaction ID for DialSuccess, a reason for CallEnded).

  • objData: Any?: (Optional) Can contain related objects, such as the WebRtcConnection during SignalingConnected or an HTTP response object on certain errors. Use type checking (is or instanceof) if accessing this data.

  • data: [String: String]: (Optional, often empty) A generic map for potential key-value data associated with the event.

  • createdAt: Date: Date when the event was created within the SDK.

Event Type Reference (VideoCallEventType)

Here's a breakdown of common event types, grouped by the typical phase of a call:

Phase 1: Initialization & Dialing

  • GotRtcId

    • When: Fired after sdkInstance.createVideoCall() successfully retrieves a unique call identifier from the backend.

    • Data: event.detail contains the rtcId (String).

    • Response: Store the rtcId. You need it to retrieve the VideoCall instance later and to initiate dialing (call.start()).

  • DialSuccess

    • When: Fired after call.start() successfully sends the dial request to the backend and receives confirmation (often includes an Interaction ID).

    • Data: event.detail may contain the Interaction ID. event.objData might hold the raw JSON response.

    • Response: Usually indicates you can proceed to the waiting/queue state.

  • Error (During Init/Dial)

    • When: Can occur if fetching rtcId fails, call.start() parameters are invalid, the backend rejects the dial request, or network issues occur.

    • Data: event.error contains the ECVError. event.detail may have more context. event.objData might hold an HTTP response. Check error.error for specific ErrorTypes like CALL_NOT_INITIALIZED, DIAL_FAILED.

    • Response: Display an error message to the user. Prevent further call progress. Potentially clean up the VideoCall instance if it's unusable.

Phase 2: Queue & Agent Connection

  • OnQueue

    • When: Fired after a successful dial, indicating the caller is now waiting for an agent.

    • Data: Typically no specific data needed.

    • Response: Show a waiting UI (like QueueScreen).

  • AgentConnected

    • When: Fired when an agent accepts the call. This is the trigger to establish the peer-to-peer media connection.

    • Data: None specific.

    • Response: Initiate the WebRTC connection:

      1. Call videoCall.connect() to get the WebRtcConnection.

      2. Call webRtcConnection.startRoom() to begin WebRTC setup.

      3. Navigate to the main call screen (CallScreen).

  • Error (During Queue)

    • When: Can occur due to queue timeouts, network issues with the queue mechanism (QUEUE_SOCKET_ERROR), or backend problems (WAIT_FOR_ROOM_FAILED if using polling (in 1.5)).

    • Data: event.error contains the ECVError.

    • Response: Inform the user the call couldn't connect. Navigate back from the queue screen. Call videoCall.hangup() to ensure cleanup if the SDK doesn't automatically trigger CallEnded.

  • Hangup / CallEnded (During Queue)

    • When: If the user explicitly hangs up (videoCall.hangup()), or the call is terminated by the backend before an agent connects.

    • Data: event.detail might contain a reason ("LOCAL_HANGUP", etc.).

    • Response: Navigate back from the queue screen. Ensure UI is cleaned up.

Phase 3: WebRTC Connection Establishment

These events are typically handled internally by WebRtcConnection but can be observed for debugging or advanced use cases. The key event signaling success is WebRtcConnected.

  • SignalingConnected: WebSocket connection to the signaling server established.

  • SignalingRegistered: Client successfully registered with the signaling server for the room (rtcId).

  • JoinedRoom: HTTP /join request successful, received initial parameters (like ICE servers, initiator status). event.objData contains JoinRoomResponse.

  • RtcPeerConnectionCreated: The underlying WebRTC PeerConnection object has been created. event.objData holds the PeerConnectionClient.

  • GotOffer / SentOffer: SDP Offer received from / sent to the peer. event.objData has the JSON message.

  • GotAnswer / SentAnswer: SDP Answer received from / sent to the peer. event.objData has the JSON message.

  • WebRtcConnected

    • When: The WebRTC PeerConnection (ICE and DTLS) is fully established and media can start flowing.

    • Data: None specific.

    • Response: Often used as a trigger to know the media path is ready. You might hide initial loading indicators on the CallScreen.

  • TextChannelConnected: The specific data channel used for text commands (hold, flash, etc.) is open. event.objData holds the DataChannel.

  • WebRtcDisconnected / WebRtcFailed: The WebRTC connection dropped or failed unexpectedly. Usually leads to a CallEnded event shortly after.

Phase 4: Active Call & Media

  • GotLocalStream: Local camera capture has started. event.objData might contain the VideoCapturer.

  • GotRemoteStream: Remote media stream is available (often triggered around WebRtcConnected).

    • Response: Ensure remote SurfaceViewRenderer is attached.

  • AudioMuted / AudioUnmuted: Local audio sending state changed (triggered by setAudioEnabled).

    • Response: Update the mute button UI state.

  • VideoMuted / VideoUnmuted: Local video sending state changed (triggered by setVideoEnabled).

    • Response: Update the video mute button UI state. Potentially show/hide the local video preview overlay.

  • RemoteAudioMuted / RemoteAudioUnmuted: The remote participant muted/unmuted their audio.

    • Response: Optionally display an indicator that the remote party is muted.

  • RemoteVideoMuted / RemoteVideoUnmuted: The remote participant muted/unmuted their video.

    • Response: Show/hide an overlay or indicator on the remote video renderer.

  • StartHold / EndHold: The call was put on hold or taken off hold by the agent (usually triggered via the text data channel).

    • Response: Show/hide a "Call on Hold" overlay. The SDK (if autoHandleHoldSideEffects is true) might automatically mute/unmute local media in response.

  • TakePhotoOn / TakePhotoOff: Signals related to agent-initiated photo capture features.

    • Response: Adjust UI if needed (e.g., potentially force fullscreen local video). The SDK might automatically switch cameras if autoHandleCameraSwitch is true.

  • TextChannelMessage: A command or message received over the text data channel.

    • Data: event.detail contains the raw message string (e.g., "FLASH_ON", "STATUS|VIDEO|MUTED|rtcId").

    • Response: SDK handles many standard messages internally (like HOLD, PICKUP, MUTE statuses, FLASH commands). You might react to custom messages specific to your implementation.

Phase 5: Call Termination

  • Hangup

    • When: Fired immediately when videoCall.hangup() is called locally.

    • Data: None specific.

    • Response: Can be used to initiate UI cleanup before the backend confirms the call end. Note that CallEnded is the definitive end event.

  • CallEnded

    • When: The final event indicating the call session is completely terminated, either locally (hangup() completion), remotely (agent/peer hangs up), or due to an unrecoverable error.

    • Data: event.detail often contains a reason (e.g., "LOCAL_HANGUP", "REMOTE_HANGUP", "PEER_DISCONNECTED", "CONNECTION_TIMEOUT", error description).

    • Response: This is the definitive signal to:

      1. Clean up all call-related UI (renderers, controls).

      2. Navigate away from the call screen.

      3. Release any resources tied to the specific VideoCall. The SDK typically removes its internal reference to the VideoCall instance after this event.

State Properties

While events signal changes, you can also check some current state properties:

  • On VideoCall:

    • rtcId: String: The unique call identifier (available after GotRtcId).

    • dialResponse: [String: Any]?: When the DialSuccess event occurs, this property contains the raw dial response.

    • agentConnected: Boolean: True if the AgentConnected event has occurred.

    • webRtcConnection: WebRtcConnection?: The associated WebRTC connection (available after connect() is called).

It's generally best practice to react to events to update your UI state rather than constantly polling these properties. Use the properties for initial state checks or decisions where needed (e.g., checking AgentConnected before calling connect()).

Customization Guide

Introduction

The provided Demo Application (ECV175_iOS_DEMO) showcases the core functionalities of the ECV SDK. However, your production application will undoubtedly have different UI/UX requirements, configuration methods, and data needs. This guide provides advice on adapting the SDK and demo concepts to your specific application.

1. UI Implementation

The demo uses SwiftUI, but the SDK itself is UI-agnostic. You can integrate it into applications built with:

  • SwiftUI

  • UIKit

  • Any combination thereof.

Key Considerations for Your UI:

  • Build Your Own UI: Do not simply copy the demo UI screens. Design screens that match your application's look, feel, and user flow.

  • Core SDK Interactions Remain the Same: Regardless of your UI toolkit, the fundamental interactions with the SDK persist:

    • Permissions: You must request and handle Camera, Microphone (and Bluetooth Connect for S+) permissions before initiating a call.

    • Video Renderers: You need to provide RTCMTLVideoView instances in your layout to display video.

    • Renderer Lifecycle: This is critical. You MUST correctly manage the lifecycle of RTCVideoRenderers:

      • Initialize them using UIView.

      • Attach them to local/remote streams using webRtcConnection.localVideoRenderer/remoteVideoRenderer.

    • Call State Display: Your UI must reflect the current call status (e.g., "Connecting...", "Waiting for agent...", "Connected", "On Hold") based on SDK events.

    • Call Controls: Implement UI elements (Buttons, Icons) for:

      • Muting/Unmuting Audio (webRtcConnection.setAudioEnabled(...))

      • Toggling Video (webRtcConnection.setVideoEnabled(...))

      • Switching Camera (webRtcConnection.switchCamera(...)): Switches between front ("f") and back ("b") cameras. If targetCamera is null, it toggles.

      • webRtcConnection.setFlashlightEnabled(state: !isFlashlightEnabled): Enabling and Disabling Flashlight (Toggle), works only with rear camera, returns current flashlight state, true when on, false when off, flashlight will be switched off automatically if cameraSwitched back to front/selfie, events will be triggered when flashlight state changes: newEvent.type == .FlashOn and newEvent.type == .FlashOff.
      • Hanging Up (videoCall.hangup())

    • Event Handling: Your UI layer must listen to SDK events (see Events and State Management) to update its state, navigate between screens, and handle errors.

2. Configuration (ECVSdkConfiguration)

The demo's EnvironmentsScreen.swift is designed for easily testing different backend environments. You will likely not need this screen in your production application.

Production Configuration Strategies:

  1. Fixed Configuration: If your app only ever connects to one backend environment (e.g., production), you can hardcode the ECVSdkConfiguration values when initializing the ECVSdk.

    // Example: Inside Application.onCreate or DI module
    let prodConfig = ECVSdkConfiguration(
        name: "My App Prod"
        serviceUrl = "https://your-prod.ecv.example.com",
        useQueueSocket = true // Assuming backend is v1.75+
        // ... other params if needed ...
    )
    
    let sdkInstance = ECVSdk("MyAppProdInstance", prodConfig)
    
  2. Dynamic Configuration: Fetch configuration details (like serviceUrl, webrtcUrl) from your own application backend before initializing the ECVSdk. This allows you to manage endpoints centrally without requiring app updates.

    • Your app fetches the config from your server.

    • Uses the fetched URLs to create the ECVSdkConfiguration.

    • Initializes ECVSdk.

Important: Ensure the useQueueSocket parameter in your chosen ECVSdkConfiguration matches the capabilities of your target ECV backend (false for v1.5, true recommended for v1.75+).

3. Call Data (DialCallParameters)

The demo uses selectableQueues in Build.swift to dynamically add dropdowns (queue selection) for call data based on the selected environment. This is specific to the demo's multi-environment setup.

In your application, you should populate DialCallParameters with relevant contextual data just before calling videoCall.start().

Example:

// User profile data
val userId = "user123"
val accountType = "Premium"
val intent = "Billing Inquiry"

// Target queue/skill
val targetQueue = "BillingSupport"

// Get SDK instance and create call
val videoCall = sdkInstance.createVideoCall()
// ... wait for GotRtcId ...

// Build parameters with app-specific data
val dialParams = DialCallParameters(
    callerName = "User $userId", // Or actual user name
    attributes = [
        "userId": userId,
        "accountType": accountType,
        "callIntent": intent
    ],
    acdAttributes = [
        "skill": "Billing" // Example ACD attribute
    ],
    queue = targetQueue, // Explicitly set the target queue
    additionalHeaders = ["Authorization": "Bearer \(getUserAuthToken())"] // Example auth
)

// Start the call
videoCall.start(dialParams)

// Navigate to Queue/Waiting screen

4. Event Handling Logic

While the demo shows how to subscribe to events, what you do in response is application-specific. Ensure your implementation correctly handles key events for:

  • Navigation: Trigger screen changes on AgentConnected, CallEnded, Error.

  • UI Updates: Reflect mute status, hold status, connection state changes visually.

  • Error Display: Show user-friendly messages when Error events occur.

  • Resource Cleanup: Use CallEnded as the definitive signal to clean up UI and potentially release call-specific resources.

5. Advanced Customization (Optional)

  • Custom Data Channel Messages: If your backend sends custom commands via the text data channel (beyond HOLD, FLASH, etc.), you can listen for the TextChannelMessage event and parse event.detail to implement custom application behavior.

  • ConnectParameters Tuning: For specific network environments or quality requirements, you might experiment with adjusting parameters in ConnectParameters, such as videoMaxBitrate, codecs, or ICE filtering modes. Consult WebRTC documentation for details on these advanced options.

  • Audio Management: While defaults (useSpeakerPhone, etc..) are provided in ConnectParameters, more complex audio routing might require deeper integration or customization beyond the scope of the basic SDK usage.

6. Permissions

Always follow iOS's best practices for requesting permissions:

  • Request permissions only when needed (just before initiating the call).

  • Clearly explain why the permissions (Camera, Microphone) are required.

  • Handle cases where the user denies permissions gracefully (e.g., disable calling functionality, provide instructions on how to enable them in settings).

7. Additional Modules

7.1 Call Transfer v1.0.18.45+

This module allows an active, ongoing call to be directed to a different agent/queue. The lifecycle of a transferred call is as follows:

  • The source call is dialed (call creation).

  • The source call waits in the queue.

  • The source call is connected.

  • The agent initiates the transfer.

  • The transfer can be to a queue or directly to a different agent.

  • A direct transfer to an agent can be canceled or rejected by the agent.

  • A "TransferPending" event is received via ECVModules?.onCallEvent.

    • When this event is received, a message such as "Please wait, your call will be transferred shortly" can be displayed to the user in the interface.

    • At any point after this event, a "TransferCancelled" event may arrive. When the cancel event is received, the displayed warning must be hidden. The call will continue as normal.

  • The agent may have placed the call on HOLD before the transfer, so the transfer message must be displayed in front of the HOLD interface.

  • When the transfer is complete, a "TransferCompleted" event will be received via ECVModules?.onCallEvent. The newEvent.data?["rtcId"] will contain the new RtcId value assigned to the transferred call.

  • The current call view and all associated resources are destroyed.

  • The user is sent back to the queue page, this time with the new RtcId.

  • The user waits in the queue as if a new call was initiated and is connected via an "AgentConnected" event when an agent is ready.

  • Notes: 
    • If transfer is detected in queue screen, a new call must be created, not getting existing one
      • // instead of
        let call = appState.sdk?.getVideoCall(rtcId: rtcId!) // without call transfer feature
        
        // we write this
        let call = isTransferred == true ? await appState.sdk?.createVideoCall(rtcId: rtcId) : appState.sdk?.getVideoCall(rtcId: rtcId!) // with call transfer feature
    • DialExRequest must not be called while waiting in-queue screen, thus, as written in Demo application, the following code must be called instead of await call?.start()
      • if(isTransferred == true){ // Call Transfer, we don't have to dial, just wait in queueSocket
          call?.isDialed = true
          _ = await call?.createQueueSocket()
          return
        }

An implementation example for this scenario is available in the demo application.

7.2 Remote agent flash triggering

This module will allow an agent to turn on/off customer phone's flashlight remotely.

Troubleshooting Guide

This guide provides solutions and debugging steps for common issues encountered while integrating or using the ECV iOS SDK.

Common Issues & Solutions

Build & Dependency Errors

  1. Error: "Could not find module 'ECV175_iOS_SDK'"

    • Cause: Xcode cannot locate the required framework files.

    • Solution:

      • Ensure the ECV SDK framework is properly embedded in your project.

      • Verify that the framework is listed in your target's "Frameworks, Libraries, and Embedded Content" section.

      • Check that the framework search paths in your project settings include the correct location.

      • Clean the build folder (Cmd + Shift + K) and rebuild the project.

  2. Error: "Undefined symbols for architecture"

    • Cause: Missing required dependencies or architecture mismatches.

    • Solution:

      • Ensure all required frameworks are properly linked in your project.

      • Verify that the deployment target and architectures match between your app and the SDK.

      • Check that you're using compatible versions of dependencies.

      • Clean and rebuild the project.

  3. Error: "Code signing failed"

    • Cause: Issues with code signing or provisioning profiles.

    • Solution:

      • Verify your Apple Developer account is active.

      • Check that your provisioning profile includes the necessary entitlements.

      • Ensure your signing certificate is valid and properly installed.

      • Update your provisioning profiles if needed.

Runtime Crashes

  1. Crash: "Thread 1: EXC_BAD_ACCESS"

    • Cause: Memory access issues, often related to WebRTC or native code.

    • Solution:

      • Ensure proper initialization of the SDK before use.

      • Check for proper memory management in your implementation.

      • Verify that all required permissions are granted.

      • Use the debugger to identify the exact point of crash.

  2. Crash: "Fatal error: Unexpectedly found nil while unwrapping an Optional value"

    • Cause: Accessing an SDK object before it's initialized or after it's been cleaned up.

    • Solution:

      • SDK Not Initialized: Ensure ECVSdk.shared.initialize(...) is called before accessing any SDK functionality.

      • VideoCall is Nil: Verify that the rtcId is valid and the call hasn't ended.

      • WebRTC Connection is Nil: Ensure videoCall.connect() was called successfully after the AgentConnected event.

      • View Controllers: Ensure proper view controller lifecycle management.

  3. Crash: Related to WebRTC or Media Handling

    • Cause: Improper lifecycle management of WebRTC components.

    • Solution:

      • Ensure proper cleanup of WebRTC resources in deinit or viewWillDisappear.

      • Verify camera and microphone permissions are granted.

      • Check for proper handling of background/foreground transitions.

      • Ensure proper thread handling for WebRTC operations.

  4. Crash: "Permission Denial"

    • Cause: Attempting to access camera or microphone without proper permissions.

    • Solution:

      • Implement proper permission request flow using AVCaptureDevice.requestAccess.

      • Check permission status before enabling call functionality.

      • Handle permission denial gracefully in the UI.

      • Verify Info.plist contains necessary permission descriptions.

Media Quality Issues

  1. Poor Video/Audio Quality (Lag, Freezing, Pixelation, Garbled Audio)

    • Debugging:

      • Network: Check network bandwidth, latency, and packet loss. Test on different networks (Wi-Fi vs Cellular).

      • Device Performance: Older devices might struggle with encoding/decoding at higher resolutions.

      • Configuration: Consider adjusting video resolution, frame rate, or bitrate settings.

      • WebRTC Stats: Enable WebRTC statistics collection for detailed performance metrics.

Specific Feature Issues

  1. Camera Switch Doesn't Work

    • Debugging:

      • Ensure webRTCConnection.switchCamera() is being called.

      • Check logs for errors during the switch attempt.

      • Verify the device has multiple cameras available.

      • Ensure proper camera initialization.

  2. Hold/Pickup Doesn't Mute/Unmute Automatically

    • Debugging:

      • Verify autoHandleHoldSideEffects is enabled in configuration.

      • Check if hold/pickup events are being received correctly.

      • Verify text channel connection status.

      • Check event handling implementation.

General Debugging Steps

  1. Check Console Logs: Use Xcode's console to monitor SDK logs. Filter by relevant tags and look for warnings and errors.

  2. Verify Configuration: Double-check ECVSdkConfiguration, DialCallParameters, and any custom parameters against your environment requirements.

  3. Check Network Connectivity: Verify the device can reach required endpoints. Test on different networks and check for any firewall issues.

  4. Simplify Implementation: Start with basic configuration and gradually add complexity to isolate issues.

  5. Isolate the Phase: Determine when the failure occurs: SDK initialization, dialing, queue, WebRTC connection, or media rendering.

  6. Consult Demo App: Compare your implementation with the provided demo source code, especially around event handling and view controller lifecycle management.

  7. Log All Events: Add logging to your event handling to track the call flow and identify missing or unexpected events.

Contacting Support

When contacting support, please provide:

  • SDK Version: (e.g., ECV-175-iOS-SDK-v1.2.3)

  • Device(s) Tested: iPhone/iPad model, iOS version

  • Backend Version: ECV v1.5 or v1.75+

  • Configuration: Your ECVSdkConfiguration, relevant DialCallParameters, and any custom parameters

  • Detailed Problem Description: Symptoms, reproduction steps, expected vs actual behavior

  • Relevant Console Logs: Capture logs during the issue, including SDK and application logs

  • Screenshots/Videos: If helpful to illustrate the problem