Listen to an event with addEventListener, which returns a subscription:
const subscription = RNCallKeep.addEventListener('answerCall', ({ callUUID }) => {
// ...
});
subscription.remove();| Event | iOS | Android |
|---|---|---|
| didReceiveStartCallAction | ✅ | ✅ |
| startCallFailed | ✅ | ✅ |
| answerCall | ✅ | ✅ |
| endCall | ✅ | ✅ |
| didActivateAudioSession | ✅ | ✅ |
| didDeactivateAudioSession | ✅ | ❌ |
| didDisplayIncomingCall | ✅ | ✅ |
| didPerformSetMutedCallAction | ✅ | ✅ |
| didToggleHoldCallAction | ✅ | ✅ |
| didPerformDTMFAction | ✅ | ✅ |
| didResetProvider | ✅ | ❌ |
| didLoadWithEvents | ✅ | ❌ |
| showIncomingCallUi | ❌ | ✅ |
| silenceIncomingCall | ❌ | ✅ |
| createIncomingConnectionFailed | ❌ | ✅ |
| checkReachability | ❌ | ✅ |
| didChangeAudioRoute | ✅ | ✅ |
| onHasActiveCall | ❌ | ✅ |
Device sends this event once it decides the app is allowed to start a call, either from the built-in phone screens (iOS/Recents, Android/Contact),
or by the app calling RNCallKeep.startCall.
Try to start your app call action from here (e.g. get credentials of the user by data.handle and/or send INVITE to your SIP server)
If the system refuses a call started with startCall, you get startCallFailed instead.
Note: on iOS, when the call comes from Recents, Contacts or Siri, callUUID is not defined: CallKit doesn't manage the call yet. Generate a UUID and call startCall, which sends this event again with the callUUID.
RNCallKeep.addEventListener('didReceiveStartCallAction', ({ handle, callUUID, name }) => {
});handle(string)- Phone number of the callee
callUUID(string)- The UUID of the call that is to be answered
name(string)- Name of the callee
The system refused a call started with startCall: it shows no call, and didReceiveStartCallAction won't come for it. Stop what your app started for this call, and tell the user.
- On iOS, CallKit refused the call.
errorCodesays why, for instanceMaximumCallGroupsReachedwhen calls already fill themaximumCallGroupsoption. - On Android, Telecom refused the call before creating it, for instance in managed mode while another outgoing call is dialing. Android doesn't say why. In self-managed mode, Android asks the user whether to end the ongoing call instead; if they cancel, Android doesn't tell the app, and this event isn't sent.
With errorCode CallUUIDAlreadyExists (iOS), CallKit already has a call with this UUID, for instance after a second startCall for the same call: that call goes on, so don't end it.
RNCallKeep.addEventListener('startCallFailed', ({ callUUID, handle, name, errorCode }) => {
if (errorCode === 'CallUUIDAlreadyExists') {
return; // the call with this UUID goes on
}
// Stop your app's call for callUUID, e.g. cancel the INVITE sent to your SIP server.
});callUUID(string)- The UUID passed to
startCall.
- The UUID passed to
handle(string)- Phone number of the callee.
name(string)- The
contactIdentifierpassed tostartCall.
- The
error(string)- iOS only. CallKit's description of the error.
errorCode(string)- iOS only. Possible values: "Unentitled", "UnknownCallProvider", "EmptyTransaction", "UnknownCallUUID", "CallUUIDAlreadyExists", "InvalidAction", "MaximumCallGroupsReached", "CallIsProtected", "Unknown". See https://developer.apple.com/documentation/callkit/cxerrorcoderequesttransactionerror for more information.
User answer the incoming call
RNCallKeep.addEventListener('answerCall', ({ callUUID }) => {
// Do your normal `Answering` actions here.
});callUUID(string)- The UUID of the call that is to be answered.
User finish the call.
RNCallKeep.addEventListener('endCall', ({ callUUID }) => {
// Do your normal `Hang Up` actions here
});callUUID(string)- The UUID of the call that is to be ended.
The AudioSession has been activated by RNCallKeep.
RNCallKeep.addEventListener('didActivateAudioSession', () => {
// you might want to do following things when receiving this event:
// - Start playing ringback if it is an outgoing call
});iOS only.
The AudioSession has been deactivated by RNCallKeep.
RNCallKeep.addEventListener('didDeactivateAudioSession', () => {
});Sent for an incoming call reported with displayIncomingCall, or from native code with reportNewIncomingCall (for example when a push arrives):
- On iOS, once CallKit has accepted or refused the call.
erroris set when it refused. - On Android, once Telecom has created the call, so methods that act on the call work from this event. If Android refuses the call, you get
createIncomingConnectionFailedinstead.
RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error, callUUID, handle, localizedCallerName, hasVideo, fromPushKit, payload }) => {
// you might want to do following things when receiving this event:
// - Start playing ringback if it is an outgoing call
});error(string)- iOS only.
errorCode(string)- iOS only. Possible values: "Unentitled", "CallUUIDAlreadyExists", "FilteredByDoNotDisturb", "FilteredByBlockList", "Unknown". See https://developer.apple.com/documentation/callkit/cxerrorcodeincomingcallerror for more information.
callUUID(string)- The UUID of the call.
handle(string)- Phone number of the caller
localizedCallerName(string)- Name of the caller to be displayed on the native UI
name(string)- Android only. The same as
localizedCallerName, for apps written when onlyreportNewIncomingCallsent this event.
- Android only. The same as
hasVideo(string)1(video enabled)0(video not enabled)
fromPushKit(string)1(call triggered from PushKit)0(call not triggered from PushKit, and always on Android)
payload(object or string)- iOS: the VoIP push payload passed to
reportNewIncomingCall. - Android: the payload passed to the native
reportNewIncomingCall(a string) ordisplayIncomingCall(aBundle, sent as an object). Absent when there is none.
- iOS: the VoIP push payload passed to
A call was muted by the system or the user:
RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID }) => {
});muted(boolean)callUUID(string)- The UUID of the call.
A call was held or unheld by the current user
RNCallKeep.addEventListener('didToggleHoldCallAction', ({ hold, callUUID }) => {
});hold(boolean)callUUID(string)- The UUID of the call.
Triggered when the audio route has been changed.
RNCallKeep.addEventListener('didChangeAudioRoute', ({ output }) => {
});output(string)⚠️ Will sendSpeakeron iOS butSPEAKERon Android.reason(number, iOS only) See case's in https://developer.apple.com/documentation/avfaudio/avaudiosession/routechangereasonhandle(string, Android only) Phone number of the incoming callercallUUID(string, Android only) The UUID of the call
Used type a number on his dialer
RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
});digits(string)- The digits that emit the dtmf tone
callUUID(string)- The UUID of the call.
iOS only.
The underlying native provider for RNCallKeep has been reset.
RNCallKeep.addEventListener('didResetProvider', () => {
// Likely an error has occurred, you may want to hang up all calls
// and log errors.
});iOS only.
Called as soon as JS context initializes if there were some actions performed by user before JS context has been created.
Since iOS 13, you must display incoming call on receiving PushKit push notification. But if app was killed, it takes some time to create JS context. If user answers the call (or ends it) before JS context has been initialized, user actions will be passed as events array of this event. Similar situation can happen if user would like to start a call from Recents or similar iOS app, assuming that your app was in killed state.
In order for this event to reliably fire, it's necessary to perform setup in AppDelegate.m
NOTE: You still need to subscribe / handle the rest events as usuall. This is just a helper whcih cache and propagate early fired events if and only if for "the native events which DID fire BEFORE js bridge is initialed", it does NOT mean this will have events each time when the app reopened.
// register `didLoadWithEvents` somewhere early in your app when it is ready to handle callkeep events.
RNCallKeep.addEventListener('didLoadWithEvents', (events) => {
// `events` is passed as an Array chronologically, handle or ignore events based on the app's logic
// see example usage in https://github.com/react-native-webrtc/react-native-callkeep/pull/169 or https://github.com/react-native-webrtc/react-native-callkeep/pull/205
});eventsArrayname: string Native event name like:RNCallKeepPerformAnswerCallActiondata: object Object with data passed together with specific event so it can be handled in the same way like original event, for example({ callUUID })foranswerCallevent ifnameisRNCallKeepPerformAnswerCallAction
Android only. Self Managed only.
Only when CallKeep is setup to be in self managed mode. Signals that the app must show an incoming call UI. The implementor must either call displayIncomingCall from react native or native android code to make this event fire.
RNCallKeep.addEventListener('showIncomingCallUi', ({ handle, callUUID, name }) => {
});The following values will match those initially passed to displayIncomingCall
handle(string)- Phone number of the incoming caller.
callUUID(string)- The UUID of the call.
name(string)- Caller Name.
Android only. Self Managed only.
Corresponds to the native onSilence event. The implementor should silence the corresponding incoming calls notification sound when and if this event is fired.
RNCallKeep.addEventListener('silenceIncomingCall', ({ handle, callUUID, name }) => {
});The following values will match those initially passed to silenceIncomingCall
handle(string)- Phone number of the incoming caller.
callUUID(string)- The UUID of the call.
name(string)- Caller Name.
Android only. Self Managed only.
Corresponds to the native onCreateIncomingConnectionFailed callback. The implementor should reject the incoming SIP INVITE with an appropriate status code, such as 483 User Busy. Android unfortunately does not provide the exact reason for refusing to let you accept an incoming call, but they do list a set of reasons here
RNCallKeep.addEventListener('createIncomingConnectionFailed', ({ handle, callUUID, name }) => {
});The following values will match those initially passed to silenceIncomingCall
handle(string)- Phone number of the incoming caller.
callUUID(string)- The UUID of the call.
name(string)- Caller Name.
Android only.
On Android when the application is in background, after a certain delay the OS will close every connection with informing about it. So we have to check if the application is reachable before making a call from the native phone application.
RNCallKeep.addEventListener('checkReachability', () => {
RNCallKeep.setReachable();
});Android only.
A listener that tells the JS side if a native call has been answered while there was an active self-managed call
RNCallKeep.addEventListener('onHasActiveCall', () => {
// eg: End active app call if native call is answered
});iOS also posts these events to NotificationCenter.default, under the names below, with the event's data as userInfo. Native code can observe them before a React Native runtime exists, or while JavaScript is busy; the events JavaScript receives are unchanged. didLoadWithEvents and didChangeAudioRoute are not posted.
Each notification is posted on the thread that sends the event, which can be CallKit's private queue (didDisplayIncomingCall, for instance). Observe with queue: .main, as below, or move to the main queue yourself before touching the UI or the app's state.
| Event | Notification name |
|---|---|
| didReceiveStartCallAction | RNCallKeepDidReceiveStartCallAction |
| startCallFailed | RNCallKeepStartCallFailed |
| answerCall | RNCallKeepPerformAnswerCallAction |
| endCall | RNCallKeepPerformEndCallAction |
| didActivateAudioSession | RNCallKeepDidActivateAudioSession |
| didDeactivateAudioSession | RNCallKeepDidDeactivateAudioSession |
| didDisplayIncomingCall | RNCallKeepDidDisplayIncomingCall |
| didPerformSetMutedCallAction | RNCallKeepDidPerformSetMutedCallAction |
| didToggleHoldCallAction | RNCallKeepDidToggleHoldAction |
| didPerformDTMFAction | RNCallKeepDidPerformDTMFAction |
| didResetProvider | RNCallKeepProviderReset |
NotificationCenter.default.addObserver(
forName: Notification.Name("RNCallKeepPerformAnswerCallAction"), object: nil, queue: .main
) { notification in
let callUUID = notification.userInfo?["callUUID"] as? String
// ...
}On Android, native code receives CallKeep's local broadcasts: see Setup from native code (Android).