Voice SDKs

Voice SDK for Android

dm.sent:voice-client lets an Android app place Sent Voice calls: to a phone number, to another user of your app, or into a conference. Your backend mints a short-lived voice token for each user with the Sent API. The SDK registers the device with it, places calls, and reports how they go. What rings when a user calls is decided by your callback, as for every call; see Answering Calls.

This SDK places calls. Receiving calls on a phone needs push credentials registered with Sent, and arrives as a minor version of this SDK with no change to its shape.

Installation

From Maven Central, for Android 8.0 (API 26) or later:

dependencies {
    implementation("dm.sent:voice-client:0.1.0")
}

dm.sent:voice-client-core comes with it. Calls use the microphone: the library declares RECORD_AUDIO, and your app asks the user for it at runtime before the first call.

Quick Start

Mint voice tokens on your backend

Mint tokens on your server with your Sent API key, never in the app, for the user who is signed in:

// On your server, behind a route such as GET /api/voice-token
const response = await fetch('https://api.sent.dm/v3/voice/tokens', {
  method: 'POST',
  headers: { 'x-api-key': process.env.SENT_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ identity: user.id }),
});
const { data } = await response.json();
return data.token;

identity names the user in calls: letters, digits, - and _, up to 200 characters. The user is bound to your default voice number unless you pass number, one of the numbers you turned voice on for with POST /v3/channels/voice, and the token lasts ttl seconds (600 by default, at most 3600). Return the token as text from an endpoint of your own, such as /api/voice-token. The token is opaque: read the identity, number, and expiry from the minting response, not from the token. The token endpoint and the binding rules are covered in Calls From Your App.

Register and call

import dm.sent.voice.SentVoice
import dm.sent.voice.core.CallEvent
import dm.sent.voice.core.SentVoiceOptions

val client = SentVoice.client(applicationContext, SentVoiceOptions(tokenProvider = { myBackend.voiceToken() }))

// From a coroutine: register and connect suspend
client.register()

val call = client.connect("+14155551234")
val listener = call.on { event ->
    when (event) {
        is CallEvent.Connected -> Log.i("voice", "Connected")
        is CallEvent.Disconnected -> Log.i("voice", "The call ended as ${event.state}")
        else -> Unit
    }
}

Keep one client for the life of the app. register() fetches a token through tokenProvider, registers with it, and refreshes it before it expires. Events arrive on the thread that reported them, so hop to the main thread before touching views. A listener stays until you call cancel() on the token on returns.

Client Options

SentVoiceOptions takes the following options. Only tokenProvider is required.

OptionDescription
tokenProviderA suspend function that returns a voice token from your backend, which mints it with POST /v3/voice/tokens. Called by register() and again before each token expires
registerRetriesHow many more times registering or refreshing the token is retried after a temporary failure, such as a network error. Default 3
baseUrlThe Sent API origin the token exchange and telemetry use. Default https://api.sent.dm
telemetryDisabledTurns off the call quality and error reports the SDK sends to Sent. Default false
logA function that receives the SDK's log lines with their level. Voice tokens never appear in them

Client

The client holds one registration and the call that runs on it.

PropertyDescription
stateUNREGISTERED, REGISTERING, REGISTERED, OFFLINE or DESTROYED. After destroy() the state is DESTROYED and every method throws
identityThe identity the current token was minted for. null until registered
numberThe number the identity is bound to. null until registered
activeCallThe call in progress, or null
MethodDescription
register()Fetches a token from tokenProvider, registers with it, and keeps it refreshed. Returns once the client is REGISTERED
unregister()Drops the registration and leaves a call in progress alone
destroy()Ends the call and tears the client down for good
connect(to)Calls a phone number in E.164 format or another user of your app by identity. See Calls
joinConference(name)Joins one of your account's rooms by name
on(listener)Adds a listener for the events below and returns a token; cancel() on it removes the listener

register(), unregister(), destroy(), connect() and joinConference() are suspend functions.

EventFires
ClientEvent.Registeringregister() started
ClientEvent.RegisteredThe client is registered and ready for calls
ClientEvent.Unregisteredunregister() finished
ClientEvent.ActiveCallChangedA call was placed, or the call in progress ended
ClientEvent.TokenWillExpireThe token is about to expire and the next refresh is due
ClientEvent.OfflineThe refresh retries ran out. The client tries again about every 30 seconds; register() tries at once
ClientEvent.ErrorSomething failed outside a method call, such as a refresh

Calls

connect(to) and joinConference(name) return a Call once the provider accepted it; the call then moves through RINGING, ANSWERED and CONNECTED to COMPLETED, FAILED, BUSY or NO_ANSWER. One call at a time: a second connect while one is in progress throws CALL_IN_PROGRESS.

PropertyDescription
id, direction, from, to, startedAtMsWhat the call is
stateThe call's state, as above
isMutedWhether the microphone is muted
connectedTimeMs, endedTimeMsWhen the parties were joined, and when the call ended
errorThe exception that ended the call, when one did
MethodDescription
hangup()Ends the call. A suspend function
mute(muted)Mutes or unmutes the microphone
sendDigits(digits)Sends DTMF digits, for example to a phone menu
on(listener)Adds a listener for the events below and returns a token
EventFires
CallEvent.Ringing, Answered, ConnectedThe call progressed
CallEvent.Reconnecting, ReconnectedThe media path dropped and came back
CallEvent.DisconnectedThe call ended in state, with the error that ended it when there is one
CallEvent.MuteChangedmute() took effect
CallEvent.QualityThe media quality degraded
CallEvent.ErrorSomething failed on the call

Errors

Every failure is a SentVoiceException with a code, a category, whether it is retriable, and a message you can show. The codes are the same on every platform:

CodeMeaning
TOKEN_INVALIDThe value from tokenProvider is not a voice token, or Sent refused it
TOKEN_EXPIREDThe token expired and could not be refreshed
NOT_REGISTEREDA call was placed while the client was not REGISTERED
MEDIA_PERMISSION_DENIEDThe user refused the microphone
INVALID_ADDRESSto is neither an E.164 number nor an identity, or a room name is malformed
CALL_IN_PROGRESSA call was placed while one was in progress
CALL_REJECTEDThe other party, or your callback, declined the call
CALL_FAILEDThe call could not be placed or dropped. Calls to a phone number need a number on the token; the message says so when there is none
NETWORKSent or the provider could not be reached. Retriable
CAPABILITY_UNSUPPORTEDThe device or the SDK version cannot do what was asked
UNKNOWNAnything else; message says what

Telemetry

The SDK reports call quality and errors to Sent so support can see what a user saw, batched and sent in the background. Set telemetryDisabled to turn it off. Nothing in the reports identifies the person beyond the identity your backend minted the token for.

On this page