Skip to content

Call lifecycle ​

A Callx call is always in one of six states. The native coordinator is the only thing that changes the state, and every change is saved before your app hears about it.

StateMeaningmediaReady
incomingThe phone is ringing with the system incoming-call UIfalse
outgoingYou started a call; the other side has not answeredfalse
connectingAnswered, locally or remotely; media is not flowing yetfalse
activeMedia is confirmed to flowtrue
heldThe call was active and is on holdstays true
endedOver. No further commands applyfalse

Answered is not connected ​

Answering moves a call to connecting, never directly to active. A call becomes active only when the media engine reports that the other party's audio actually arrives (mediaConnected, which the LiveKit adapter reports for you). This lets your UI show "Connecting…" honestly instead of a live call with silence.

Media interruptions ​

mediaInterrupted is a flag, not a state. It becomes true when media that had connected drops, for example while the media SDK reconnects after a network change, and returns to false when media flows again. It can only be true while the call is active or held.

It never ends, holds or otherwise changes the call. If media does not come back, your backend decides to end the call through signaling. Hold is not an interruption.

The call object ​

ts
interface Call {
  callId: string;
  displayName: string;
  direction: 'incoming' | 'outgoing';
  state: 'incoming' | 'outgoing' | 'connecting' | 'active' | 'held' | 'ended';
  muted: boolean;
  mediaReady: boolean;
  mediaInterrupted?: boolean;
  endReason?: EndReason;
  createdAtMs?: number;
  acceptedAtMs?: number;      // when it was answered
  mediaConnectedAtMs?: number; // first time media flowed
  endedAtMs?: number;
}

Timestamps are Unix milliseconds. Unknown timestamps are absent, never 0. Use acceptedAtMs for a call timer; Android's ongoing-call notification does the same.

End reasons ​

endReasonMeaning
localHangupThis device hung up
declinedThis device declined the incoming call
remoteEndedThe other party hung up an established call
callerCancelledThe caller gave up before anyone answered
unansweredNobody answered before the ring deadline
busyThe callee was busy
failedThe call could not be set up or recovered
answeredElsewhereAnother device of the same user answered
declinedElsewhereAnother device of the same user declined

Your host passes one of these when it reports a remote end; any other value is rejected.

Rules that keep calls from coming back ​

  • A callId is never reused. The core keeps a ledger of ended calls (24 hours, 1,000 calls), so a late or duplicate invitation for an ended call is rejected while its tombstone is retained. Your backend must expire invitations and never reuse call IDs beyond that retention window.
  • A remote end for a call that has not rung yet is recorded too, so a cancel that overtakes its invitation still wins.
  • An unanswered call ends at its ring deadline: 45 seconds, or the invitation's expiresAtMs if earlier.
  • Ended calls disappear from snapshots after five minutes. Keep your own call history if you need one.

One call at a time ​

This version supports one live call. An invitation that arrives during a call does not ring and is reported to your listener as busy, so your backend can tell the caller. A startCall during a call returns busy.

Released under the MIT License. No telemetry, in the library or on this site.