onecxi_flutter_sdk 1.0.0+1
onecxi_flutter_sdk: ^1.0.0+1 copied to clipboard
A Flutter SDK for OneCxi calling functionality with WebSocket and VoIP support
OneCxi Flutter SDK #
A production-ready Flutter SDK for VoIP calling with call persistence when app is killed.
🎯 Overview #
This Flutter SDK provides enterprise-grade calling functionality with a critical feature: calls DO NOT disconnect when the user kills the app!
Key Features #
- ✅ Call Persistence: Calls continue even when app is killed (Android native, iOS CallKit)
- ✅ Lock Screen Calling: Full-screen native call interface on locked devices (Android)
- ✅ Inbound Calls: Receive calls via FCM push notifications
- ✅ Outbound Calls: Initiate calls to backend numbers
- ✅ App-to-App Calls: Direct calls between mobile users
- ✅ Real-time Audio: Bidirectional audio streaming via WebSocket (8kHz, 16-bit PCM)
- ✅ Full Call Controls: Mute, hold, speaker, DTMF, end call
- ✅ Cross-Platform: iOS (CallKit) + Android (Native Foreground Service)
- ✅ Firebase Integration: FCM for push notifications
What Makes This Special #
The Challenge: When a user kills your calling app, traditional implementations disconnect the call because the Dart VM is destroyed.
Our Solution:
- Android: Native foreground service with
phoneCall|microphonetypes - iOS: CallKit integration with background audio
- Result: Calls survive indefinitely after app termination! 🎉
📚 Documentation #
- Complete SDK Documentation - Full API reference, examples, testing guide
- Lock Screen Calling Feature - Native lock screen call interface documentation
- Android Call Persistence Guide - Deep dive into Android native implementation
- Integration Guide - Step-by-step integration
- Quick Start Guide - Get started in 5 minutes
🚀 Quick Start #
import 'package:onecxi_flutter_sdk/onecxi_flutter_sdk.dart';
// 1. Initialize SDK
await OneCxiSdk.instance.initialize(
accountName: 'your_account',
apiKey: 'your_api_key',
serverName: 'https://your-server.com',
);
// 2. Request permissions
await OneCxiSdk.instance.requestPermissions();
// 3. Make a call
await OneCxiSdk.instance.startOutBoundCall(
registeredNumber: '1234567890',
);
// 4. Kill the app - call continues! 🎉
See full examples in SDK_DOCUMENTATION.md
Architecture #
The SDK is structured to mirror the iOS implementation:
lib/
├── src/
│ ├── models/
│ │ ├── call_status.dart # Call state management
│ │ ├── call_type.dart # Call type enumeration
│ │ └── onecxi_error.dart # Error handling
│ ├── callbacks/
│ │ └── call_progress_callback.dart # Call event callbacks
│ ├── services/
│ │ ├── audio_helper.dart # Audio recording/playback
│ │ ├── websocket_client.dart # WebSocket communication
│ │ └── callkit_manager.dart # Native calling UI
│ └── onecxi_sdk.dart # Main SDK class
└── onecxi_flutter_sdk.dart # Public API exports
Installation #
1. Add Dependencies #
Add the following dependencies to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
# Calling functionality
flutter_callkit_incoming: ^2.5.8
# WebSocket communication
web_socket_channel: ^2.4.0
# Audio handling
flutter_audio_capture: ^1.1.7
just_audio: ^0.9.36
# Permissions
permission_handler: ^11.0.1
# Utilities
uuid: ^4.0.0
logger: ^2.0.2+1
2. Platform Configuration #
iOS Configuration
- Add VoIP capability in
ios/Runner.entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.voip-push-notification</key>
<true/>
<key>com.apple.developer.push-to-talk</key>
<true/>
</dict>
</plist>
- Add background modes in
ios/Runner/Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>voip</string>
<string>audio</string>
<string>background-processing</string>
</array>
- Add microphone permission in
ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone for voice calls</string>
Android Configuration
- Add permissions in
android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
- Add service in
android/app/src/main/AndroidManifest.xml:
<service
android:name="com.hiennv.flutter_callkit_incoming.CallkitSoundPlayerService"
android:exported="false" />
Usage #
1. Initialize the SDK #
import 'package:onecxi_flutter_sdk/onecxi_flutter_sdk.dart';
final OneCxiSdk sdk = OneCxiSdk();
// Initialize with your credentials
await sdk.initialize(
accountName: 'demo_user', // Demo account name
apiKey: 'KKca829f80c7d2cfab9898f9517a26e93a', // Demo API key
serverName: 'serverName', // Demo server name
);
2. Set up Call Progress Callback #
class MyCallHandler implements CallProgressCallback {
@override
void onCallStatusChanged(CallStatus status) {
// Handle call status changes
print('Call status: ${status.isCallOngoing}');
}
@override
void onCallStarted(String callType, String did, String registeredNumber) {
// Handle call start
print('Call started: $callType');
}
@override
void onCallEnded(String callType, String did) {
// Handle call end
print('Call ended: $callType');
}
@override
void onCallError(String error, String? callType) {
// Handle call errors
print('Call error: $error');
}
@override
void onWebSocketStatusChanged(bool isConnected) {
// Handle WebSocket connection changes
print('WebSocket: $isConnected');
}
}
// Set the callback
sdk.setCallProgressCallback(MyCallHandler());
3. Request Permissions #
// Request microphone permission
bool hasPermission = await sdk.requestPermissions();
if (hasPermission) {
print('Microphone permission granted');
} else {
print('Microphone permission denied');
}
4. Make Calls #
Inbound Call (App → Server)
await sdk.startInBoundCall(
did: '90050', // Demo DID from reference app
registeredNumber: '9876543210',
);
Outbound Call (Server → App)
await sdk.startOutBoundCall(
userMobile: '1234567890',
registeredNumber: '9876543210',
);
App-to-App Inbound Call
await sdk.startAppToAppInboundCall(
inputNumber: '1234567890',
registeredNumber: '9876543210',
);
App-to-App Outbound Call
await sdk.startAppToAppOutboundCall(
inputNumber: '1234567890',
registeredNumber: '9876543210',
callFromNumber: '5555555555',
);
5. Handle Incoming Calls #
// Handle incoming VoIP push notification
await sdk.handleIncomingCall(
from: '1234567890',
callType: 'outbound',
registeredNumber: '9876543210',
);
6. Call Controls #
// End call
await sdk.endCall();
// Mute/Unmute
await sdk.muteCall(true); // Mute
await sdk.muteCall(false); // Unmute
// Hold/Resume
await sdk.holdCall(true); // Hold
await sdk.holdCall(false); // Resume
// Speaker
await sdk.setSpeaker(true); // Enable speaker
await sdk.setSpeaker(false); // Disable speaker
// Send DTMF
sdk.sendDTMF('1');
sdk.sendDTMF('*');
sdk.sendDTMF('#');
7. Check Call Status #
// Check if call is active
bool isActive = sdk.isCallActive;
// Check if call is muted
bool isMuted = sdk.callMuted;
// Check if call is on hold
bool isOnHold = sdk.callOnHold;
// Check if speaker is on
bool isSpeakerOn = sdk.isSpeakerOn;
Call Types Explained #
1. Inbound Calls #
- Purpose: App initiates call to server
- Implementation: WebSocket connection only
- Use Case: User wants to call a service/number through the app
- No Native CallKit: Uses app's own calling UI
2. Outbound Calls #
- Purpose: Server initiates call to app
- Implementation: VoIP push notifications + CallKit
- Use Case: Incoming calls from external numbers
- Native CallKit: Shows native calling UI
3. App-to-App Calls #
- Purpose: Direct calls between app users
- Implementation: VoIP push notifications + CallKit
- Use Case: Internal app communication
- Native CallKit: Shows native calling UI
WebSocket Communication #
The SDK establishes WebSocket connections for real-time audio streaming:
WebSocket URL: wss://{serverName}/ws?account={accountName}&apiKey={apiKey}&did={did}&call={callType}&ucid={uniqueCallId}
Audio Format #
- Recording: 48kHz, mono, 16-bit PCM
- Playback: 8kHz, mono, 16-bit PCM
- Buffer Size: 160 frames per transmission
Message Types #
- Audio Data: Binary audio samples
- DTMF:
{"event": "dtmf", "digit": "1", "ucid": "..."} - Hold/Unhold:
{"event": "hold", "action": "hold", "ucid": "..."} - Mute/Unmute:
{"event": "mute", "action": "mute", "ucid": "..."}
Error Handling #
The SDK provides comprehensive error handling:
try {
await sdk.startInBoundCall(did: '1234567890', registeredNumber: '9876543210');
} on OneCxiError catch (error) {
switch (error) {
case OneCxiError.notInitialized:
print('SDK not initialized');
break;
case OneCxiError.invalidInput:
print('Invalid input parameters');
break;
case OneCxiError.permissionDenied:
print('Permission denied');
break;
case OneCxiError.webSocketConnectionFailed:
print('WebSocket connection failed');
break;
default:
print('Unknown error: ${error.message}');
}
}
Integration Steps #
Step 1: Setup Project #
- Create a new Flutter project
- Add dependencies to
pubspec.yaml - Configure platform-specific settings
Step 2: Initialize SDK #
- Import the SDK
- Initialize with credentials
- Set up call progress callback
- Request permissions
Step 3: Implement Call Types #
- Inbound: Use
startInBoundCall()for app-initiated calls - Outbound: Handle VoIP push notifications with
handleIncomingCall() - App-to-App: Use
startAppToAppInboundCall()orstartAppToAppOutboundCall()
Step 4: Add Call Controls #
- Implement mute/unmute functionality
- Add hold/resume controls
- Include speaker toggle
- Add DTMF keypad
Step 5: Handle VoIP Push Notifications #
- Configure push notification handling
- Parse incoming call data
- Call
handleIncomingCall()with parsed data
Step 6: Test and Debug #
- Test each call type
- Verify audio quality
- Test call controls
- Debug WebSocket connections
Troubleshooting #
Common Issues #
-
WebSocket Connection Failed
- Check server URL and credentials
- Verify network connectivity
- Check firewall settings
-
Audio Not Working
- Ensure microphone permission is granted
- Check audio session configuration
- Verify audio format compatibility
-
CallKit Not Showing
- Verify iOS entitlements
- Check background modes
- Ensure proper push notification setup
-
Permission Denied
- Request permissions before making calls
- Handle permission denial gracefully
- Guide users to settings if needed
Debug Logging #
Enable debug logging to troubleshoot issues:
// The SDK includes comprehensive logging
// Check console output for detailed information
✅ Status: Production Ready #
All features are implemented and tested:
- ✅ Real audio streaming (native Android AudioRecord/AudioTrack, iOS flutter_audio_capture)
- ✅ WebSocket connection (native Android OkHttp, iOS web_socket_channel)
- ✅ CallKit integration (flutter_callkit_incoming)
- ✅ Permission handling (permission_handler)
- ✅ FCM push notifications (Firebase Cloud Messaging)
- ✅ Tested on real devices (Android 14, iOS 16+)
- ✅ Call persistence verified (2+ minutes after app kill)
Ready for production deployment! See SDK_DOCUMENTATION.md for complete guide.
Support #
For issues and questions:
- Check the troubleshooting section
- Review the iOS native implementation for reference
- Test with the provided sample app
- Ensure all platform configurations are correct
License #
This SDK is based on the OneCxi iOS implementation and follows the same licensing terms.