social_live_one_on_one_call (SDK)
Latest version 1.0.4
Platform Support
Android | iOS |
---|---|
✅ | ✅ |
Installation
- Add 'social_live_one_on_one_call' in pubspec.yaml
or
flutter pub add social_live_one_on_one_call
- Import library
import 'package:social_live_one_on_one_call/social_live_one_on_one_call.dart';
NOTE: 'social_live_one_on_one_call' required dart 3.4.0 and later, please upgrade your dart as needed.
*** IMPORTANT ***
- On Android, add following permission (if not available) in 'android/app/src/main/AndroidManifest.xml'
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<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.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
- To maintain audio call when app go background, please start foreground service using package 'flutter_foreground_service'
void main() {
runApp(MyApp());
startForegroundService();
}
void startForegroundService() async {
ForegroundService().start();
debugPrint("Started service");
}
-
On iOS
- open Info.plist on iOS Project, then add following description for microphone and camera feature (if not available)
<string>Use camera for Video Call.</string> <key>NSMicrophoneUsageDescription</key> <string>Use microphone for Voice Call.</string> <key>UIApplicationSupportsIndirectInputEvents</key>
- open Podfile of Pods project, then add following script to add camera and microphone configurations
post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| # You can remove unused permissions here # e.g. when you don't need camera permission, just add 'PERMISSION_CAMERA=0' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', ## dart: PermissionGroup.camera 'PERMISSION_CAMERA=1', ## dart: PermissionGroup.microphone 'PERMISSION_MICROPHONE=1', ] end end end
-
And to let call run in background, you need to set background mode to Voice over IP
Flow
Step 1: Obtain Authentication token
-
To use the SDK, you need an authentication token and an AppID.
- To get AppID, please contact us.
- To get Authentication Token, please read this documentation Authentication Flow.
- The example simulate a simple flow to obtain authentication via phone number regsiter.
final phoneToken = await NetworkUtils().createToken(number: myNumber, appId: appId);
-
After you got AppID and Authentication Token, the CallSDK is ready to use now.
Step 2: Start SDK
- Start the SDK with AppID and token from previous step.
void startSDK({
required String appId,
required String token,
required Function(CallEvent eventName, Map<String,dynamic>? data) callEvent,
Function(bool)? completion})
- The callEvent will emit call-related event, for example when call 'connecting', 'connected', 'ended'. The 'CallEvent.audio' will emit when audio route change, for example when user plugin headphone or turn on/off speaker. Based on these events, you can update your Call UI appropriately.
enum CallEvent {
connecting,
connected,
ended,
audio,
// For testing call flow only, remove on distribute this library
notify,
busy,
}
The 'CallEvent.notify' and 'CallEvent.busy' is just for demo.
- 'CallEvent.notify' used for notify when someone call-in. You can follow our example to implement your own notify event. The 'call_id' is necessary for the call. You can also put in other information like 'caller_name'.
if (event == CallEvent.notify) {
if (data != null) {
final inputCallId = BaseEntity.stringValue(data, "call_id");
final callerName = BaseEntity.stringValue(data, "caller_name");
if (inputCallId != null) {
handleReceiveCall(callId: inputCallId, callerNumber: callerName);
}
}
}
- 'CallEvent.busy' used for response caller whether callee is busy or available.
if (name == "busy") {
final inputCallId = BaseEntity.stringValue(info, "call_id");
final status = BaseEntity.stringValue(info, "status");
if (inputCallId == widget.callId && status == "busy") {
_doEndCall();
}
}
Step 3: Call Feature (Make, Cancel, Accept, Decline, End)
MAKE CALL
- To make call, you need to get a CallID. Please read this documentation Call Flow
- The example also simulate flow to get CallId via phone number, just enter your number and the number you want to call.
final newCallId = await NetworkUtils().createCall(appId: appId, callerNumber: registeredPhoneNumber, calleeNumber: calleeNumber);
- After you get CallId, just call 'startCall'. You can put 'callerName' and 'calleeName' if needed.
void startCall({
required String callId,
bool videoCall = false,
String? callerName,
String? calleeName,
Function()? completion
})
CANCEL CALL
- To Cancel the call on caller-side (before callee accept call), just call cancelCall
CallSDK().cancelCall();
ACCEPT CALL
- On the Callee-side, you need to implement mechanism to notify Callee client about the callId, so that they can join the call and start communicate. The example use the CallEvent.notify to get notification and callId.
if (event == CallEvent.notify) {
if (data != null) {
final inputCallId = BaseEntity.stringValue(data, "call_id");
final callerName = BaseEntity.stringValue(data, "caller_name");
if (inputCallId != null) {
handleReceiveCall(callId: inputCallId, callerNumber: callerName);
}
}
- When receive notify about the call. First, 'startCall' just like the caller do to connect with our server. When 'startCall' completed, you can now show incoming-call screen to the user.
void handleReceiveCall({required String callId}) async {
CallSDK().startCall(callId: callId, callerName: callerNumber, calleeName: registeredPhoneNumber, completion: () {
goToCallScreen(
newRole: "callee",
inputCallId: callId,
callerNumber: callerNumber,
calleeNumber: registeredPhoneNumber,
);
});
}
- On the incoming-call screen, to accept call on Callee, just call acceptCall
void handleAcceptButton() async {
await stopRingSound();
if (widget.role == "callee") {
CallSDK().acceptCall(completion: (result) {
if (mounted) {
if (result == true) {
callAccepted = true;
setState(() {
});
} else {
NetworkUtils.showAlertDialog(
context: context,
alertMsg: "Cannot accept call, please allow microphone",
);
}
}
});
}
}
- Within seconds, both caller and callee will connect and the you can start talking.
DECLINE CALL
- To Decline the call on Callee-side, just call declineCall
CallSDK().declineCall();
END CALL
- To stop the on-going call, just call terminateCall. Below code check the current call status and decide which action to do.
final callReady = CallSDK().isCallConnected();
if (callReady) {
CallSDK().terminateCall();
} else {
CallSDK().cancelCall();
}
_closeCallView();
- You receive 'CallEvent.ended' in following cases:
- Call was connected, and the other end the call.
- The callee decline the call.
- The caller cancel the call before you make any decision (Decline/Accept).
Step 4: Handle Microphone and Speaker
- The SDK provide enough function to handle microphone and speaker when in-call
AudioDevice? getCurrentAudioInput();
bool currentDeviceIsSpeaker();
bool currentDeviceIsBluetooth();
bool currentDeviceIsHeadphone();
Future<void> turnOnSpeaker();
Future<void> turnOffSpeaker();
bool haveBluetooth();
bool microMuted();
void setMicroMuted({required bool muted});
- 'turnOnSpeaker' turn the loud speaker on device
- 'turnOffSpeaker' turn off loud speaker and use either in-ear speaker or external device (headphone/bluetooth) if available.
void handleSpeakerButton() async {
final isCurrentSpeaker = CallSDK().currentDeviceIsSpeaker();
if (isCurrentSpeaker == true) {
await CallSDK().turnOffSpeaker();
} else {
await CallSDK().turnOnSpeaker();
}
setState(() {
});
}
- 'microMuted' and 'setMicroMuted' to handle microphone when in-call
void handleMicrophoneButton() async {
final isMuted = CallSDK().microMuted();
CallSDK().setMicroMuted(muted: !isMuted);
await Future.delayed(const Duration(milliseconds: 200));
setState(() {
});
}
Step 5: Stop SDK
- When not used anymore especially when logout, you can stop CallSDK
await CallSDK().stopSDK(completion: () {
}
VIDEO CALL
- This SDK also support video call. To make a video call or a call that can be turn on video later, pass true to parameter 'videoCall' when startCall (both caller and callee). In practice, you should notify callee about the video call.
Get started to video call
CallSDK().startCall(callId: newCallId,
videoCall: true,
videoOnStart: true,
callerName: registeredPhoneNumber,
calleeName: calleeNumber,
completion: () {
if (mounted) {
goToCallScreen(newRole: "caller",
inputCallId: newCallId,
videoCall: videoCall,
calleeNumber: calleeNumber,
callerNumber: registeredPhoneNumber);
}
}
);
-
Set the 'videoCall' parameter to true if you want a video call or an audio call that can be switch to video call later. Pass 'false' if you only want audio call only. Note that you can't turn on video later if you set 'videoCall' to false.
-
Set the 'videoOnStart' to true if you want to turn on local camera on waiting call to connected and when call started.
Outgoing video call | Incoming video call | In call video call |
---|---|---|
Listen to video events
- Obviously you want to display your local video and remote video (video of the other). To do that, just handle CallEvent.videoAdded and CallEvent.videoRemoved
enum CallEvent {
connecting,
connected,
ended,
audio,
videoAdded,
videoRemoved,
}
- The data in CallEvent.videoAdded and CallEvent.videoRemoved contain video information as a map object, including 3 main key
Map<String, dynamic> videoInfo = {
"renderer": renderer,
"is_local": true,
"name":_getName(),
};
- 'renderer' is a RTCVideoRenderer object that you can add to your widget to display video
- 'is_local' tell you this video is your local video or not.
- 'name' name of the video, in this case the name of caller or callee
- Other value you can get from video information is 'video_width', 'video_height' which tells you the size of the video.
Controlling video
- The SDK provide enough function to handle camera when in-call
bool currentCameraOn(); // check if camera is on or off
void switchCameraOn(); // turn on camera, front is default
void switchCameraOff(); // turn off camera
void switchCamera(); // change camera from front to back or vice versa
void switchCameraFront(); // switch to front camera
void switchCameraBack(); // switch to back camera