Home
/
Calls
/
iOS

Direct call

This page explains key functions of direct call consisting of how to make, receive, handle, and end a call from your app.


DirectCall object

This object represents a 1-to-1 call. It has the following properties and functions:

Properties

NameDescription

callId

Type: String
A unique identifier for a direct call.

caller

Type: DirectCallUser
The user’s role that initiates the call.

callee

Type: DirectCallUser
The user’s role that receives the call.

callLog

Type: DirectCallLog
The history of the call. A value of nil indicates that the call is ongoing. The value after the call has ended can be different due to the synchronization with Sendbird server. This object's isFromServer property indicates whether the call log is from the server.

isEnded

Type: Bool
Indicates whether the call is ended.

isVideoCall

Type: Bool
Indicates whether the call is a video call. The call should be configured with the video call option when initiating it.

endResult

Type: DirectCallEndResult
The result of how the call has ended.

myRole

Type: DirectCall.UserRole
The role of the local user in the call.

localUser

Type: DirectCallUser
The local user of the call.

isLocalAudioEnabled

Type: Bool
Indicates whether the local user has enabled their audio.

isLocalVideoEnabled

Type: Bool
Indicates whether the local user has enabled their video.

localVideoView

Type: SendBirdVideoView
The SendBirdVideoView object that represents the view which renders the local user’s video. Update this property by the updateLocalVideo(_:) method.

remoteUser

Type: DirectCallUser
The remote user of the call.

isRemoteAudioEnabled

Type: Bool
Indicates whether the remote user has enabled their audio.

isRemoteVideoViewEnabled

Type: Bool
Indicates whether the remote user has enabled their video.

remoteVideoView

Type: SendBirdVideoView
The SendBirdVideoView object that represents the view which renders the remote user’s video. Update this property by the updateRemoteVideo(_:) method.

startedAt

Type: Int64
The timestamp of when the call was dialed, in Unix milliseconds. A value of 0 indicates that the call hasn’t started yet.

endAt

Type: Int64
The timestamp of when the call was ended, in Unix milliseconds. A value of 0 indicates that the call hasn’t ended yet.

duration

Type: Int64
The period from the call was accepted to when the call was ended, in Unix milliseconds. A value of 0 indicates that the call hasn’t started yet.

customItems

Type: [String:String]
A dictionary of key-value items to store customized data for the call.

Methods

NameDescription

accept(with: AcceptParams)

Accepts an incoming call.

end()

Ends the call. The DirectCallDelegate.didEnd(call:) delegate method will be called after receiving a success callback from Sendbird server. This delegate is also called when the remote user has ended the call.

muteMicrophone()

Mutes the local user's audio. The remote user will be notified through the DirectCallDelegate.didRemoteAudioSettingsChange() delegate method. If the remote user changes audio settings, the local user will be notified through the same delegate method.

unmuteMicrophone()

Unmutes the local user's audio. The remote user will be notified through the DirectCallDelegate.didRemoteAudioSettingsChange() delegate method. If the remote user changes audio settings, the local user will be notified through the same delegate method.

startVideo()

Starts the local user's video. The local user will be notified through the DirectCallDelegate.didRemoteVideoSettingsChange() delegate method if the remote user changes video settings.

stopVideo()

Stops the local user's video. The local user will be notified through the DirectCallDelegate.didRemoteVideoSettingsChange() delegate method if the remote user changes video settings.

updateLocalVideoView(_:)

Updates the SendBirdVideoView object of the local user. Another view object can be used to render the local video.

updateRemoteVideoView(_:)

Updates the SendBirdVideoView object of the remote user. Another view object can be used to render the remote video.

updateCustomItems(customItems:completionHandler:)

Updates the custom items of the call.

deleteCustomItems(customItemsKeys:completionHandler:)

Deletes the custom items of the call.

deleteAllCustomItems(completionHandler:)

Delete all custom items of the call.


DirectCallDelegate event delegate

MethodDescription

didConnect(call:)

