nexacon_sdk 1.3.6
nexacon_sdk: ^1.3.6 copied to clipboard
A comprehensive Flutter SDK for Nexacon API, providing plug-and-play P2P calling with WebRTC and NX signaling, plus real-time messaging with presence and read receipts.
Nexacon Flutter SDK #
A comprehensive Flutter SDK for Nexacon API — providing plug-and-play P2P audio/video calling with WebRTC and NX signaling, plus real-time messaging with presence and read receipts.
Table of Contents #
- Features
- Installation
- Platform Requirements
- Platform Configuration
- Quick Start — Outgoing Call
- Quick Start — Incoming Call
- Advanced Usage
- Real-Time Messaging
- Foldable Device Support
- Call States
- API Reference
- Troubleshooting
Features #
- Simplified API: Make or receive calls in 3 steps with
NexaconSDK - P2P Calling: Full WebRTC peer-to-peer audio/video calling with automatic signaling
- NX Token Management: Automatic token generation, validation, and client authentication
- Incoming Call Support:
initialize()+acceptCall()for clean incoming call handling - Consecutive Call Safety: SDK automatically resets all internal state after each call — safe for back-to-back calls
- Stale Signal Guard: Signaling messages from previous calls are filtered by room ID and cancelled subscription — no ghost
call_endevents - Real-Time Messaging: Instant messaging with typing indicators and read receipts
- Presence Management: Online/offline status tracking
- Call Controls: Mute, speaker toggle, video toggle, camera switch, duration tracking
- Automatic Reconnection: Built-in connection management with exponential backoff
- ICE Management: Automatic ICE candidate buffering and exchange
- Foldable Device Support: Detect fold state changes on Android devices
- Cross-Platform: Android, iOS, Web, Linux, macOS, Windows
- Professional Logging: Emoji-based console logging for easy debugging
Installation #
Add to your pubspec.yaml:
dependencies:
nexacon_sdk: ^1.3.4
Install:
flutter pub get
Platform Requirements #
| Platform | Minimum Version | Notes |
|---|---|---|
| Android | API 21 (Android 5.0) | Requires camera/audio permissions |
| iOS | 12.0 | Requires camera/audio permissions |
| Linux | Any | Works out of the box |
| macOS | 10.14 | Requires camera/audio entitlements |
| Web | Modern browsers | Requires WebRTC support |
| Windows | Any | Works out of the box |
Platform Configuration #
Android #
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
Set minimum SDK in android/app/build.gradle:
android {
defaultConfig {
minSdkVersion 21
}
}
iOS #
Add to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is required for video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required for audio calls</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>voip</string>
</array>
Set minimum iOS version in ios/Podfile:
platform :ios, '12.0'
macOS #
Add to macos/Runner/DebugProfile.entitlements and Release.entitlements:
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
Web, Linux, Windows #
No additional configuration required.
Quick Start — Outgoing Call #
Use NexaconSDK for the simplest possible integration:
import 'package:nexacon_sdk/nexacon_sdk.dart';
final sdk = NexaconSDK(
apiKey: 'your_api_key',
secretKey: 'your_secret_key',
);
// Set up callbacks
sdk.onCallStateChanged = (state) => print('📱 State: $state');
sdk.onCallEnded = (reason) => print('📞 Ended: $reason');
sdk.onError = (error) => print('❌ Error: $error');
// Start an outgoing call — handles token, connection & signaling automatically
await sdk.startCall(
to: '+255788811192', // recipient
username: '+255788811191', // your username
audio: true,
video: false,
);
// In-call controls
sdk.toggleMute(true); // mute microphone
sdk.toggleSpeaker(true); // enable speaker
sdk.toggleVideo(true); // enable video
await sdk.switchCamera(); // switch front/back camera
// End call and release resources
await sdk.endCall();
await sdk.dispose();
Quick Start — Incoming Call #
Incoming calls can be handled via two paths:
Path 1: NX (app in foreground) #
Use acceptWhenReady() when the app is already open — it waits for the NX callInvitation signal:
import 'package:nexacon_sdk/nexacon_sdk.dart';
final sdk = NexaconSDK(
apiKey: 'your_api_key',
secretKey: 'your_secret_key',
);
// Set up callbacks
sdk.onCallStateChanged = (state) => print('📱 State: $state');
sdk.onIncomingCall = (name) => print('📞 Incoming from: $name');
sdk.onCallEnded = (reason)=> print('📞 Ended: $reason');
sdk.onError = (error) => print('❌ Error: $error');
// Initialize and automatically accept when NX signal arrives
await sdk.acceptWhenReady(
username: '+255788811191',
audio: true,
video: false,
);
// End call and release resources
await sdk.endCall();
await sdk.dispose();
Path 2: Push Notification (app opened from FCM) #
Use acceptFromNotification() when the user opens the app from a push notification — the FCM payload already contains roomId and callerNxId:
// Called when user taps the FCM notification
await sdk.acceptFromNotification(
username: '+255788811191',
roomId: fcmData['room'], // from FCM payload
callerNxId: fcmData['caller'], // from FCM payload (caller's phone/NX ID)
callerName: fcmData['caller_name'],
audio: true,
video: false,
);
Advanced Usage #
For full low-level control, use NexaconClient directly.
Step 1: Initialize Client #
import 'package:nexacon_sdk/nexacon_sdk.dart';
final client = NexaconClient(
apiKey: 'your_api_key',
secretKey: 'your_secret_key',
// baseUrl is optional — defaults to https://nxservice.quantumvision-tech.com/api/v1.0
);
Step 2: Generate NX Token #
final nxResponse = await client.auth.getNxToken(username: '+255788811191');
final nxtoken = nxResponse['token'];
final nxid = nxResponse['jid'];
final wsUrl = nxResponse['nxws'];
// IMPORTANT: Required to avoid 403 errors on subsequent API calls
client.setToken(nxtoken);
Step 3: Create CallManager #
final callManager = await client.createCallManager(
nxtoken: nxtoken,
nxid: nxid,
wsUrl: wsUrl,
name: 'Your Display Name',
onCallStateChanged: (state) {
if (state == CallState.connected) print('✅ Connected');
},
onIncomingCall: (callerName) => print('📞 Incoming: $callerName'),
onCallEnded: (reason) => print('📞 Ended: $reason'),
onError: (error) => print('❌ $error'),
);
Step 4: Make or Accept 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
callManager.rejectCall();
// End current call
await callManager.endCall();
Step 5: In-Call Controls #
callManager.webrtcService?.toggleAudio(false); // mute
callManager.webrtcService?.toggleAudio(true); // unmute
callManager.webrtcService?.toggleVideo(false); // disable video
callManager.webrtcService?.toggleVideo(true); // enable video
callManager.webrtcService?.toggleSpeaker(true); // speaker on
await callManager.webrtcService?.switchCamera();
final duration = callManager.callDuration;
print('Duration: ${duration.inSeconds}s');
Step 6: Cleanup #
callManager.dispose();
client.close();
Real-Time Messaging #
final messagingManager = client.createMessagingManager();
// Receive messages
messagingManager.messageStream.listen((message) {
print('💬 ${message['from']}: ${message['message']}');
});
// Send a message
messagingManager.sendMessage(to: 'recipient@example.com', message: 'Hello!');
// Typing indicator
messagingManager.sendTypingIndicator('recipient@example.com', isTyping: true);
// Read receipt
messagingManager.sendReadReceipt('recipient@example.com', 'msg_123');
// Presence (online/offline)
messagingManager.presenceStream.listen((presence) {
final isOnline = presence['type'] == null || presence['type'] == 'available';
print('User is ${isOnline ? 'online' : 'offline'}');
});
messagingManager.dispose();
Foldable Device Support #
final foldStateService = FoldStateService();
foldStateService.foldStateStream.listen((state) {
switch (state) {
case FoldState.flat: print('Device is flat');
case FoldState.folded: print('Device is folded');
case FoldState.halfOpen: print('Device is half open');
case FoldState.unknown: print('Fold state unknown');
}
});
if (foldStateService.isFolded) {
// Adjust UI for folded state
}
foldStateService.dispose();
Call States #
| State | Description |
|---|---|
idle |
No active call |
calling |
Outgoing call in progress |
incoming |
Incoming call received |
connected |
Call connected |
ended |
Call ended |
API Reference #
NexaconSDK (Simplified) #
NexaconSDK({required String apiKey, required String secretKey, String? baseUrl})
| Method | Description |
|---|---|
initialize({required username, name}) |
Connect to signaling without dialing — use for incoming calls |
startCall({required to, required username, name, audio, video}) |
Start outgoing call — handles everything internally |
acceptWhenReady({required username, name, audio, video, timeout}) |
Initialize and auto-accept when NX signal arrives (foreground) |
acceptFromNotification({required username, roomId, callerNxId, callerName, name, audio, video}) |
Accept using FCM/push payload data (background) |
acceptCall({audio, video}) |
Accept an incoming call (must be in incoming state) |
rejectCall() |
Reject an incoming call |
endCall() |
End the current call |
toggleMute(bool muted) |
Toggle microphone |
toggleSpeaker(bool enabled) |
Toggle speaker |
toggleVideo(bool enabled) |
Toggle video |
switchCamera() |
Switch front/back camera |
dispose() |
Cleanup all resources |
| Property | Type | Description |
|---|---|---|
callDuration |
Duration |
Current call duration |
| Callback | Signature | Description |
|---|---|---|
onCallStateChanged |
Function(CallState) |
Call state updates |
onIncomingCall |
Function(String) |
Incoming call received |
onCallEnded |
Function(String) |
Call ended with reason |
onError |
Function(String) |
Error occurred |
onLocalStream |
Function() |
Local video stream ready |
onRemoteStream |
Function() |
Remote video stream ready |
NexaconClient (Advanced) #
NexaconClient({required String apiKey, required String secretKey, String? baseUrl})
| Method | Description |
|---|---|
auth.getNxToken({required username}) |
Generate NX token |
setToken(String token) |
Set NX token for API auth |
createCallManager({...}) |
Create a CallManager instance |
createMessagingManager() |
Create a MessagingManager instance |
close() |
Close the client |
CallManager #
| Method | Description |
|---|---|
initiateCall({required to, audio, video}) |
Start outgoing call |
acceptCall({audio, video}) |
Accept incoming call |
rejectCall() |
Reject incoming call |
endCall() |
End current call |
dispose() |
Cleanup resources |
MessagingManager #
| Stream | Description |
|---|---|
messageStream |
Incoming messages |
typingStream |
Typing indicators |
readReceiptStream |
Read receipts |
presenceStream |
Presence changes |
| Method | Description |
|---|---|
sendMessage({required to, required message}) |
Send a message |
sendTypingIndicator(to, {isTyping}) |
Send typing status |
sendReadReceipt(to, messageId) |
Send read receipt |
dispose() |
Cleanup |
Troubleshooting #
403 Error on Call Initiation #
Cause: NX token not set on the client.
Fix (Advanced API only — NexaconSDK handles this automatically):
client.setToken(nxtoken); // Must be called after getNxToken()
NX Connection Timeout #
Cause: WebSocket URL uses https:// instead of wss://.
Fix: NexaconSDK converts this automatically. If using NexaconClient directly, ensure wsUrl starts with wss://.
Camera / Microphone Not Working #
- Android: Ensure permissions are in
AndroidManifest.xmland granted at runtime - iOS: Ensure keys are in
Info.plist - Web: App must be served over HTTPS (WebRTC requirement)
Call Stuck in "Calling" #
- Ensure the callee is online with the SDK initialized
- Calls time out after 60 seconds if not accepted
Second Call Auto-Ends After Acceptance #
Cause (pre-1.3.4): When the NX connection is re-established for a second call, the server replays queued messages from the previous session — including the old call_end signal. Two bugs compounded this:
- The
CallManagerNX subscription was never cancelled on call end — an orphaned subscription could process stale signals after internal state was reset toidle. - There was no room ID validation — any
call_endwas processed unconditionally.
Fixed in v1.3.4:
- The
StreamSubscriptionis now stored and immediately cancelled in_endCall(), making the oldCallManagerpermanently deaf after a call ends. - Every non-invitation signal is validated against
_currentRoomId— mismatched room IDs are silently dropped. NexaconSDK.endCall()nulls the internal_callManagerso the next call always gets a completely fresh instance.
Required app-side guard (still needed even with 1.3.4):
onOtherUserLeft: () {
// Only end call if the WebRTC peer actually joined.
// Guards against any residual state on the app layer.
if (!_isOtherUserConnected) return;
_endCall();
},
Console Log Reference #
| Emoji | Meaning |
|---|---|
| 🔐 | Authentication |
| 📞 | Call operations |
| 📡 | Signaling / WebRTC |
| ✅ | Success |
| ❌ | Error |
| ⚠️ | Warning |
Getting Help #
License #
MIT License — see LICENSE for details.