Calls Service¶
The Calls service handles all call operations — 1:1 audio/video calls, group calls, and full peer-to-peer WebRTC calls with the CallManager.
// Access via client
final calls = client.calls;
P2P Calling with CallManager¶
For full peer-to-peer calling (audio/video), use the built-in CallManager. It handles NX signaling, WebRTC peer connections, ICE negotiation, and call lifecycle automatically.
Step 1: Generate NX Token
final nxResponse = await client.auth.generateXMPPToken(
username: '+255788811191',
);
final nxtoken = nxResponse['token'];
final nxid = nxResponse['jid'];
final wsUrl = nxResponse['nxws'];
Step 2: Create CallManager
final callManager = await client.createCallManager(
nxtoken: nxtoken,
nxid: nxid,
wsUrl: wsUrl,
name: 'Your Name',
onCallStateChanged: (state) {
// Handle call state changes
print('Call state: $state');
},
onIncomingCall: (callerName) {
// Show incoming call UI
print('Incoming call from: $callerName');
},
onCallEnded: (reason) {
// Handle call ended
print('Call ended: $reason');
},
onError: (error) {
// Handle errors
print('Error: $error');
},
);
Step 3: Make a Call
// Outgoing call
await callManager.initiateCall(
to: '+255788811192',
audio: true,
video: false,
);
// Accept incoming call
await callManager.acceptCall(
audio: true,
video: false,
);
// Reject incoming call
await callManager.rejectCall();
// End call
await callManager.endCall();
Step 4: Call Controls
// Mute / Unmute
callManager.webrtcService?.toggleAudio(false);
// Speaker toggle
callManager.webrtcService?.toggleSpeaker(true);
// Switch front/back camera
await callManager.webrtcService?.switchCamera();
Step 5: Cleanup
callManager.dispose();
Call States¶
State |
Description |
|---|---|
|
No active call |
|
Outgoing call in progress |
|
Incoming call received |
|
Call established and connected |
|
Call has ended |
Call Types¶
Type |
Description |
|---|---|
|
Audio-only call |
|
Audio and video call |
|
Peer-to-peer WebRTC call (direct, low latency) |
|
Group call with multiple participants |
Methods¶
initiateCall¶
Start a 1:1 call with a single user.
Signature
Future<Map<String, dynamic>> initiateCall({
required String to,
CallType callType = CallType.video,
String? room,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
Recipient’s phone number or NX identifier |
|
CallType |
Type of call: |
|
String? |
Optional custom room identifier |
Example
await client.calls.initiateCall(
to: '+255788811192',
callType: CallType.video,
);
initiateGroupCall¶
Start a call with multiple participants.
Signature
Future<Map<String, dynamic>> initiateGroupCall({
required List<String> participants,
CallType callType = CallType.video,
String? room,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
List<String> |
List of recipient phone numbers or NX identifiers |
|
CallType |
Type of call: |
|
String? |
Optional custom room identifier |
Example
await client.calls.initiateGroupCall(
participants: ['+255788811192', '+255788811193'],
callType: CallType.audio,
);
initiateP2PCall¶
Start a direct peer-to-peer WebRTC call. This sends both an FCM push notification and an NX signaling message to the recipient.
Signature
Future<Map<String, dynamic>> initiateP2PCall({
required String to,
String? room,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
Recipient’s phone number or NX identifier |
|
String? |
Optional custom room identifier |
Example
await client.calls.initiateP2PCall(
to: '+255788811192',
);
declineCall¶
Decline an incoming call.
Signature
Future<Map<String, dynamic>> declineCall(String room)
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
The room identifier of the incoming call |
Example
await client.calls.declineCall(roomId);
getCallUrl¶
Generate a pre-signed call URL for use in mobile apps or web clients.
Signature
Future<String> getCallUrl({
required String to,
CallType callType = CallType.video,
String? room,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
Recipient’s phone number or NX identifier |
|
CallType |
Type of call: |
|
String? |
Optional custom room identifier |
Example
final url = await client.calls.getCallUrl(
to: '+255788811192',
callType: CallType.video,
);
// Open url in browser or deep link
getWebRTCCredentials¶
Fetch TURN/STUN server credentials required for WebRTC P2P calls.
Note
Credentials have a 24-hour TTL. Always fetch fresh credentials immediately before starting a call.
Signature
Future<Map<String, dynamic>> getWebRTCCredentials()
Returns
A map containing iceServers, username, and password for use with the WebRTC peer connection.
Example
final credentials = await client.calls.getWebRTCCredentials();
final iceServers = credentials['iceServers'];
recordCall¶
Record a call event for analytics. Call this after every call ends, fails, is declined, or is missed to track call history.
Signature
Future<Map<String, dynamic>> recordCall({
required String room,
required CallType callType,
required CallAnalyticsStatus status,
int durationSeconds = 0,
Map<String, dynamic>? metadata,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
The room identifier of the call |
|
CallType |
Type of call: |
|
CallAnalyticsStatus |
Outcome: |
|
int |
Duration of the call in seconds (0 for failed/declined/missed) |
|
Map<String, dynamic>? |
Optional extra data (e.g. who ended the call, error message) |
Analytics Status Values
Status |
Description |
|---|---|
|
Call was initiated by the caller |
|
Ringing, waiting for recipient to answer |
|
Recipient answered the call |
|
Recipient declined the call |
|
Caller cancelled before answer |
|
Recipient did not answer |
|
Call completed successfully |
|
Call failed (e.g. ICE connection error) |
Examples
Record a completed call:
await client.calls.recordCall(
room: 'room_abc123',
callType: CallType.video,
status: CallAnalyticsStatus.ended,
durationSeconds: 120,
metadata: {'ended_by': 'caller', 'is_group': false},
);
Record a failed call:
await client.calls.recordCall(
room: 'room_xyz789',
callType: CallType.audio,
status: CallAnalyticsStatus.failed,
durationSeconds: 0,
metadata: {'error': 'ICE connection failed'},
);
Record a declined call:
await client.calls.recordCall(
room: 'room_declined123',
callType: CallType.video,
status: CallAnalyticsStatus.declined,
);
Usage with CallManager
Note
CallManager automatically records call analytics on every call end. You don’t need to manually call recordCall when using CallManager.
getCallHistory¶
Fetch call history for the current user with optional filters.
Signature
Future<Map<String, dynamic>> getCallHistory({
DateTime? startDate,
DateTime? endDate,
CallType? callType,
CallAnalyticsStatus? status,
String? participant,
int page = 1,
int pageSize = 20,
})
Parameters
Parameter |
Type |
Description |
|---|---|---|
|
DateTime? |
Filter calls from this date onwards (format: YYYY-MM-DD) |
|
DateTime? |
Filter calls up to this date (format: YYYY-MM-DD) |
|
CallType? |
Filter by call type (audio, video, p2p, group) |
|
CallAnalyticsStatus? |
Filter by call outcome (ended, failed, declined, etc.) |
|
String? |
Filter by participant (NX ID or phone number) |
|
int |
Page number (default: 1) |
|
int |
Results per page (default: 20) |
Example
// Get all calls (paginated)
final history = await client.calls.getCallHistory();
// Filter by date range
final recent = await client.calls.getCallHistory(
startDate: DateTime(2026, 5, 1),
endDate: DateTime(2026, 5, 27),
);
// Filter by call type
final videoCalls = await client.calls.getCallHistory(
callType: CallType.video,
);
// Filter by status
final failedCalls = await client.calls.getCallHistory(
status: CallAnalyticsStatus.failed,
);
// Filter by participant
final callsWithUser = await client.calls.getCallHistory(
participant: 'user123',
);
// Combined filters with pagination
final filtered = await client.calls.getCallHistory(
callType: CallType.video,
status: CallAnalyticsStatus.ended,
startDate: DateTime(2026, 5, 20),
page: 1,
pageSize: 20,
);
CallManager Advanced Features¶
CallManager provides advanced features for quality control and call statistics.
Video Quality Settings
Adjust video resolution and frame rate based on network conditions:
// Set low quality for poor network
callManager.setVideoQuality(
width: 640,
height: 480,
fps: 15,
);
// Set high quality for good network
callManager.setVideoQuality(
width: 1920,
height: 1080,
fps: 30,
);
Bitrate Control
Limit audio and video bandwidth:
// Set audio bitrate (kbps)
callManager.setAudioBitrate(64); // 64 kbps
// Set video bitrate (kbps)
callManager.setVideoBitrate(1000); // 1000 kbps
Call Statistics
Monitor real-time call quality metrics:
// Start collecting statistics (every 2 seconds)
callManager.startCallStatsCollection();
// Listen to statistics stream
callManager.callStatsStream?.listen((stats) {
print('Video bitrate: ${stats['video']['bitrate']} bps');
print('Packets lost: ${stats['video']['packetsLost']}');
print('Frame size: ${stats['video']['frameWidth']}x${stats['video']['frameHeight']}');
});
// Get latest stats snapshot
final latest = callManager.latestCallStats;
Statistics Data Structure
The stats stream provides the following data:
Field |
Description |
|---|---|
|
Current video bitrate in bps |
|
Total video packets received |
|
Total video packets lost |
|
Total video bytes received |
|
Total video frames received |
|
Current video frame width |
|
Current video frame height |
|
Current audio bitrate in bps |
|
Total audio packets received |
|
Total audio packets lost |
|
Total audio bytes received |
|
Total video packets sent |
|
Total video bytes sent |
|
Total audio packets sent |
|
Total audio bytes sent |
Quality Presets
Recommended quality settings for different network conditions:
Network |
Resolution |
Bitrate |
|---|---|---|
Poor (3G) |
640x480 @ 15fps |
500 kbps video, 32 kbps audio |
Medium (4G/WiFi) |
1280x720 @ 30fps (default) |
1000 kbps video, 64 kbps audio |
Good (5G/Fiber) |
1920x1080 @ 30fps |
2000 kbps video, 128 kbps audio |