Media devices (for example, microphone and speakers) between the caller and callee are connected and can start the call.

didEstablish(call:)

The callee has accepted the call by using the directCall.accept() method, but the media devices of caller and callee are not yet connected.

didStartReconnecting(call:)

The call begins attempting to reconnect to Sendbird server after losing connection.

didReconnect(call:)

The call successfully reconnects to Sendbird server.

didEnd(call:)

One of the parties ends a call by using the directCall.end() method and a call is ended due to other reasons such as decline or connection lost.

didAudioDeviceChange(call:session:previousRoute:reason:)

The audio device has been changed. It provides information about the audio session, previous audio route, and change of reason.

didRemoteAudioSettingsChange(call:)

The remote user changes audio settings.

didRemoteVideoSettingsChange(call:)

The remote user changes video settings.

didRemoteRecordingStatusChange(Call:)

The remote user has changed their recording status.

didUpdateCustomItems(call:updatedKeys:)

The custom items of the call that match the updatedKeys parameter are updated.

didDeleteCustomItems(call:deletedKeys:)

The custom items of the call that match the deletedKeys parameter are deleted.


Make a call

Initiate a call by providing the callee’s user ID into the SendBirdCall.dial() method. Use the CallOptions object to choose a call’s initial settings.

Light Color Skin
Copy
let params = DialParams(calleeId: CALLEE_ID, callOptions: CallOptions())

let directCall = SendBirdCall.dial(with: params) { directCall, error in
    // The call has been created successfully
}

directCall.delegate = self

Configure video settings

In a video call, configure the video view settings prior to making the call so that the transition from a voice call to a video call will be seamless. Create SendBirdVideoView instances to render local and remote video streams as shown below:

Light Color Skin
Copy
let localSBView = SendBirdVideoView(frame: self.localVideoView?.frame ?? CGRect.zero)
let remoteSBView = SendBirdVideoView(frame: self.remoteVideoView?.frame ?? CGRect.zero)

// When making a call or accepting an incoming call.
let callOptions = CallOptions(
            isAudioEnabled: true,
            isVideoEnabled: true,
            localVideoView: localSBVideoView,
            remoteVideoView: remoteSBVideoView)

// Or when updating local/remote view
self.call.updateLocalVideoView(localSBView)
self.call.updateRemoteVideoView(remoteSBView)

Receive a call

To receive an incoming call, a SendBirdCallDelegate event delegate should already be registered in the callee’s client app. Accept or decline the call using the directCall.accept() or the directCall.end() method. If the call is accepted, a media session will automatically be established by the Calls SDK.

Before accepting the call, the call-specific DirectCallDelegate event delegate must be added to the call object. It enables the callee’s app to react to events during the call through its delegate methods.

Light Color Skin
Copy
class MyClass: SendBirdCallDelegate {
    func didStartRinging(_ call: DirectCall) {
        call.delegate = self
    }
}

The callee’s client app receives an incoming call through either the established connection with Sendbird server or PushKit if the app uses CallKit. To use the Calls SDK in the callee’s client app, the SendBirdCall instance must deliver received PushKit messages to the Calls SDK.

Light Color Skin
Copy
class AppDelegate: PKPushRegistryDelegate {
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        SendBirdCall.pushRegistry(registry, didReceiveIncomingPushWith: payload, for: type)
    }
}

If a client app has CallKit implemented and propagates PushKit messages through the PKPushRegistryDelegate’s pushRegistry(:didReceiveIncomingPushWith:for:completion) method, incoming calls displayed by PushKit messages should be delivered to the didStartRinging delegate method as shown below:

Light Color Skin
Copy
func didStartRinging(_ call: DirectCall) {
    // IMPORTANT: An incoming call should be delivered to this delegate method when you receive a PushKit push message for the call.
    let provider = CXProvider(configuration: CXProviderConfiguration)
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .generic, value: CALLER_ID)
    provider.reportNewIncomingCall(with: uuid, update: update) { (error) in
        completion()
    }
}

Handle a current call

