Audio and video calls
This page covers one-to-one and group audio and video calls on Android: starting, incoming calls, answering and hanging up, plus video views and audio devices. Calling and answering go through im.calls; media, audio devices and the system incoming-call UI come from the im-call module, with media carried by the LiveKit service the platform deploys.
Before you start, the platform must have a media service configured and audio and video turned on for your app. The media service's keys live only on the server; the app doesn't need them and can't set them.
| You want to | See |
|---|---|
| Add the module, declare the incoming-call screen, request permissions | Installation and permissions |
| Start, answer and hang up | Start, answer and end |
| Show call status, query call history | State and history |
| Show video, switch camera and speaker | Video and audio devices |
| Lock-screen incoming calls, the system call UI | System incoming calls and background |
| Handle an insufficient balance | App balance and admission |
Installation and permissions
Add im-call and configure the JitPack repository from the overview. Pass DRCallOptions at initialization:
import com.deeprespond.im.*
import com.deeprespond.im.call.*
val options = DRClientOptions(
appKey = "org#app",
apiUrl = "https://api.example.com",
call = DRCallOptions(incomingCallActivity = IncomingCallActivity::class.java),
)IncomingCallActivity is your app's own incoming-call screen (opened for full-screen incoming calls and when the user taps "Answer"), and it must be declared in the manifest (see below). Without incomingCallActivity, the app's launch Activity opens. The SDK's manifest already declares the call service, notification, audio, camera and other permissions, but your app must request them at runtime:
- Voice calls need
RECORD_AUDIO; video calls also needCAMERA. - Request notification and Bluetooth permissions according to the Android version.
- From Android 14, whether full-screen notifications can be used and the Google Play permission declarations are your app's responsibility.
<activity
android:name=".IncomingCallActivity"
android:exported="false"
android:showWhenLocked="true"
android:turnScreenOn="true" />DRCallOptions also has:
| Option | Default | Description |
|---|---|---|
useTelecom | true | Integrates with the system call framework (Jetpack Core-Telecom, Android 8 and later) |
autoBusy | true | Declines incoming calls as busy while the user is on a phone call or a call in another app |
ringtone | The system ringtone | Incoming-call ringtone; only takes effect the first time the call notification channel is created |
To receive incoming calls when the app isn't running, also set up push. Only calls.supported being true means media is wired up; being able to call im.calls doesn't mean media is configured.
Start, answer and end
import com.deeprespond.im.*
val call = im.calls.start(CallMedia.AUDIO, CallTarget.User("bob"))
// 拿到句柄后,按业务界面管理当前通话。
println(call.callId)- For group calls use
CallTarget.Group(groupId, usernames?); for video, useVIDEOinstead ofCallMedia.AUDIO. incomingis the incoming call not yet handled (aStateFlow), andcurrentis the current call.- When a
CallIncomingevent arrives orincomingchanges, show your incoming-call screen with that call'sCallHandle:
call.join() // 用户接听。
call.setMicrophoneEnabled(false)
call.setCameraEnabled(false)
call.hangup() // 本设备退出通话。| Method | What it does |
|---|---|
join() | Answers an incoming call, or joins an ongoing group call |
reject(reason) | Declines; reason is DECLINED or BUSY |
hangup() | Leaves the call; others can continue |
end() | Ends the whole call for everyone (requires permission) |
invite(usernames) | Invites more people, returning a result per person |
resume() | Restores an interrupted media connection |
Mind the difference between hangup and end: a regular "Hang up" button uses hangup.
State and history
call.snapshotis the call's state, including the server's call information and local media state;call.tracksare the audio and video tracks.call.datareceives data messages during the call, andsendData(ByteArray)sends them. They only exist during the call and aren't a substitute for reliable chat messages.get(callId)gets a call's handle, andhistory(cursor?, limit?)pages through call history.groupCall(groupId)gets the call going on in a group, andgroupCallBannerreturns what a "group call in progress" banner needs.CallChangedandGroupCallChangedare emitted when calls change; update your UI from them.
Video and audio devices
import com.deeprespond.im.call.*
val view = DRCallVideoView(context)
view.scaleType = DRCallVideoView.ScaleType.FIT
view.mirror = false
val track = call.tracks.value.local
view.bind(call, track)
// 轨道变化时重新 bind;页面不显示时 unbind。
view.unbind()DRCallVideoView only renders once it's actually in the view hierarchy; the track passed to bind can be null (no picture). Rebind whenever local or remote tracks change, and don't cache old tracks.
import com.deeprespond.im.call.*
call.switchCamera()
call.setSpeakerphoneOn(true)
val devices = call.audioEndpoints.value
val selected = devices.available.firstOrNull()
if (selected != null) call.setAudioEndpoint(selected)audioEndpoints is a StateFlow holding the available devices (available) and the current one (current); types include headset, earpiece, speaker, Bluetooth and streaming devices. Switching may fail when permissions are missing or the device is in the wrong state.
System incoming calls and background
DRCallUI.of(im) returns the controller for the system incoming-call UI, or null without the call option:
| Method | What it does |
|---|---|
canUseFullScreenIntent / openFullScreenIntentSettings | Checks whether full-screen incoming-call notifications can be used, and opens the matching system setting |
callChannelEnabled | Checks whether the incoming-call notification channel was turned off |
intentCall(intent) | Gets the call from the Intent that launched the incoming-call screen |
actions | Collects what the user did in the notification or the system call UI (answer, decline and so on) |
The SDK uses Core-Telecom to integrate with system calling and a foreground service to keep calls going in the background. Your app handles permissions, the lock-screen incoming-call screen and screen lifecycle. Test background calls, being woken by an incoming call after the process was killed, and each phone brand's battery saving on real devices. For the full API, see CallHandle.
Standalone channels
The calling, ringing and answering on this page belong to calls. For meetings and voice rooms that don't need an IM group, use standalone RTC channels: channels don't ring and don't use the incoming-call UI or notifications. Both share the same media service but are billed separately.
App balance and admission
For apps on the new billing, calls and standalone channels share the app's standard audio/video minutes balance. Whether a call can start is checked by the server:
- The balance must be above zero.
- The app's policies must allow it, and the number of concurrent calls and channels must be within the limit.
- The platform must have a media service configured that can meter usage.
calls.supported only says this device has media capabilities; it doesn't mean the balance is sufficient or that the server will allow the call. Tell users what happened based on the error code and reason the server returns.
