Integration Guide
30 min
introduction exotelvoice flutter sdk enables you to add the voip calling feature into your app this document outlines the integration steps the library supports only peer to peer 2 way calls multi party conferencing use cases are not supported licensing create a trial account https //my exotel com in exotel to enable voip calling in the trial account, contact exotel support once exotel support enables the voip capability in the account , a voip exophone will be created as shown below and available in the account under the exophones section sip exophones discussed later in the section refers to exophones of voip type as shown above note sip and voip are used interchangeably in the document flutter sdk integration sdk architecture getting started software package sdk (tar gz file) includes android , ios, lib directories integration guide sample app for reference add sdk to your project download the sdk from github exotel/exotel voip sdk flutter https //github com/exotel/exotel voip sdk flutter untar sdk tar xzvf flutter sdk tar gz copy the following directories in your flutter app flutter sdk/android flutter sdk/ios flutter sdk/lib add native sdk to android directory go to android directory and run make deps it will download and add the aar file(exotel voice release aar) at \<flutter path>/android/exotel voice sdk/ note you might need to change exotel sdk version in makefile to get latest version to ios directory go to ios directory and run make deps it will download and add the aar file(exotelvoice xcframework) at \<flutter path>/ios/ note you might need to change exotel sdk version in makefile to get latest version sdk file structure after extracting the sdk , it contains the following directories android android native code that has integrated exotel android sdk app/src/main/java/com/exotel/voice sample mainactivity java register the flutter engine and create instance of exotelsdkchannel exotelsdkchannel java it is android channel interface class which communicate with flutter via method channel voiceappservice java this class is communicating with android sdk as per android sdk integration docid 4x75laoyftuomb4t4yvfd ios ios native code that has integrated exotel ios sdk runner/appdelegate swift register the flutter engine and create instance of exotelsdkchannel runner/translator exotelsdkchannel swift \ it is ios channel interface class which communicate with flutter via method channel it is also communicating with ios sdk as per ios integration guide docid\ htyc lapafhk4imgbv6jq lib contains flutter dart files exotelsdk exotelvoiceclientfactory dart it is factory class of sdk which provide exotelvoiceclient using method getexotelvoiceclient() exotelvoiceclient java it exotel voice client interface which exposes sdk apis abstract class exotelvoiceclient { void setexotelsdkcallback(exotelsdkcallback callback); void registerplatformchannel(); future getdeviceid(); future initialize(string hostname, string subsribername, string displayname, string accountsid,string subscribertoken); future reset(); future stop(); future dial(string dialto, string message); future mute(); future unmute(); future enablespeaker(); future disablespeaker(); future enablebluetooth(); future disablebluetooth(); future hangup(); future answer(); future senddtmf(string digit); future postfeedback(int? rating, string? issue); future getversiondetails(); future uploadlogs(datetime startdate, datetime enddate, string description); void relaysessiondata(map\<string, dynamic> data) {} } exotelsdkclient dart it is sdk’s implementation class which has implemented exotelvoiceclient it is also flutter channel interface class which communicate with android and ios via method channel exotelsdkcallaback dart it is sdk callback or observer interface that must be implement by your flutter app to handle callbacks from exotelsdkclient abstract class exotelsdkcallback { void oninitializationsuccess(); void oninitializationfailure(string loginstatus); void ondeinitialized(); void onauthenticationfailure(string loginstatus); void oncallinitiated(); void oncallringing(); void oncallestablished(); void oncallended(); void onmissedcall(); void onmediadisrupted(); void onrenewingmedia(); void oncallincoming(string callid, string destination); } methodchannelinvokemethod dart it contains the channel method strings which is used to invoke native method of android/ios from flutter service pushnotificationservice dart sample push notification class dependency add following dependecny in pubspec yaml of your flutter project add permission handling dependency permission handler ^11 3 0 add firebase dependency firebase messaging ^14 7 20 permissions flutter how to request permission static future requestpermissions() async { // you can request multiple permissions at once map\<permission, permissionstatus> statuses = await \[ permission phone, permission microphone, permission notification, permission nearbywifidevices, permission accessmedialocation, permission location, permission bluetoothscan, permission bluetoothconnect, // add other permissions you want to request ] request(); // check permission status and handle accordingly } android android native code must add following permission in androidmanifest xml \<uses permission android\ name="android permission internet" /> \<uses permission android\ name="android permission access network state" /> \<uses permission android\ name="android permission broadcast sticky" /> \<uses permission android\ name="android permission record audio" /> \<uses permission android\ name="android permission modify audio settings"/> \<uses permission android\ name="android permission read phone state" /> \<uses permission android\ name="android permission wake lock" /> \<uses permission android\ name="android permission disable keyguard" /> \<uses permission android\ name="android permission foreground service" /> \<uses permission android\ name="android permission post notifications"/> \<uses permission android\ name="android permission bluetooth"/> \<uses permission android\ name="android permission bluetooth admin"/> \<uses permission android\ name="android permission bluetooth connect" /> \<uses permission android\ name="android permission foreground service" /> \<uses permission android\ name="android permission foreground service phone call"/> \<uses permission android\ name="android permission manage own calls"/> \<uses permission android\ name="android permission receive boot completed"/> ios add following depedencies in your info plist \<key>uibackgroundmodes\</key> \<array> \<string>audio\</string> \<string>fetch\</string> \<string>remote notification\</string> \<string>voip\</string> \</array> \<key>nsmicrophoneusagedescription\</key> \<string>enable microphone access to allow other people to hear you \</string> the permission handler plugin use macros to control whether a permission is enabled you must list the permission you want to use in your application add the following to your podfile file post install do |installer| installer pods project targets each do |target| flutter additional ios build settings(target) target build configurations each do |config| config build settings\['gcc preprocessor definitions'] ||= \[ '$(inherited)', \## dart permissiongroup microphone 'permission microphone=1', ] end end en please refer permission handler | flutter package https //pub dev/packages/permission handler to know in detail about permission handling sdk initialization exotel flutter sdk provide a interface class exotelvoiceclient that exposes initialize api to inialize the sdk(android/ios) // get exotelvoiceclient object from factory class exotelvoiceclient exotelvoiceclient = exotelvoiceclientfactory getexotelvoiceclient(); var mapplicationutil = applicationutils getinstance(context); // implemented exotelsdkcallback exotelvoiceclient setexotelsdkcallback(mapplicationutil); exotelvoiceclient registerplatformchannel(); //initialization try { exotelvoiceclient initialize(hostname!, subscribername!, displayname!,maccountsid!, subscribertoken!) } catch (e) { log("error while login"); } parameter description hostname https //miles apac sg exotel in/v2 subscribername param “subscriber name” returned as part of the subscriber management api to create a subscriber displayname subscriber name accountsid account sid param from api settings page in the exotel dashboard subscribertoken can be gotten from the api in the `get subscriber token` section in the subscriber management api document in the subscribertoken , both the refresh token and access token are base64 encoded subscriber token format example { "refresh token" "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9 eyjpc3mioijleg90zwwilcjzdwiioijbcmnoaxqilcjpyxqioje1nzy2ndc5otksimv4cci6mtu3njc0nzk5oswiy2xpzw50x2lkijoinuuwntg2nuuifq hc3umvfflkipij8r9kcp9o9he9he51le08ro22u7eqs","access token" "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9 eyjpc3mioijleg90zwwilcjzdwiioijbcmnoaxqilcjpyxqioje1nzy2ndc5otksimv4cci6mtu3njc0nzk5oswic2nvcguioij2b2ljzsisimnsawvudf9pzci6ijvfmdu4njvfin0 uqd ahgbit4xqtpfl 567dmjqrklmltpag0kpgb2qma"} note copying manually generated subscriber token has issues best way is to make api calls in source code and pass them by converting to jobject to the exotelvoiceclient initialize(); pass the proper device id to the login api to avoid invalid refresh token error device id is the actual device id in which the app is running using below sample code string deviceid = exotelvoiceclient getdeviceid(); in above code, first you need to get the exotelsdkclient instance register the method handler to handle the callback from android/ios native module set the callback so that exotelsdkclient can send callback to ui for some events like onloggedinsuccess , onloggedinfailure here it is assumed that applicationutil must implement exotelsdkcallback call the login api with required fields userid sub internal working for initialize api invoking method " initialize " via methodchannel with required arguments on method " initialize " , android/ios native method handler will initialize the android/ios library as mentioned in android sdk integration docid 4x75laoyftuomb4t4yvfd when login is successful, client sdk will send callback oninitializationsuccess() android/ios native method handler will then invoke flutter method "inialize result" on flutter method "inialize result" , exotelsdkclient will check status and then send oninitializationsuccess() callback when login is fail, client sdk will send callback oninitializationfailure()/onauthenticationfailure() android/ios native method handler will then invoke flutter method "inialize result" on flutter method "inialize result" , exotelsdkclient will check status and then send oninitializationfailure()/onauthenticationfailure() callback stopping sdk to de initialize the sdk, exotelvoiceclient exposes stop api to de initialize the sdk exotelvoiceclient? stop(); when sdk gets de initialized, client sdk will send callback ondeinitialized() android/ios native method handler will then invoke flutter method "on deinitialized" on flutter method "on deinitialized" , exotelsdkclient will check status and then send on deinitialized() callback managing calls after successful initialization, the library is ready to handle calls dialing out calls or receiving incoming calls is managed through same class exotelsdkclient make outbound calls to make outgoing calls, provide from username and dial to number in dial() api of exotelsdkclient interface to make outgoing calls exotelvoiceclient dial(dialto,message); param mandatory description dialto yes exotelvoice library always routes the call via sip exophone configured for your account sip exophones are different from the pstn / mobile exophones they can be identified using sip prefix contact exotel support to enable sip exophone in your account similar to the number masking workflow, the application backend must maintain the call context between caller and callee on receiving the call at sip exophone, the exotel platform will request the callee details from the application backend to bridge the call refer section dial whom endpoint for details message no the string that will be passed here will be sent to the dialwhom endpoint under the “customfield” param all alphanumeric characters are allowed along with comma `,` colon ` ` and all kinds of brackets {}()\[] in above code, exotelsdkclient will invoke " dial " method on method " dial " , android/ios native method handler will dial the exophone and set the destination number as call context of current user when destination user is getting dialed, client sdk will send callback oncallringing() android/ios native method handler will then invoke flutter method "on call ringing" on flutter method "on call ringing" , exotelsdkclient will check status and then send oncallringing() callback when destination user has accepted the call, client sdk will send callback oncallestablished() android/ios native method handler will then invoke flutter method "on call established" on flutter method "on call established" , exotelsdkclient will check status and then send oncallestablished() callback receive incoming calls the mobile application receives incoming call data in push notification sent by your application backend refer section push notification endpoint for details to receive push notification from push notification server, firebasemessaging must be initialized and subscribed for firebase message refer following pushnotificationservice class which is provided as part of sdk // main dart main() async { await firebase initializeapp(options defaultfirebaseoptions currentplatform); } // pushnotificationservice dart class pushnotificationservice { static final pushnotificationservice instance = pushnotificationservice internal(); firebasemessaging? fcm; static pushnotificationservice getinstance() { return instance; } pushnotificationservice internal(){ fcm = firebasemessaging instance; } future initialize() async { firebasemessaging onmessage listen((remotemessage message) { } ); } } the sample application uses firebase cloud messaging to push call data to the device once received at the mobile app, it is sent to exotleclientsdk through relaysessiondata api firebasemessaging onmessage listen((remotemessage message) { exotelvoiceclient relaysessiondata(message data); }); in above code, you must inialize the pushnotificationservice class when push notification comes, data will be relayed to exotelsdkclient using relayfirebasemessagingdata() exotelsdkclient will invoke "relay session data" method on method "relay session data" , android/ios native method handler will relay data to client sdk which will be processes by client sdk and client sdk send callback onincomingcall() android/ios native method handler will then invoke flutter method "on incoming call" on flutter method "on incoming call" , exotelsdkclient will send callback oncallincoming() hangup the application can call hangup() api on the call object to decline an incoming call or reject ongoing call exotelvoiceclient hangup(); in above code, exotelsdkclient will invoke "hangup" method on method "hangup" , android/ios native method handler will decline and terminate the call when call is successfully rejected, client sdk will send callback oncallended() android/ios native method handler will then invoke flutter method "on call ended" on flutter method "on call ended" , exotelsdkclient will check call status and then send oncallended() callback audio management before the call begins, exotel’s sdk checks the current audio output mode and sets the audio route accordingly by default, it is in the earpiece mode for example, if a wired headset is plugged in, audio will get routed via wired headset speaker phone mode can be enabled and disabled using exotelsdkclient object apis // change audio route to speaker exotelvoiceclient enablespeaker(); // change audio route to phone earpiece exotelvoiceclient disablespeaker(); bluetooth mode can be enabled and disabled using exotelsdkclient object apis this will only work when any bluetooth device is connected to the phone // enable the bluetooth exotelvoiceclient enablebluetooth(); // disable the bluetooth exotelvoiceclient disablebluetooth(); local mic can be muted and unmuted during in call using exotelsdkclient object apis // mute the call exotelvoiceclient mute(); // unmute the call exotelvoiceclient unmute() reporting problems the voice client library logs are stored in the application internal storage any issue along with the logs can be reported by app to exotel using uploadlogs() api of exotelsdkclient // upload logs with description from startdate and enddate exotelvoiceclient uploadlogs(startdate, enddate, description); the api triggers onuploadlogsuccess() or onuploadlogfailure() callback based on the success or failure of the operation reporting call quality feedback call quality can be queried from the user and reported to the exotel platform at the end of the call // upload logs with description from startdate and enddate int rating = 3 callissue issue = background noise exotelvoiceclient postfeedback(rating, issue); the rating is a quality score that can take values 1 to 5 5 excellent quality no issues 4 good quality negligible issues 3 average quality minor audio noise 2 bad quality frequent choppy audio or high audio delay 1 terrible unable to communicate call drop, no audio or one way audio the call issue is a descriptive explanation of the issue no issue no issues observed background noise low audio clarity due to noisy audio choppy audio frequent breaks in audio or garbling in audio high latency significant delay in audio no audio no audio received from far user echo echo during the call platform integration customer api endpoints exotel provides full control to customers to implement call routing business logic for this, customers need to host the following http endpoints to handle callbacks from exotel platform dial whom endpoint host a http endpoint which will be queried by the exotel platform to get to the destination user method get request url (example) https //company com/v1/accounts/exotel/dialtonumber note this url needs to be provided to exotel or configured in the connect applet request body callsid unique call identifier callfrom caller username callto exophone expected response on success, the api should return with response code 200 ok the response body should be a string of type sip \<remoteid> “sip ” tag is needed to hint the exotel platform to connect calls over voip at present, sip and pstn intermixing is not supported any other response is treated as failure remoteid is the username with which the call destination subscriber was registered with exotel platform example request get /v1/accounts/exotelip2ipcalling1/dialtonumber?callsid=743ddcf5a0552050bc37b6d0ff9613bn\&callfrom=sip\ alice\&callto=sip 08040408080\&callstatus=ringing\&direction=incoming\&created=sat,+23+nov+2019+22 00 37\&dialcallduration=0\&starttime=2019 11 23+22 00 37\&endtime=1970 01 01+05 30 00\&calltype=call attempt\&dialwhomnumber=\&flow id=249196\&tenant id=113828\&from=sip\ alice\&to=sip 08040408080\¤ttime=2019 11 23+22 00 37 response { "fetch after attempt" false, "destination" { "numbers" \[ "sip 1234567890" ] }, "outgoing phone number" "08080808080", "record" false, "recording channels" "dual", "max ringing duration" 30, "max conversation duration" 3600, "request id" "2e48100e6b474714b1a64bfa9f5b7a55", "method" "get", "http code" 200, "code" null, "error data" null, "status" null } push notification endpoint exotel implements registration less dialing where a user need not periodically register with sip registrar for receiving incoming calls instead the call details are pushed to the device as push notification using which call can be established this method is beneficial as calls will reach the user even when the app is not in foreground or swipe killed it saves battery since it does not need to keep persistent voip connection when idle exotel will provide call data as payload & payloadversion to your push notification endpoint that needs to be pushed to the client device from your application backend refer section receive incoming call for handling of push notification payload note headers are not supported in the notify endpoints method post request url (example) https //\<your api key> \<your api token>@company com/v1/accounts/\<accountsid>/pushtoclient note this url needs to be configured in exotel platform request body subscribername name with which subscriber registered with exotel platform payload call data which should be passed to the client sdk payloadversion version of payload scheme expected response on success, the api should return with response code 200 ok any other response is treated as failure exotel api endpoints subscriber management customers can manage their subscribers in the exotel platform using the apis listed “subscriber management api” document for clients to be able to use voip calling features they need to be added as subscribers under your account there are two ways in which your client registration with exotel account happens, pre provisioning the customer pre configures the client accounts even before the app is installed by the client refer to the “subscriber management api” document for details on creating client accounts dynamic provisioning a client account is created after the app is installed by the user and signs up with the application backend refer to authentication and authorization for workflow details once client provisioning happens, clients can be managed using subscriber management apis authentication and authorization access to the exotel platform by exotelvoice is authenticated using a set of bearer tokens subscriber token the application backend must obtain these tokens from the exotel platform and provide them to the client on request refer to the “subscriber management api” document for details on receiving the onauthenticationfailure event, the application should request a new subscriber token from its backend and reinitialize the sdk as shown in section initialize library contact exotel support https //hello\@exotel com if you get onauthenticationfailure even after token renewal onauthenticationerror() event provides following error types authentication invalid token token parameters are invalid authentication expired token token expired
