OMICALL SDK FOR Flutter

The OmiKit exposes the omicall_flutter_plugin library.

The most important part of the framework is :

  • Help to easy integrate with Omicall.
  • Easy custom Call UI/UX.
  • Optimize codec voip for you.
  • Full interface to interactive with core function like sound/ringtone/codec.

Status

Currently active maintenance and improve performance

Running

Install via pubspec.yaml:

omicall_flutter_plugin: ^latest_version

Configuration

Android:

  • Add these settings in build.gradle:
jcenter() 
maven {
    url "https://gitlab.com/api/v4/projects/47675059/packages/maven"
    credentials(HttpHeaderCredentials) {
        name = "Private-Token"
        value = "glpat-AzyyrvKz9_pjsgGW4xfp"
    }
    authentication {
        header(HttpHeaderAuthentication)
    }
}
//in dependencies
classpath 'com.google.gms:google-services:4.3.13'
//under buildscript
allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
         maven {
            url "https://gitlab.com/api/v4/projects/47675059/packages/maven"
            credentials(HttpHeaderCredentials) {
                name = "Private-Token"
                value = "glpat-AzyyrvKz9_pjsgGW4xfp"
            }
            authentication {
                header(HttpHeaderAuthentication)
            }
        }
    }
}

You can refer android/build.gradle to know more information.

  • Add these settings in app/build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.gms.google-services'

You can refer android/app/build.gradle to know more information.

  • Update AndroidManifest.xml:
//need request this permission
<uses-permission android:name="android.permission.INTERNET" />
//add these lines inside <activity>
<intent-filter>
    <action android:name="com.omicall.sdk.CallingActivity"/>
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
//add these lines outside <activity>
<receiver
    android:name="vn.vihat.omicall.omisdk.receiver.FirebaseMessageReceiver"
    android:exported="true"
    android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
</receiver>
<service
    android:name="vn.vihat.omicall.omisdk.service.NotificationService"
    android:exported="false">
</service>

You can refer AndroidManifest to know more information.

  • We registered permissions into my plugin:
<uses-permission android:name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"
    tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
  • Setup push notification: Only support Firebase for remote push notification.

iOS(Object-C):

  • Assets: Add call_image into assets folder to update callkit image. We only support png style.

  • Add variables in Appdelegate.h:

#import <UIKit/UIKit.h>
#import <UserNotifications/UserNotifications.h>
#import <OmiKit/OmiKit-umbrella.h>
#import <OmiKit/Constants.h>
#import <UserNotifications/UserNotifications.h>

PushKitManager *pushkitManager;
CallKitProviderDelegate * provider;
PKPushRegistry * voipRegistry;
  • Edit AppDelegate.m:
#import <OmiKit/OmiKit.h>
#import <omicall_flutter_plugin/omicall_flutter_plugin-Swift.h>
  • Add these lines into didFinishLaunchingWithOptions:
[OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX userNameKey:@"extension" maxCall:1 callKitImage: @"callkit_image"];
provider = [[CallKitProviderDelegate alloc] initWithCallManager: [OMISIPLib sharedInstance].callManager];
voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
pushkitManager = [[PushKitManager alloc] initWithVoipRegistry:voipRegistry];
if (@available(iOS 10.0, *)) {
    [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}

Notes:
- To custom callkit image, you need add image into assets and paste image name into setEnviroment function.

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
    bool value = [SwiftOmikitPlugin processUserActivityWithUserActivity:userActivity];
    return value;
}
  • Add these lines into Info.plist:
<key>NSCameraUsageDescription</key>
<string>Need camera access for video call functions</string>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for make Call</string>
  • Save token for OmiClient: if you added firebase_messaging in your project so you don't need add these lines.
