Skip to main content

1-to-1 call

This page explains the main 1-to-1 calling features, including how to place, answer, manage, and end a call in your app.

Key classes

NCCallEngine

NCCallEngine is the core class in Call SDK for Web. It manages call behavior on the client, including placing calls, answering, hanging up, controlling audio and video devices, and managing call logs.

MethodDescription
setCallEventsListenerRegister app-level events such as incoming calls, call end, participant state changes, and media type changes
setConnectionQualityListenerRegister a listener for call quality, including uplink and downlink quality and volume. See the API documentation for details.
setVideoViewBind video players for the users you want to watch, including the local user and remote users
removeVideoViewRemove the video player for a specific user when you no longer want to watch that user's video
playMediaPlay audio or video for a specific user. Before you play video for a user, call setVideoView first.
createVideoConfigBuilderCreate a video configuration builder
setVideoConfigApply the video configuration
createAudioConfigBuilderCreate an audio configuration builder
createAndroidPushConfigBuilderCreate an Android push configuration builder
setAudioConfigApply the audio configuration
startCallStart a call. calleeIds can be a single userId string or a userId array.
joinCallJoin an ongoing group call directly
inviteToCallInvite other users during a call
acceptCallAnswer a call
endCallHang up a call
requestChangeMediaTypeRequest a media type change during a call
cancelChangeMediaTypeCancel a pending media type change request
replyChangeMediaTypeReply to a media type change request
getCurrentCallSessionGet the current call
getOngoingCallLogsGet unfinished call logs from the server
getHistoryCallLogsQuery the current user's call history with pagination
deleteCallLogsForMeDelete call logs in batches by callId
deleteAllCallLogsForMeDelete all call logs
enableCameraTurn the camera on or off by passing true or false
switchCameraSwitch the camera
enableMicrophoneTurn the microphone on or off by passing true or false
getAudioInputDevicesGet the list of audio input devices, such as microphones
selectAudioInputDeviceSelect a microphone input device
getAudioOutputDevicesGet the list of audio output devices, such as speakers
selectAudioOutputDeviceSelect an audio output device
getVideoInputDevicesGet the camera (video input) device list
isMicrophoneEnabledCheck whether the local microphone is enabled
getVersionGet the SDK version

NCCallSession

NCCallSession represents a call and provides methods for reading call details.

MethodDescription
getCallId()Get the call ID
getCallState()Get the call state as NCCallState
getCallType()Get the call type as NCCallType
getMediaType()Get the call media type as NCCallMediaType
getParticipants()Get the participant list. Each item contains the participant user ID and the participant state as NCCallUserState.
getCallerUserId()Get the caller user ID
getInvitorUserId()Get the inviter user ID
getStartTime()Get the call start time
getDuration()Get the call duration

INCCallEventsListener

INCCallEventsListener provides callbacks for incoming calls, call state changes, and call logs.

CallbackDescription
onCallReceivedFired when the local user receives an incoming call. session is the current call session and extra is the extra data passed by the caller.
onCallEndedFired when a call ends. In a group call, a local hang-up does not always mean the entire call has ended. reason is the end reason.
onCallConnectedFired when the call is established. The SDK publishes media internally after this point.
onLocalCallLogReceivedFired when the local call log as INCCallLog is available
onServerCallLogReceivedFired when the server pushes a call log as INCCallLog
onMediaTypeChangeRequestReceivedFired when a media type change request arrives. This callback is supported only in 1-to-1 calls. The parameters include the requesting userId, the transactionId, and the target mediaType.
onMediaTypeChangeResultReceivedFired when the media type change result is available. This callback is supported only in 1-to-1 calls.
onRemoteCameraStateChangedFired when the remote camera state changes. disabled === true means the camera is off.
onRemoteMicrophoneStateChangedFired when the remote microphone state changes. disabled === true means the microphone is off.
onRemoteUserInvitedFired in group calls when remote users are invited to join
onRemoteUserStateChangedFired when a participant state changes during the call
onUserMediaAvailableFired when audio or video for a specific user is ready to play. Use it together with playMedia.
onStartTimeReceivedFired when the call start time is available. The payload includes callId, callType, and callStartTime.