Audio

During a current call, both the caller and callee’s audio can be muted or unmuted by the directCall.muteMicrophone() or directCall.unmuteMicrophone() method. If one party changes audio settings, the other party receives an event callback through the DirectCallDelegate.didRemoteAudioSettingsChange(_:) delegate method.

Light Color Skin
Copy
// Mute my microphone
call.muteMicrophone()
// Unmute my microphone
call.unmuteMicrophone()
class MyClass: SendBirdCallDelegate {
    func didRemoteAudioSettingsChange(_ call: DirectCall) {
        if (call.isRemoteAudioEnabled) {
            // The remote user has been unmuted.
            // Update UI components accordingly
        } else {
            // The remote user has been muted.
            // Update UI components accordingly
        }
    }
}

Video

During a current call, both the caller and callee’s video can be enabled or disabled by the directCall.startVideo() or directCall.stopVideo() method. If one party changes audio settings, the other party receives an event callback through the DirectCallDelegate.didRemoteVideoSettingsChange(_:) delegate method.

Light Color Skin
Copy
// Start my local video
call.startVideo()
// Stop my local video
call.stopVideo()

// Receive the event
class MyClass: DirectCallDelegate {
    func didRemoteVideoSettingsChange(_ call: DirectCall) {
        if (call.isRemoteVideoEnabled) {
            // The remote user has started video.
        } else {
            // The remote user has ended video.
        }
    }
}

End a call

The directCall.end() method ends a current call of either the caller or callee’s side. If one party ends a current call, the other party receives an event callback through the DirectCallDelegate.didEnd() method.

Light Color Skin
Copy
// End a call
call.end()

// Receiving the end event
class MyClass: DirectCallDelegate {
    func didEnd(_ call: DirectCall) {
        // Update UI components accordingly
    }

    ...
}

Manage custom items

With custom items, you can store additional information to a call in addition to default values in the DirectCall object. These key-value custom items are delivered as a [String: String] dictionary and can be updated during the call. Examples of items that could be included in the call are customer service, refund, or inquiry for better user experience.

Add

Custom items can be added to a call either by a caller or a callee. When dialing, the caller can add a [String: String] to the customItems of DialParam. The default value of customItems is an empty dictionary.

Light Color Skin
Copy
let customItemsToAdd = ["key1": "value1", "key2": "value2"]
call.updateCustomItems(customItems: customItemsToAdd) { (addedCustomItems, addedCustomItemKeys, error) in
    // Handle added custom items.
}

Update and delete

During a call, custom items can be modified by directly updating or deleting custom items of a given call. You can use directCall.updateCustomItems(:completionHandler:) to update current custom items with new custom items. If keys for the new custom items don't exist, new custom items will be added to the existing list of items. Otherwise, existing items will be replaced with new custom items.

You can modify custom items without directly referring to the DirectCall object. The custom items of the call from the SendBirdCall can also be modified by calling the same set of methods with an additional callId parameter. If a call with the corresponding callId exists, the SendBirdCall will update the custom items of that call.

You can delete a specific custom item with its given key by using directCall.deleteCustomItems(:completionHandler:) or delete all the items associated with the call by using directCall.deleteAllCustomItems(:). Through a completion handler, you will receive the updated custom items, a list of keys of the modified custom items, and an error from Sendbird server.

Light Color Skin
Copy
let customItemsToModify = ["key1": "value3", "key2": "value4"]
call.updateCustomItems(customItems: customItemsToModify) { (modifiedCustomItems, modifiedCustomItemKeys, error) in
    // Handle updated custom items.
}

let customItemKeysToDelete = ["key1", "key2"]
call.deleteCustomItems(customItemKeys: customItemKeysToDelete) { (deletedCustomItems, deletedCustomItemKeys, error) in
    // Handle deleted custom items.
}

call.deleteAllCustomItems { (deletedCustomItems, deletedCustomItemKeys, error) in
    // Handle deleted custom items.
}

Receive events