- (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken
{
    // parse token bytes to string
    const char *data = [devToken bytes];
    NSMutableString *token = [NSMutableString string];
    for (NSUInteger i = 0; i < [devToken length]; i++)
    {
        [token appendFormat:@"%02.2hhX", data[i]];
    }
    
    // print the token in the console.
    NSLog(@"Push Notification Token: %@", [token copy]);
    [OmiClient setUserPushNotificationToken:[token copy]];
}

*** Only use under lines when added firebase_messaging plugin in your project ***

  • Setup push notification: We only support Firebase for push notification.
    • Add google-service.json in android/app (For more information, you can refer firebase_core)

    • Add Firebase Messaging to receive fcm_token (You can refer firebase_messaging to setup notification for Flutter)

    • For more setting information, please refer Config Push for iOS

iOS(Swift):

  • Assets: Add call_image into assets folder to update callkit image. We only support png style.

  • Add variables in Appdelegate.swift:

import OmiKit
import PushKit
import NotificationCenter

var pushkitManager: PushKitManager?
var provider: CallKitProviderDelegate?
var voipRegistry: PKPushRegistry?
  • Add these lines into didFinishLaunchingWithOptions:
OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_SANDBOX, prefix: "", userNameKey: "extension", maxCall: 1, callKitImage: "callkit_image")
provider = CallKitProviderDelegate.init(callManager: OMISIPLib.sharedInstance().callManager)
voipRegistry = PKPushRegistry.init(queue: .main)
pushkitManager = PushKitManager.init(voipRegistry: voipRegistry)

Notes:
- To custom callkit image, you need add image into assets and paste image name into setEnviroment function.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    var value = SwiftOmikitPlugin.processUserActivity(userActivity: userActivity)
    return value
}
  • Add these lines into Info.plist:
<key>NSCameraUsageDescription</key>
<string>Need camera access for video call functions</string>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for make Call</string>
  • Save token for OmiClient: if you added firebase_messaging in your project so you don't need add these lines.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let deviceTokenString = deviceToken.hexString
    OmiClient.setUserPushNotificationToken(deviceTokenString)
}

extension Data {
    var hexString: String {
        let hexString = map { String(format: "%02.2hhx", $0) }.joined()
        return hexString
    }
}

*** Only use under lines when added firebase_messaging plugin in your project ***

  • Setup push notification: We only support Firebase for push notification.
    • Add google-service.json in android/app (For more information, you can refer firebase_core)

    • Add Firebase Messaging to receive fcm_token (You can refer firebase_messaging to setup notification for Flutter)

    • For more setting information, please refer Config Push for iOS

*** Important release note ***

We support 2 environments. So you need set correct key in Appdelegate.
- KEY_OMI_APP_ENVIROMENT_SANDBOX support on debug mode
- KEY_OMI_APP_ENVIROMENT_PRODUCTION support on release mode
- Visit on web admin to select correct enviroment.   

Implement

  • Set up for Firebase:
await Firebase.initializeApp();
// If you only use Firebase on Android. Add these line `if (Platform.isAndroid)`
// Because we use APNS to push notification on iOS so you don't need add Firebase for iOS.
  • Important function.

    • Start Serivce: OmiKit need start services and register some events.
      //Call in the root widget
      OmicallClient.instance.startServices();
      
    • Create OmiKit: OmiKit need userName, password, realm, host to init enviroment. ViHAT Group will provides these information for you. Please contact for my sales:
      await OmicallClient.instance.initCall(
        userName: "", 
        password: "",
        realm: "",
        host: "",
        isVideo: true/false,
      );
      
    • Create OmiKit With ApiKey: OmiKit need apikey, username, user id to init enviroment. ViHAT Group will provides api key for you. Please contact for my sales:
       await OmicallClient.instance.initCallWithApiKey(
        usrName: "",
        usrUuid: "",
        isVideo: true/false,
        apiKey: "",
      );
      
    • Get call when user open app from killed status(only iOS):
      final result = await OmicallClient.instance.getInitialCall();
      ///if result is not equal False => have a calling.
      
    • Config push notification: With iOS, I only support these keys: prefixMissedCallMessage, missedCallTitle, userNameKey. With Android, We don't support missedCallTitle:
      OmicallClient.instance.configPushNotification(
        notificationIcon : "calling_face", //notification icon on Android
        prefix : "Cuộc gọi tới từ: ",
        incomingBackgroundColor : "#FFFFFFFF",
        incomingAcceptButtonImage : "join_call", //image name
        incomingDeclineButtonImage : "hangup", //image name
        backImage : "ic_back", //image name: icon of back button
        userImage : "calling_face", //image name: icon of user default
        prefixMissedCallMessage: 'Cuộc gọi nhỡ từ' //config prefix message for the missed call
        missedCallTitle: 'Cuộc gọi nhỡ', //config title for the missed call
        userNameKey: 'uuid', //we have 3 values: uuid, full_name, extension
        channelId: 'channelid.callnotification' // need to use call notification,
        audioNotificationDescription: "" //audio description
        videoNotificationDescription: "" //video descriptipn
      );
      //incomingAcceptButtonImage, incomingDeclineButtonImage, backImage, userImage: Add these into `android/app/src/main/res/drawble`
      
  • Upload token: OmiKit need FCM for Android and APNS to push notification on user devices.

    final token = await FirebaseMessaging.instance.getToken();
    String? apnToken;
    if (Platform.isIOS) {
        apnToken = await FirebaseMessaging.instance.getAPNSToken();
    }
    String appId = 'Bundle id on iOS/ App id on Android'
    await OmicallClient.instance.updateToken(
      fcmToken: token,
      apnsToken: apnToken,
    );
    
  • Other functions:

    • Call with phone number (mobile phone or internal number):
    final result = await OmicallClient.instance.startCall(
        phone, //phone number
        _isVideoCall, //call video or audio. If true is video call. 
    );
    //we will return OmiStartCallStatus with:
    - invalidUuid: uuid is invalid (we can not find on my page)
    - invalidPhoneNumber: sip user is invalid.
    - samePhoneNumber: Can not call same phone number.
    - maxRetry: We try to refresh call but we can not start your call.
    - permissionDenied: Check audio permission.
    - couldNotFindEndpoint: Please login before make your call.
    - accountRegisterFailed: We can not register your account.
    - startCallFailed: We can not start you call.
    - startCallSuccess: Start call successfully.
    - haveAnotherCall: We can not start you call because you are joining another call.
    
    • Call with UUID (only support with Api key):
    final result = OmicallClient.instance.startCallWithUUID(
        uuid, //your user id
        _isVideoCall, //call video or audio. If true is video call. 
    );
    // Result is the same with startCall
    
    • Accept a call:
      OmicallClient.instance.joinCall();
      
    • End a call: We will push a event endCall and return call information for you.
      OmicallClient.instance.endCall().then((value) {
          //value is call information
      });
      Sample output:
      {
         "transaction_id":ea7dff38-cb1e-483d-8576...........,
         "direction":"inbound",
         "source_number":111,
         "destination_number":110,
         "time_start_to_answer":1682858097393,
         "time_end":1682858152181,
         "sip_user":111,
         "disposition":"answered"
      }
      
    • Toggle the audio: On/off audio a call
      OmicallClient.instance.toggleAudio();
      
    • Toggle the speaker: On/off the phone speaker
      OmicallClient.instance.toggleSpeaker();
      
    • Send character: We only support 1 to 9 and * #.
      OmicallClient.instance.sendDTMF(value);
      
    • Get current user information:
      final user = await OmicallClient.instance.getCurrentUser();
      Output Sample:  
      {
          "extension": "111",
          "full_name": "chau1",
          "avatar_url": "",
          "uuid": "122aaa"
      }
      
    • Get guest user information:
      final user = await OmicallClient.instance.getGuestUser();
      Output Sample:  
      {
          "extension": "111",
          "full_name": "chau1",
          "avatar_url": "",
          "uuid": "122aaa"
      }
      
    • Get user information from sip:
      final user = await OmicallClient.instance.getUserInfo(phone: "111");
      Output Sample:  
      {
          "extension": "111",
          "full_name": "chau1",
          "avatar_url": "",
          "uuid": "122aaa"
      }
      
    • Logout:
      OmicallClient.instance.logout();
      
  • Video Call functions: Support only video call, We need enable video in init functions and start call to implements under functions.

    • Switch front/back camera: We use the front camera for first time.
    OmicallClient.instance.switchCamera();
    
    • Toggle a video in video call: On/off video in video call
    - OmicallClient.instance.toggleVideo();
    
    • Register video event: Need to listen remote video ready
     OmicallClient.instance.toggleVideo();
    
    • Remove video event: Need to remove video event
    OmicallClient.instance.removeVideoEvent();
    
    • Local Camera Widget: Your camera view in a call
    LocalCameraView(
      width: double.infinity,
      height: double.infinity,
      onCameraCreated: (controller) {
        _localController = controller;
        //we will return the controller to call some functions.
      },
    )
    
    • Remote Camera Widget: Remote camera view in a call
    RemoteCameraView(
       width: double.infinity,
       height: double.infinity,
       onCameraCreated: (controller) {
         _remoteController = controller;
       },
    )
    
    • More function: Refresh camera
    //camera controller receive when you create camera widget.
    RemoteCameraController? _remoteController;
    LocalCameraController? _localController;
    //refresh remote camera
    void refreshRemoteCamera() {
      _remoteController?.refresh();
    }
    //refresh remote camera
    void localRemoteCamera() {
      _localController?.refresh();
    }
    
  • Event listener:

    • Important event callStateChangeEvent: We provide it to listen call state change.