Call flow

A 1-to-1 call in Call SDK is a call between two users. The flow is:

  1. The caller starts a call. The SDK creates audio and video resources for the caller. The callee receives onCallReceived.
  2. After the callee answers, the SDK creates audio and video resources for the callee.
  3. When the answer succeeds, the caller's and callee's media resources are published to Nexconn media servers.
  4. Nexconn signaling notifies both sides that the other user's media is available.
  5. Both sides call setVideoView to bind the video they want to watch. Audio is handled internally by the SDK.
  6. Both sides can call playMedia inside onUserMediaAvailable to play the target user's media. Always check both the user ID and the media type. Do not play locally captured audio on the same device, or you might cause echo or feedback.

For the caller, startCall returns a promise that resolves to { code: number, callId?: string }. For the callee, onCallReceived provides the NCCallSession object. Both sides can call getCurrentCallSession on the NCCallEngine instance to get the current call.

Prerequisites

  • Audio and video calling is enabled for your App Key.
  • You have initialized the Call SDK and obtained an NCCallEngine instance. In this document, ncCallEngine refers to that initialized instance.

Register the listener

Call SDK for Web provides INCCallEventsListener so your app can react to events from remote users and the server. For example, you can answer or reject incoming calls, accept or reject media type upgrade requests, or show the call duration after the call start time becomes available.

After you get the NCCallEngine instance, call setCallEventsListener():