To receive events from Sendbird server when custom items are modified, you can implement didUpdateCustomItems() and didDeleteCustomItems() from the DirectCallDelegate. Events contain the DirectCall object of changed custom items and updatedKeys or deletedKeys. Custom items can always be modified, however these events will only be delivered if the call is ongoing. If the call ends, events are not delivered to the DirectCallDelegate. You can always access modified custom items even after the call ends with the Calls API or by using the directCall.customItems.

Light Color Skin
Copy
class MyClass: DirectCallDelegate {
    ...

    func didUpdateCustomItems(call: DirectCall, updatedKeys: [String]) {
        // Handle updated custom items with 'call.customItems' and 'updatedKeys'.
    }

    func didDeleteCustomItems(call: DirectCall, deletedKeys: [String]) {
        // Handle deleted custom items with 'call.customItems' and 'deletedKeys'.
    }
    ...

}

Retrieve call information

One party’s information can be retrieved through the directCall.localUser property while the other party’s information through the directCall.remoteUser property.


Retrieve call history

A user’s call history can be retrieved using the next() method of a DirectCallLogListQuery instance which returns a list of call objects.

Light Color Skin
Copy
let params = DirectCallLogListQuery.Params()
params.myRole = .caller     // Filter for current user’s role in the call
params.endResultsArray = [.cancel, .decline, .complete]     // Filter for end results of the call
params.limit = 25       // Number of call logs to be returned at once.
let listQuery = SendBirdCall.createDirectCallLogListQuery(with: params)

if listQuery.hasNext {
    listQuery.next() { [weak listQuery] (callLogs, error) in
        // The 'listQuery.next()' can be called once more to fetch more call logs.
    }
}

The call log can be immediately obtained after a call has ended. However, in the caller’s case, only the local log will be retrieved unless the sync has been made with the server. If you want to check whether a call log is synchronized with the server or not, use the directCallLog.isFromServer. To retrieve call history from the server instead of the local log, use the SendBirdCall.DirectCallLogListQuery.

Light Color Skin
Copy
class MyClass: DirectCallDelegate {
    ...

    func didEnd(_ call: DirectCall) {
        ...

        let callLog = call.callLog
        // Appropriately add this callLog object to the callLog list.
    }
}

Select video output

Users can select a device for video output during a video call. The list of available video devices can be accessed by the DirectCall.availableVideoDevices() method. In order to change the current video output device to one of the other available video devices, the DirectCall.selectVideoDevice(completionHandler:) method should be called.

Light Color Skin
Copy
let availableCapturers = self.call.availableVideoDevices
guard let oppositeCamera = availableCapturers.first(where: { $0.position != self.call.currentVideoDevice?.position }) else {
    return
}
self.call.selectVideoDevice(oppositeCamera) { (error) in
    // Current video device has been changed to the `oppositeCamera` with error.
}

To turn on the switch between the front and back cameras function, use the switchCamera(completionHandler:) method.

Light Color Skin
Copy
self.call.switchCamera { (error)
    // Switching between the front and back cameras function has been turned on.
}

Capture video views

During a video call, the caller and callee can capture the images of their streaming video by using either the captureLocalVideoView() or captureRemoteVideoView() methods when needed.

Local video view

Light Color Skin
Copy
self.call.captureLocalVideoView { [weak self] (image, error) in
    guard let image = image, error == nil else {
        // Handle error.
        return
    }

    self?.localImage = image
    // Implement code for handling a successfully-captured image.
}

Parameters

NameDescription

completionHandler

Type: CaptureVideoViewHandler
A callback handler to receive the result of video streaming capture images. The result is delivered through either image or error parameter. The image indicates a captured image of the video view.

Remote video view

Light Color Skin
Copy
self.call.captureRemoteVideoView { [weak self] (image, error) in
    guard let image = image, error == nil else {
        // Handle error.
        return
    }

    self?.remoteImage = image
    // Implement code for handling a successfully-captured image.
}

Parameters

NameDescription

completionHandler