OmicallClient.instance.controller.callStateChangeEvent
//OmiAction have 2 variables: actionName and data
- Action Name value: 
    - `onCallStateChanged`: Call state changed.
    - `onSwitchboardAnswer`: Switchboard sip is listening. 
+ onCallStateChanged is call state tracking event. We will return status of state. Please refer `OmiCallState`.
+ Incoming call state lifecycle: incoming(receive on foreround state) -> early -> connecting -> confirmed -> disconnected
+ Outgoing call state lifecycle: calling -> early (call created) -> connecting -> confirmed -> disconnected
+ onSwitchboardAnswer have callback when employee answered script call.
  • Other events:
    • Call Quality event: Listen call quality event changed
    OmicallClient.instance.setCallQualityListener((data) {
        final quality = data["quality"] as int;
    });
    //we return `quality` key with: 0 is GOOD, 1 is NORMAL, 2 is BAD
    
    • Mic event: Listen on/off mic in a call
    OmicallClient.instance.setSpeakerListener((data) {
        setState(() {
          isSpeaker = data;
        });
    });
    //data is current speaker status.
    
    • Mute event: Listen on/off muted in a call
    OmicallClient.instance.setMuteListener((data) {
        setState(() {
          isMuted = data;
        });
    });
    //data is current muted status.
    
    • Remote video ready: Listen remote video ready.
    OmicallClient.instance.setVideoListener((data) {
        refreshRemoteCamera(); => need refresh camera
        refreshLocalCamera(); => need refresh camera
    });
    
    • User tab a missed call notification:
    OmicallClient.instance.setMissedCallListener((data) {
        final String callerNumber = data["callerNumber"];
        final bool isVideo = data["isVideo"];
        makeCallWithParams(context, callerNumber, isVideo);
    });
    // data is Map. Data has 2 keys: callerNumber, isVideo
    
    • User tab a call log (only iOS):
    OmicallClient.instance.setCallLogListener((data) {
        final callerNumber = data["callerNumber"];
        final isVideo = data["isVideo"];
        makeCallWithParams(
          context,
          callerNumber,
          isVideo,
        );
    });
    // data is Map. Data has 2 keys: callerNumber, isVideo