typescript
// Register app-level call events.
ncCallEngine.setCallEventsListener({
/**
* Incoming call notification.
* @param session Current call session
* @param extra Extra data passed by the caller when the call was started
*/
async onCallReceived(session: NCCallSession, extra?: string) {
const callId = session.getCallId();
console.log(`Incoming call, callId: ${callId}`);
const { code } = await ncCallEngine.acceptCall(callId);
console.log(`Accept call result, code: ${code}`);
},

/**
* Fired when the call ends.
* In a group call, a local hang-up does not always mean the entire call has ended.
* @param session Current call session
* @param reason End reason
*/
onCallEnded(session: NCCallSession, reason: NCCallEndReason) {
console.log(`Call ended, callId: ${session.getCallId()}, reason: ${reason}`);
},

/**
* Fired when the call is established. The SDK publishes media internally after this point.
* @param session Current call session
*/
onCallConnected(session: NCCallSession) {
console.log(`Call connected, callId: ${session.getCallId()}`);
},

/**
* Fired when the local call log is available.
* @param record Call log. See `INCCallLog` in the API documentation.
*/
onLocalCallLogReceived(record: INCCallLog) {
console.log('Local call log', record);
},

/**
* Fired when a server call log is delivered.
* @param record Call log. See `INCCallLog` in the API documentation.
*/
onServerCallLogReceived(record: INCCallLog) {
console.log('Server call log', record);
},

/**
* Fired when a media type change request arrives. Supported only in 1-to-1 calls.
* @param userId User ID that requested the change
* @param transactionId Transaction ID for this request and response pair
* @param mediaType Target media type
*/
onMediaTypeChangeRequestReceived(
userId: string,
transactionId: string,
mediaType: NCCallMediaType,
) {
console.log(`Media type change request, userId: ${userId}, transactionId: ${transactionId}, mediaType: ${mediaType}`);
ncCallEngine.replyChangeMediaType(transactionId, true);
},

/**
* Fired when the media type change result is available. Supported only in 1-to-1 calls.
* @param info.userId Related user ID
* @param info.transactionId Transaction ID that matches the original request
* @param info.mediaType Negotiated media type
* @param info.result Change result
*/
onMediaTypeChangeResultReceived(info: {
userId: string;
transactionId: string;
mediaType: NCCallMediaType;
result: NCCallMediaTypeChangeResult;
}) {
console.log('Media type change result', info);
},

/**
* Fired when the remote camera state changes.
* @param callId Call ID
* @param userId Remote user ID
* @param disabled `true` means the camera is off
*/
onRemoteCameraStateChanged(callId: string, userId: string, disabled: boolean) {
console.log(`Remote camera state, callId: ${callId}, userId: ${userId}, disabled: ${disabled}`);
},

/**
* Fired when the remote microphone state changes.
* @param callId Call ID
* @param userId Remote user ID
* @param disabled `true` means the microphone is off
*/
onRemoteMicrophoneStateChanged(callId: string, userId: string, disabled: boolean) {
console.log(`Remote microphone state, callId: ${callId}, userId: ${userId}, disabled: ${disabled}`);
},

/**
* Fired in a group call when remote users are invited.
* @param inviteeUserList Invited user IDs
* @param inviterUserId User ID that sent the invitation
* @param callId Call ID
*/
onRemoteUserInvited(inviteeUserList: string[], inviterUserId: string, callId: string) {
console.log(`Remote users invited, callId: ${callId}, inviteeUserList: ${inviteeUserList}, inviterUserId: ${inviterUserId}`);
},

/**
* Fired when a participant state changes during the call.
* @param callId Call ID
* @param userId User whose state changed
* @param state New state. See `NCCallUserState`.
* @param reason Optional reason for the state change
*/
onRemoteUserStateChanged(
callId: string,
userId: string,
state: NCCallUserState,
reason?: NCCallEndReason,
) {
console.log(`Participant state changed, callId: ${callId}, userId: ${userId}, state: ${NCCallUserState[state]}, reason: ${reason}`);
},

/**
* Fired when audio or video for a specific user is ready to play.
* Do not play locally captured audio on the same device, or you might cause echo or feedback.
* @param userId User ID
* @param mediaType Media type
*/
onUserMediaAvailable(userId: string, mediaType: NCCallMediaType) {
ncCallEngine.playMedia(userId, mediaType);
},

/**
* Fired when the call start time becomes available.
* @param info.callId Call ID
* @param info.callType Call type. See `NCCallType`.
* @param info.callStartTime Call start timestamp determined by the server or engine
*/
onStartTimeReceived(info: { callId: string; callType: NCCallType; callStartTime: number }) {
console.log(`Call timer started, callId: ${info.callId}, startTime: ${info.callStartTime}`);
},
});
caution

If you do not register INCCallEventsListener, you cannot receive incoming call events such as onCallReceived. Register it as soon as possible after you initialize the Call SDK.

Add media playback elements

A video playback element is an HTMLVideoElement <video> that displays a video stream. To show local and remote video, add the following two elements to your page. The element with the ID local_video_element_id is for local video, and the element with the ID remote_video_element_id is for remote video. Audio playback uses an internal Audio object created by the SDK, so your app does not need to manage it.

html
<video id="local_video_element_id"></video>
html
<video id="remote_video_element_id"></video>

After these elements are ready, call ncCallEngine.setVideoView to bind the video elements for the users you want to watch. After you bind a video view for user A, the SDK notifies your app through onUserMediaAvailable when A's video becomes available. You can then call ncCallEngine.playMedia() to start playback. Do not play locally captured audio on the same device, or you might cause echo or feedback.

typescript
/**
* Set media players for the users you want to watch, including the local user and the remote user.
* @param list A list of media player bindings
* @param item.userId User ID
* @param item.videoElement A video element mounted in your page
* @param item.useTinyStream Whether to subscribe to the remote user's low-resolution stream
* @returns code Whether the call succeeded
*/
setVideoView(list: { userId: string; videoElement: HTMLVideoElement; useTinyStream?: boolean }[]): { code: number }

Set the local and remote video players before placing or answering the call:

typescript
ncCallEngine.setVideoView([
{
userId: '<local-userId>',
videoElement: document.getElementById('local_video_element_id') as HTMLVideoElement,
},
{
userId: '<remote-userId>',
videoElement: document.getElementById('remote_video_element_id') as HTMLVideoElement,
useTinyStream: true,
},
]);
tip

You must set the local video player before placing or answering a call. You can set the remote player's view in advance, or only after the remote user reaches NCCallUserState.ONCALL in onRemoteUserStateChanged. If you want the remote low-resolution stream, set useTinyStream to true.

Start a call

tip

The startCall method accepts optional pushConfig and extra parameters. pushConfig carries mobile push settings through INCCallPushConfig, including pushTitle, pushContent, and platform-specific push attributes. extra carries custom data that is passed through to the callee.

Call startCall with the callee user ID, the call type, and the media type. If the call starts successfully, the SDK returns a numeric code and, on success, a callId.

typescript
/**
* Start a call.
* @param calleeIds Callee user ID. For a one-to-one call, you can pass a single string.
* @param callType Call type: single or multi-party
* @param mediaType Call media type: audio or video
* @param pushConfig Optional mobile push configuration
* @param extra Optional passthrough data for the callee
* @returns callId Session ID created after the call starts successfully
*/
const { code, callId } = await ncCallEngine.startCall(
'<userId>',
NCCallType.SINGLE,
NCCallMediaType.VIDEO,
);

Answer a call

The callee must register INCCallEventsListener before the app can receive onCallReceived and other call events.

Use acceptCall to answer the call or endCall to reject it. If the call is answered, Call SDK establishes the media session automatically.

javascript
ncCallEngine.setCallEventsListener({
/**
* Incoming call notification.
* When a call comes in, you can answer or hang up.
* @param session Current call session
* @param extra Extra data passed by the caller when the call was started
*/
async onCallReceived(session: NCCallSession, extra?: string) {
const callId = session.getCallId();
const { code } = await ncCallEngine.acceptCall(callId);
console.log(`Accept call result, code: ${code}`);
}
});

Call SDK receives call events through the IM connection to Nexconn, which is established by NCEngine.connect(). If the network disconnects unexpectedly, the SDK reconnects automatically.

Manage the current call

Audio

During the call, both the caller and the callee can turn the microphone on or off by calling enableMicrophone with true or false. If one side changes the microphone state, the other side receives onRemoteMicrophoneStateChanged.

typescript
/**
* Turn the microphone on or off.
* @param enable `true` to turn it on, `false` to turn it off
* @description This call can fail during an active call if resource propagation fails and the remote side does not receive the local state update.
*/
async enableMicrophone(enable: boolean): Promise<{ code: number }>

/**
* Select the microphone input device.
* After the switch succeeds, `onUserMediaAvailable` receives the newly selected microphone stream,
* and you can call `ncCallEngine.playMedia()` to play it.
* @param microphoneId Microphone device ID from the browser. Call `getAudioInputDevices` to get the list.
* @description You can call this before the call or during the call.
*/
async selectAudioInputDevice(microphoneId: string): Promise<{ code: number }>

/**
* Select the audio output device, such as a speaker.
* @param deviceId Speaker device ID
*/
async selectAudioOutputDevice(deviceId: string): Promise<{ code: number }>

Video

During the call, both the caller and the callee can turn the camera on or off by calling enableCamera with true or false. If one side changes the camera state, the other side receives onRemoteCameraStateChanged.

typescript
/**
* Turn the camera on or off.
* @param enable `true` to turn it on, `false` to turn it off
* @description This call can fail during an active call if resource propagation fails and the remote side does not receive the local state update.
*/
async enableCamera(enable: boolean): Promise<{ code: number }>

/**
* Switch the camera.
* After the switch succeeds, `onUserMediaAvailable` receives the newly selected camera stream,
* and you can call `ncCallEngine.playMedia()` to display it.
* @param cameraId Camera device ID from the browser. Call `getVideoInputDevices` to get the list.
* @description You can call this before the call or during the call.
*/
async switchCamera(cameraId: string): Promise<{ code: number }>