Type: CaptureVideoViewHandler
A callback handler to receive the result of video streaming capture images. The result is delivered through either image or error parameter. The image indicates a captured image of the video view.

Note: For errors that may occur when capturing video views, see the Error codes page.


Record audio and video

When making a direct call with Sendbird Calls, audio and video recordings for both local and remote users are available. The recorded file will be saved on the user’s local file storage and users can transfer or process the file.

Only one ongoing recording session is allowed, which means that the current recording session must be stopped in order to start another recording. However, several sessions can be recorded throughout the call, thus multiple recording files created from one call.

The SendBirdCall currently supports five recording types:

Recording types

TypeDescription

remoteAudioAndVideo

An option to record the video and audio of the remote user.

remoteAudioOnly

An option to record the audio of the remote user.

localRemoteAudios

An option to record both audios of the local and remote users.

localAudioRemoteAudioAndVideo

An option to record both audios of the local and remote users, and the video of the remote user.

localAudioAndVideoRemoteAudio

An option to record both audios of the local and remote users, and the video of the local user.

Start recording a call

Start recording a call using the directCall.startRecording() method. You can customize the type and the name of the recording as well as the output path where a recorded file will be saved with a RecordingOptions object.

If the name of a file isn't specified for the filename parameter in the method, the recorded file’s name will follow the default pattern of {recording_type}_{call_id}_{timestamp}.

Light Color Skin
Copy
let options = RecordingOptions(type: .remoteAudioAndVideo, directoryPath: OUTPUT_PATH, fileName: FILE_NAME)
// Record remote user’s audio and video, with output URL of `OUTPUT_PATH/FILE_NAME.mp4`

call.startRecording(options: options) { (recordingId, error) in
    ...
}

A recordingId is delivered through the completion handler of the directCall.startRecording() method when the recording starts successfully. Make sure to save the recordingId to stop the recording session. If the recordingId is invalid or missing when the directCall.stopRecording() method is called, you can’t stop the recording session when needed.

Note: The SendBirdCall doesn’t check for file read and write permissions or any other permissions related to the media. Make sure that the application can write at the specified destination folder before starting a recording session.

Stop recording a call

Stop recording a call using the directCall.stopRecording() method with the recordingId received from the completion handler of the directCall.startRecording() method. If a recording session isn’t stopped by the time the call has ended, the recording session automatically ends as well.

Light Color Skin
Copy
call.stopRecording(recordingId: recordingId)
// Stop a recording session with the given ‘recordingId’.
// If the provided ‘recordingId’ is invalid, the request will be ignored.

After the recording is finished, the SendBirdRecordingDelegate.didSaveRecording() event delegate method will be called.

Receive events

In order to receive events about the completion of the recordings, add a device-specific SendBirdRecordingDelegate by using the SendBirdCall.addRecordingDelegate(:) delegate method. Once the event delegate is added, your app can handle the following two events as shown below:

Light Color Skin
Copy
SendBirdCall.addRecordingDelegate(self, identifier: UNIQUE_ID)

class MyClass: SendBirdRecordingDelegate {
    ...

    func didSaveRecording(call: DirectCall, recordingId: String, options: RecordingOptions, outputURL: URL) {
        // Recording was successfully saved to ‘outputURL’.
    }

    func didFailToSaveRecording(call: DirectCall, recordingId: String, error: SBCError) {
        // Recording wasn’t saved due to an ‘error’.
    }
}

You can remove the device-specific SendBirdRecordingDelegate as shown below:

Light Color Skin
Copy
SendBirdCall.removeRecordingDelegate(identifier: UNIQUE_ID)
// Remove a recording delegate with the specified identifier.

SendBirdCall.removeAllRecordingDelegates()
// Remove all recording delegates from the application.

Note: The SendBirdRecordingDelegate.didFailToSaveRecording() event delegate method won’t be called if errors occur at the start of recording sessions after calling the directCall.startRecording() method.

Recorded files

