Implement incoming and outgoing call notifications with accept/reject functionality. Ringing enables real-time call signaling between users, allowing them to initiate calls and respond to incoming call requests.
Ringing functionality requires the CometChat Chat SDK to be integrated alongside the Calls SDK. The Chat SDK handles call signaling (initiating, accepting, rejecting calls), while the Calls SDK manages the actual call session.
How Ringing Works
The ringing flow involves two SDKs working together:
- Chat SDK - Handles call signaling (initiate, accept, reject, cancel)
- Calls SDK - Manages the actual call session once accepted
Initiate a Call
Use the Chat SDK to initiate a call to a user or group:
let receiverID = "USER_ID"
let receiverType: CometChat.ReceiverType = .user
let callType: CometChat.CallType = .video
let call = Call(receiverId: receiverID, callType: callType, receiverType: receiverType)
CometChat.initiateCall(call: call, onSuccess: { call in
print("Call initiated: \(call?.sessionID ?? "")")
// Show outgoing call UI
}, onError: { error in
print("Call initiation failed: \(error?.errorDescription ?? "")")
})
NSString *receiverID = @"USER_ID";
CometChatReceiverType receiverType = CometChatReceiverTypeUser;
CometChatCallType callType = CometChatCallTypeVideo;
Call *call = [[Call alloc] initWithReceiverId:receiverID callType:callType receiverType:receiverType];
[CometChat initiateCallWithCall:call onSuccess:^(Call * call) {
NSLog(@"Call initiated: %@", call.sessionID);
// Show outgoing call UI
} onError:^(CometChatException * error) {
NSLog(@"Call initiation failed: %@", error.errorDescription);
}];
| Parameter | Type | Description |
|---|
receiverID | String | UID of the user or GUID of the group to call |
receiverType | ReceiverType | .user or .group |
callType | CallType | .video or .audio |
Call Timeout
By default, if the receiver does not answer within 45 seconds, the call is automatically marked as unanswered and the caller receives the onOutgoingCallRejected callback. You can customize this duration by passing a timeout parameter (in seconds) when initiating the call.
let receiverID = "USER_ID"
let receiverType: CometChat.ReceiverType = .user
let callType: CometChat.CallType = .video
let call = Call(receiverId: receiverID, callType: callType, receiverType: receiverType)
// Set a custom timeout of 30 seconds
CometChat.initiateCall(call: call, timeout: 30, onSuccess: { call in
print("Call initiated: \(call?.sessionID ?? "")")
}, onError: { error in
print("Call initiation failed: \(error?.errorDescription ?? "")")
})
NSString *receiverID = @"USER_ID";
CometChatReceiverType receiverType = CometChatReceiverTypeUser;
CometChatCallType callType = CometChatCallTypeVideo;
Call *call = [[Call alloc] initWithReceiverId:receiverID callType:callType receiverType:receiverType];
// Set a custom timeout of 30 seconds
[CometChat initiateCallWithCall:call timeout:30 onSuccess:^(Call * call) {
NSLog(@"Call initiated: %@", call.sessionID);
} onError:^(CometChatException * error) {
NSLog(@"Call initiation failed: %@", error.errorDescription);
}];
| Parameter | Type | Description |
|---|
call | Call | The call object with receiver and call type details |
timeout | Double | Time in seconds to wait before marking the call as unanswered. Defaults to 45. Non-integer values are truncated (e.g., 30.7 becomes 30). Values ≤ 0 fall back to the default 45 seconds. |
When the timeout expires without the call being accepted, the SDK automatically:
- Sends an
unanswered call status to the server.
- Triggers the
onOutgoingCallRejected callback on the caller’s side with the call status set to .unanswered.
- Cleans up the call session.
If the call is accepted, rejected, or cancelled before the timeout expires, the timer is automatically stopped and the timeout has no effect.
Listen for Incoming Calls
Register a call listener to receive incoming call notifications:
let listenerID = "UNIQUE_LISTENER_ID"
CometChat.addCallListener(listenerID, self)
// Implement CometChatCallDelegate
extension CallViewController: CometChatCallDelegate {
func onIncomingCallReceived(incomingCall: Call?, error: CometChatException?) {
guard let call = incomingCall else { return }
print("Incoming call from: \(call.callInitiator?.name ?? "")")
// Show incoming call UI with accept/reject options
}
func onOutgoingCallAccepted(acceptedCall: Call?, error: CometChatException?) {
guard let call = acceptedCall else { return }
print("Call accepted, joining session...")
joinCallSession(sessionId: call.sessionID ?? "")
}
func onOutgoingCallRejected(rejectedCall: Call?, error: CometChatException?) {
print("Call rejected")
// Dismiss outgoing call UI
}
func onIncomingCallCancelled(cancelledCall: Call?, error: CometChatException?) {
print("Incoming call cancelled")
// Dismiss incoming call UI
}
func onCallEndedMessageReceived(endedCall: Call?, error: CometChatException?) {
print("Call ended")
}
}
NSString *listenerID = @"UNIQUE_LISTENER_ID";
[CometChat addCallListener:listenerID delegate:self];
// Implement CometChatCallDelegate
- (void)onIncomingCallReceivedWithIncomingCall:(Call *)incomingCall error:(CometChatException *)error {
NSLog(@"Incoming call from: %@", incomingCall.callInitiator.name);
// Show incoming call UI with accept/reject options
}
- (void)onOutgoingCallAcceptedWithAcceptedCall:(Call *)acceptedCall error:(CometChatException *)error {
NSLog(@"Call accepted, joining session...");
[self joinCallSessionWithSessionId:acceptedCall.sessionID];
}
- (void)onOutgoingCallRejectedWithRejectedCall:(Call *)rejectedCall error:(CometChatException *)error {
NSLog(@"Call rejected");
// Dismiss outgoing call UI
}
- (void)onIncomingCallCancelledWithCancelledCall:(Call *)cancelledCall error:(CometChatException *)error {
NSLog(@"Incoming call cancelled");
// Dismiss incoming call UI
}
- (void)onCallEndedMessageReceivedWithEndedCall:(Call *)endedCall error:(CometChatException *)error {
NSLog(@"Call ended");
}
| Callback | Description |
|---|
onIncomingCallReceived | A new incoming call is received |
onOutgoingCallAccepted | The receiver accepted your outgoing call |
onOutgoingCallRejected | The receiver rejected your outgoing call, or the call timed out as unanswered |
onIncomingCallCancelled | The caller cancelled the incoming call |
onCallEndedMessageReceived | The call has ended |
Remember to remove the call listener when it’s no longer needed to prevent memory leaks:CometChat.removeCallListener(listenerID)
Accept a Call
When an incoming call is received, accept it using the Chat SDK:
func acceptIncomingCall(sessionId: String) {
CometChat.acceptCall(sessionID: sessionId, onSuccess: { call in
print("Call accepted")
self.joinCallSession(sessionId: call?.sessionID ?? "")
}, onError: { error in
print("Accept call failed: \(error?.errorDescription ?? "")")
})
}
- (void)acceptIncomingCallWithSessionId:(NSString *)sessionId {
[CometChat acceptCallWithSessionID:sessionId onSuccess:^(Call * call) {
NSLog(@"Call accepted");
[self joinCallSessionWithSessionId:call.sessionID];
} onError:^(CometChatException * error) {
NSLog(@"Accept call failed: %@", error.errorDescription);
}];
}
Reject a Call
Reject an incoming call:
func rejectIncomingCall(sessionId: String) {
let status: CometChat.CallStatus = .rejected
CometChat.rejectCall(sessionID: sessionId, status: status, onSuccess: { call in
print("Call rejected")
// Dismiss incoming call UI
}, onError: { error in
print("Reject call failed: \(error?.errorDescription ?? "")")
})
}
- (void)rejectIncomingCallWithSessionId:(NSString *)sessionId {
[CometChat rejectCallWithSessionID:sessionId status:CometChatCallStatusRejected onSuccess:^(Call * call) {
NSLog(@"Call rejected");
// Dismiss incoming call UI
} onError:^(CometChatException * error) {
NSLog(@"Reject call failed: %@", error.errorDescription);
}];
}
Cancel a Call
Cancel an outgoing call before it’s answered:
func cancelOutgoingCall(sessionId: String) {
let status: CometChat.CallStatus = .cancelled
CometChat.rejectCall(sessionID: sessionId, status: status, onSuccess: { call in
print("Call cancelled")
// Dismiss outgoing call UI
}, onError: { error in
print("Cancel call failed: \(error?.errorDescription ?? "")")
})
}
- (void)cancelOutgoingCallWithSessionId:(NSString *)sessionId {
[CometChat rejectCallWithSessionID:sessionId status:CometChatCallStatusCancelled onSuccess:^(Call * call) {
NSLog(@"Call cancelled");
// Dismiss outgoing call UI
} onError:^(CometChatException * error) {
NSLog(@"Cancel call failed: %@", error.errorDescription);
}];
}
Join the Call Session
After accepting a call (or when your outgoing call is accepted), join the call session using the Calls SDK:
func joinCallSession(sessionId: String) {
let sessionSettings = CometChatCalls.sessionSettingsBuilder
.setType(.video)
.build()
CometChatCalls.joinSession(
sessionID: sessionId,
callSetting: sessionSettings,
container: callViewContainer,
onSuccess: { message in
print("Joined call session")
},
onError: { error in
print("Failed to join: \(error?.errorDescription ?? "")")
}
)
}
- (void)joinCallSessionWithSessionId:(NSString *)sessionId {
SessionSettings *sessionSettings = [[[CometChatCalls sessionSettingsBuilder]
setType:CallTypeVideo]
build];
[CometChatCalls joinSessionWithSessionID:sessionId
callSetting:sessionSettings
container:self.callViewContainer
onSuccess:^(NSString * message) {
NSLog(@"Joined call session");
} onError:^(CometChatCallException * error) {
NSLog(@"Failed to join: %@", error.errorDescription);
}];
}
End a Call
Properly ending a call requires coordination between both SDKs to ensure all participants are notified and call logs are recorded correctly.
Always call CometChat.endCall() when ending a call. This notifies the other participant and ensures the call is properly logged. Without this, the other user won’t know the call has ended and call logs may be incomplete.
When using the default call UI, listen for the end call button click using ButtonClickListener and call endCall():
class CallViewController: UIViewController, ButtonClickListener {
var currentSessionId: String = ""
override func viewDidLoad() {
super.viewDidLoad()
CallSession.shared.addButtonClickListener(self)
}
func onLeaveSessionButtonClicked() {
endCall(sessionId: currentSessionId)
}
func endCall(sessionId: String) {
// 1. Leave the call session (Calls SDK)
CallSession.shared.leaveSession()
// 2. Notify other participants (Chat SDK)
CometChat.endCall(sessionID: sessionId, onSuccess: { call in
print("Call ended successfully")
self.navigationController?.popViewController(animated: true)
}, onError: { error in
print("End call failed: \(error?.errorDescription ?? "")")
self.navigationController?.popViewController(animated: true)
})
}
// Other ButtonClickListener callbacks...
}
@interface CallViewController () <ButtonClickListener>
@property (nonatomic, strong) NSString *currentSessionId;
@end
@implementation CallViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[CallSession shared] addButtonClickListener:self];
}
- (void)onLeaveSessionButtonClicked {
[self endCallWithSessionId:self.currentSessionId];
}
- (void)endCallWithSessionId:(NSString *)sessionId {
// 1. Leave the call session (Calls SDK)
[[CallSession shared] leaveSession];
// 2. Notify other participants (Chat SDK)
[CometChat endCallWithSessionID:sessionId onSuccess:^(Call * call) {
NSLog(@"Call ended successfully");
[self.navigationController popViewControllerAnimated:YES];
} onError:^(CometChatException * error) {
NSLog(@"End call failed: %@", error.errorDescription);
[self.navigationController popViewControllerAnimated:YES];
}];
}
@end
The other participant receives onCallEndedMessageReceived callback and should leave the session:
func onCallEndedMessageReceived(endedCall: Call?, error: CometChatException?) {
CallSession.shared.leaveSession()
navigationController?.popViewController(animated: true)
}
- (void)onCallEndedMessageReceivedWithEndedCall:(Call *)endedCall error:(CometChatException *)error {
[[CallSession shared] leaveSession];
[self.navigationController popViewControllerAnimated:YES];
}
Call Status Values
| Status | Description |
|---|
initiated | Call has been initiated but not yet answered |
ongoing | Call is currently in progress |
busy | Receiver is busy on another call |
rejected | Receiver rejected the call |
cancelled | Caller cancelled before receiver answered |
ended | Call ended normally |
missed | Receiver didn’t answer in time |
unanswered | Call was not answered within the timeout duration |