Change the media type

Only 1-to-1 calls support media type changes during a call.

Call SDK defines NCCallMediaType.VIDEO for video calls and NCCallMediaType.AUDIO for audio calls. During a 1-to-1 call, either side can call requestChangeMediaType to request a media type change.

typescript
/**
* Request a media type change during the call.
* @param mediaType Target media type
* @returns transactionId Transaction ID used in later cancel or reply calls
*/
async requestChangeMediaType(
mediaType: NCCallMediaType.AUDIO | NCCallMediaType.VIDEO,
): Promise<{ code: number; transactionId?: string }>

The requesting side can also call cancelChangeMediaType to cancel the request.

typescript
/**
* Cancel a pending media type change request.
* @param transactionId Transaction ID returned by the original media type change request
*/
async cancelChangeMediaType(transactionId: string): Promise<{ code: number }>

The remote user receives the request through onMediaTypeChangeRequestReceived. After that, the remote side can call replyChangeMediaType on the NCCallEngine instance to accept or reject the change.

typescript
/**
* Reply to a media type change request.
* @param transactionId Transaction ID received in `onMediaTypeChangeRequestReceived`
*/
async replyChangeMediaType(transactionId: string, isAgree: boolean): Promise<{ code: number }>

The final result is delivered to both sides through onMediaTypeChangeResultReceived.

typescript
/**
* Media type change result. Supported only in 1-to-1 calls.
* @param info.userId Related user ID
* @param info.transactionId Transaction ID that uniquely identifies this request and response
* @param info.mediaType Final media type
* @param info.result Change result
* - The requester canceled the request
* - The responder rejected the request
* - The server allowed the media type change
*/
onMediaTypeChangeResultReceived(info: {
userId: string,
transactionId: string,
mediaType: NCCallMediaType,
result: NCCallMediaTypeChangeResult
}): void

End the call

Note

The endCall method accepts an optional pushConfig parameter. pushConfig carries mobile push settings through INCCallPushConfig, including pushTitle, pushContent, and platform-specific push attributes.

Use endCall to hang up an active call or reject an incoming call. Both the caller and the callee can call endCall(). If one side ends the call, the other side receives INCCallEventsListener.onCallEnded().

typescript
/**
* End a call.
* @param callId Optional. If omitted, the SDK ends the current primary session.
* If provided, it can end either the main call or a ringing call that has not been answered yet.
* A ringing call that has not been answered means a call that arrives while another call is already active.
*/
async endCall(callId?: string, pushConfig?: INCCallPushConfig): Promise<{ code: number }>

In a 1-to-1 call, if either side hangs up, the call ends for both sides and Nexconn stops billing immediately.

Answer an incoming call while already in a call

You can answer a new incoming call while another call is active. Because only one active call is allowed at a time, the SDK ends the current call internally before answering the new one.

Note

Call SDK does not support holding and resuming calls during another active call.

Use acceptCall to answer the incoming call. The new call becomes the current active call.

typescript
/**
* Answer a call.
* @param callId Call ID
*/
async acceptCall(callId: string, pushConfig?: INCCallPushConfig): Promise<{ code: number }>

Get call logs

Get call logs after the call ends

After the call ends, or after the user rejects the call, the engine can deliver the server-side call log through onServerCallLogReceived as INCCallLog. In one-to-one calls, the SDK can also deliver a locally constructed call log through onLocalCallLogReceived.

typescript
/** Server call log delivered as INCCallLog */
onServerCallLogReceived(record: INCCallLog): void

/** Local call log delivered as INCCallLog */
onLocalCallLogReceived(record: INCCallLog): void

Query server-side call logs

You can also call getHistoryCallLogs on the NCCallEngine instance to fetch the user's call history from the Nexconn server.