While streaming audio or video is being recorded, it is transcoded almost simultaneously to convert the stream into an MP4 file regardless of the recording option. After your recording is done, it typically takes less than a second to save the recording as an MP4 file.

Recorded videos will have a fixed frame size of 1280x720 pixels. Video recording uses around 20 MB of data per minute, but this may vary depending on the type of the recording content.

The SendBirdCall doesn’t handle anything related to managing recorded files. If there isn’t enough storage to save the recorded file, SendBirdRecordingDelegate.didFailToSaveRecording will be called.

Recording notifications

During a voice or video call, users can notify each other if the call is being recorded.

If a user starts or stops local recording on their device, the other user will be able to receive an event callback through the DirectCallDelegate.didRemoteRecordingStatusChange(_ call: DirectCall) delegate method. Users can also check the recording status of the other user with DirectCall.remoteRecordingStatus.

Light Color Skin
Copy
// Recording status of the local user
call.localRecordingStatus
// Recording status of the remote user
call.remoteRecordingStatus

class MyClass: DirectCallDelegate {
    func didRemoteRecordingStatusChange(_ call: DirectCall) {
        // When the remote user is recording the call, call.remoteRecordingStatus == .recording
        // When the remote user has finished recording the call, call.remoteRecordingStatus == .none
    }
}

Share screen

During a video call, both the caller and callee can share their screens with each other by using Apple’s ReplayKit.

Start screen share

Light Color Skin
Copy
let recorder = RPScreenRecorder.shared()

self.call.startScreenShare { (bufferHandler, error) in
    guard error == nil else { return }

    recorder.startCapture { (buffer, bufferType, error) in
        bufferHandler?(buffer, error)
    } completionHandler: { (error) in
        Guard error == nil else { return } // Handle error
        print("Successfully started screen share.")
    }
}

When the startScreenShare method is called, both the local user's localVideoView and the remote user’s remoteVideoView will be replaced with the screen share view. Currently, displaying the local camera view and the local screen share view together is not supported. If RPScreenRecorder.startCapture is invoked, the operating system will automatically ask the user to confirm the permission to record the screen.

Note: There is a known issue in iOS that if the user is recording the remote video view while the remote user is sharing the screen, it will result in a distorted video view.

Stop screen share

You can stop sharing your screen and return to the video view as shown below:

Light Color Skin
Copy
let recorder = RPScreenRecorder.shared()
recorder.stopCapture { (error) in
    self.call.stopScreenShare()
}

Add sound effects

You can use different sound effects to enhance the user experience for events that take place while using Sendbird Calls.

To add sound effects, use the SendBirdCall.addDirectCallSound(_:forType:) method for the following events: dialing, ringing, reconnecting, and reconnected. Remember to set sound effects before the mentioned events occur. To remove sound effects, use the SendBirdCall.removeDirectCallSound(_:forType:) method.

To set the dialing sound effect in silent mode, use the setDirectCallDialingSoundOnWhenSilentMode(isEnabled:) method.

Light Color Skin
Copy
// Play on a caller’s side when making a call.
SendBirdCall.addDirectCallSound("dialing.mp3", forType: .dialing)

// Set up dialing sound when silent mode.
SendBirdCall.setDirectCallDialingSoundOnWhenSilentMode(isEnabled: true)

// Play on a callee’s side when receiving a call.
SendBirdCall.addDirectCallSound("ringing.mp3", forType: .ringing)

// Play when a connection is lost, but the SDK immediately attempts to reconnect.
SendBirdCall.addDirectCallSound("reconnecting.mp3", forType: .reconnecting)

// Play when the connection is re-established.
SendBirdCall.addDirectCallSound("reconnected.mp3", forType: .reconnected)

If you’re using Apple’s CallKit framework, you should use CXProviderConfiguration.ringtoneSound instead to add sound effects as ringtones like the following:

Light Color Skin
Copy
let configuration = CXProviderConfiguration()
configuration.ringtoneSound = "ringing.mp3"
...

For more information about sound effects, refer to our Calls SDK GitHub repository's README.