The order parameter of getHistoryCallLogs supports reverse-order queries. For example, when you page through recent calls, pass -1 for startTime on the first request. To continue, pass the returned syncTime as the next startTime.

typescript
/**
* Query the current user's call history with pagination.
* @param startTime Start timestamp. Pass -1 for the first request.
* @param count Number of records per request. Pass a positive integer.
* @param order Sort order. The default is ascending.
* 0 means ascending and queries after `startTime`.
* 1 means descending and queries before `startTime`.
* @returns result.hasMore Whether more call logs are available
* @returns result.callLogList Call log list as INCCallLog[]
* @returns result.syncTime When hasMore is true, pass this value as the next `startTime`
*/
async getHistoryCallLogs(startTime: number, count: number, order: 0 | 1 = 0): Promise<{ code: number; result?: INCCallLogInfo }>

Manage audio and video configuration

Call SDK uses the browser's default camera and microphone for the local user. The default video resolution is W640_H480, and the default frame rate is FPS_15. You can build custom video and audio configurations with NCVideoConfigBuilder and NCAudioConfigBuilder, then apply them through setVideoConfig and setAudioConfig.

Set the local video capture configuration

Create a NCVideoConfigBuilder first. Use it to configure the video resolution, frame rate, camera ID, and video bitrate.

typescript
import { NCCallVideoResolution, NCCallVideoFrameRate } from '@nexconn/call';
/**
* Create a video configuration builder.
* @returns NCVideoConfigBuilder Used to configure video capture settings such as
* camera ID, frame rate, and resolution. Call build(), then apply the result through setVideoConfig().
*/
const videoConfigBuilder = ncCallEngine.createVideoConfigBuilder();

// Set the resolution.
videoConfigBuilder
.setVideoResolution(NCCallVideoResolution.W640_H360)
.setFrameRate(NCCallVideoFrameRate.FPS_24)
// Call `ncCallEngine.getVideoInputDevices()` to get available values.
.setDefaultCameraId(cameraId)
.setBitrate(min, max);

// Build the video configuration.
const videoConfig = videoConfigBuilder.build();

Apply the video configuration:

typescript
/**
* Apply the video configuration.
* It takes effect the next time media capture starts, such as after you switch the camera
* or establish a new call.
*/
ncCallEngine.setVideoConfig(videoConfig);

Change the local audio capture configuration

Create a NCAudioConfigBuilder first. Use it to configure the microphone ID, audio sample rate, and audio bitrate.

Build the audio configuration:

typescript
/**
* Create an audio configuration builder.
* @returns NCAudioConfigBuilder Used to configure audio capture settings such as the microphone ID.
* Call build(), then apply the result through setAudioConfig().
*/
const audioConfigBuilder = ncCallEngine.createAudioConfigBuilder();

audioConfigBuilder
// Call `ncCallEngine.getAudioInputDevices()` to get available values.
.setDefaultMicrophoneId(microphoneId)
.setBitrate(min, max)
.setSampleRate(sampleRate);

// Build the audio configuration.
const audioConfig = audioConfigBuilder.build();

Apply the audio configuration:

typescript
/**
* Apply the audio configuration.
* It takes effect the next time media capture starts, such as when a new call is established.
*/
ncCallEngine.setAudioConfig(audioConfig);

Manage call quality

Users can receive notifications when call quality changes so they can check their network connection and reduce call interruptions.

Set IConnectionQualityListener

To monitor call quality changes, register IConnectionQualityListener. Call setConnectionQualityListener() on the NCCallEngine instance.

typescript
/**
* Register a call quality listener, including send and receive quality and volume.
* @param listener Type `IConnectionQualityListener`. See the API documentation for definitions and callback frequency.
*/
setConnectionQualityListener(listener: IConnectionQualityListener): void

IConnectionQualityListener includes the following callbacks:

CallbackDescription
onSendQualityChangedThe local send link quality changed
onReceiveQualityChangedThe receive link quality changed. Data can include multiple users.
onAudioVolumeChangedThe call volume changed