Skip to contentSkip to navigationSkip to topbar
Page toolsOn this page
Looking for more inspiration?Visit the

Android Video Application Recommendations and Best Practices


This guide provides recommendations and best practices for building an Android video app using the Twilio Programmable Video Android SDK.


To choose the right ConnectOptions(link takes you to an external page) when initializing the video call for your use case, see the Developing High Quality Video Applications guide.


On Android, multiple applications can stream audio at the same time. Your app must manage audio focus to make sure it behaves correctly when other apps play audio. Without proper audio focus handling, your app might stream audio during an incoming phone call or lose audio when placed in the background.

For more details on how audio focus works across Android versions, see the Android audio focus documentation(link takes you to an external page).

(information)

Info

The AudioFocusRequest API used in this section requires Android API level 26 (Android 8.0) or later. The Video Android SDK supports API level 25 and higher.

Request audio focus

request-audio-focus page anchor

Request audio focus before connecting to a Room. Use AudioAttributes.CONTENT_TYPE_SPEECH so the system knows your app is handling voice communication. This also prevents automatic ducking on Android 12 and later, which would otherwise lower your app's audio volume when another app requests transient focus.

1
private AudioManager audioManager;
2
private AudioFocusRequest audioFocusRequest;
3
4
private void requestAudioFocus() {
5
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
6
7
AudioAttributes audioAttributes = new AudioAttributes.Builder()
8
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
9
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
10
.build();
11
12
audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
13
.setAudioAttributes(audioAttributes)
14
.setOnAudioFocusChangeListener(focusChangeListener)
15
.build();
16
17
int result = audioManager.requestAudioFocus(audioFocusRequest);
18
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
19
// Connect to the Room
20
}
21
}

Handle audio focus changes

handle-audio-focus-changes page anchor

When another app requests audio focus, your app receives a callback through the OnAudioFocusChangeListener. Handle each focus change to provide the best user experience.

1
private AudioManager.OnAudioFocusChangeListener focusChangeListener =
2
new AudioManager.OnAudioFocusChangeListener() {
3
@Override
4
public void onAudioFocusChange(int focusChange) {
5
switch (focusChange) {
6
case AudioManager.AUDIOFOCUS_GAIN:
7
// Regained focus. Resume audio if it was paused.
8
break;
9
case AudioManager.AUDIOFOCUS_LOSS:
10
// Permanent loss. Another app has taken focus.
11
// Consider disconnecting from the Room or notifying the user.
12
break;
13
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
14
// Temporary loss, such as an incoming phone call.
15
// Consider muting the microphone track.
16
break;
17
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
18
// Another app requested focus but your app can continue
19
// at a lower volume. For voice communication, you may
20
// want to mute instead of ducking.
21
break;
22
}
23
}
24
};

When the user disconnects from the Room, abandon audio focus so other apps can resume normal audio playback.

1
private void abandonAudioFocus() {
2
if (audioManager != null && audioFocusRequest != null) {
3
audioManager.abandonAudioFocusRequest(audioFocusRequest);
4
}
5
}

Mute and unmute the microphone

mute-and-unmute-the-microphone page anchor

When the local participant mutes the microphone, unpublish the microphone track instead of disabling it(link takes you to an external page). When the microphone track is only disabled, the system still shows the microphone indicator, which can confuse users.

1
private Room room; // Set when connected to a video room
2
private LocalAudioTrack micTrack;
3
4
private void setMicEnabled(boolean enabled) {
5
LocalParticipant localParticipant = room.getLocalParticipant();
6
if (localParticipant == null) {
7
return;
8
}
9
10
if (enabled) {
11
micTrack = LocalAudioTrack.create(this, true, "mic");
12
if (micTrack != null) {
13
localParticipant.publishTrack(micTrack);
14
}
15
} else {
16
if (micTrack != null) {
17
localParticipant.unpublishTrack(micTrack);
18
micTrack.release();
19
micTrack = null;
20
}
21
}
22
}

To review a similar implementation working in an app, see the reference Android video collaboration app(link takes you to an external page).

Turn the camera on and off

turn-the-camera-on-and-off page anchor

When the local participant turns off the camera, unpublish the camera track instead of disabling it(link takes you to an external page). Unpublishing the camera track minimizes resources consumed and there is no impact to the user experience.

1
private Room room; // Set when connected to a video room
2
private Camera2Capturer cameraCapturer;
3
private LocalVideoTrack cameraTrack;
4
5
private void setCameraEnabled(boolean enabled) {
6
LocalParticipant localParticipant = room.getLocalParticipant();
7
if (localParticipant == null) {
8
return;
9
}
10
11
if (enabled) {
12
Camera2Enumerator enumerator = new Camera2Enumerator(this);
13
String frontCameraId = null;
14
for (String cameraId : enumerator.getDeviceNames()) {
15
if (enumerator.isFrontFacing(cameraId)) {
16
frontCameraId = cameraId;
17
break;
18
}
19
}
20
21
if (frontCameraId != null) {
22
cameraCapturer = new Camera2Capturer(this, frontCameraId);
23
cameraTrack = LocalVideoTrack.create(this, true, cameraCapturer, "camera");
24
if (cameraTrack != null) {
25
localParticipant.publishTrack(cameraTrack);
26
}
27
}
28
} else {
29
if (cameraTrack != null) {
30
localParticipant.unpublishTrack(cameraTrack);
31
cameraTrack.release();
32
cameraTrack = null;
33
}
34
if (cameraCapturer != null) {
35
cameraCapturer.dispose();
36
cameraCapturer = null;
37
}
38
}
39
}

To review a similar implementation working in an app, see the reference Android video collaboration app(link takes you to an external page).

Display media status in the user interface

display-media-status-in-the-user-interface page anchor

When displaying track status in the user interface, check if the track is subscribed and enabled. Tracks may be disabled instead of unpublished for some edge cases or to optimize the experience for users on a platform that isn't using the Android Video SDK.

1
private boolean isRemoteParticipantMicOn(RemoteParticipant participant) {
2
for (RemoteAudioTrackPublication publication : participant.getRemoteAudioTracks()) {
3
if ("mic".equals(publication.getTrackName())) {
4
return publication.isTrackSubscribed() && publication.isTrackEnabled();
5
}
6
}
7
return false;
8
}
9
10
private boolean isRemoteParticipantCameraOn(RemoteParticipant participant) {
11
for (RemoteVideoTrackPublication publication : participant.getRemoteVideoTracks()) {
12
if ("camera".equals(publication.getTrackName())) {
13
return publication.isTrackSubscribed() && publication.isTrackEnabled();
14
}
15
}
16
return false;
17
}

To review a similar implementation working in an app, see the reference Android video collaboration app(link takes you to an external page).


Run the app in the background

run-the-app-in-the-background page anchor

Handle camera interruptions

handle-camera-interruptions page anchor

When the app moves to the background, the system can interrupt camera capture. Disable the camera track instead of unpublishing it, since interruptions can be frequent due to things like notifications. When the app returns to the foreground, re-enable the track.

1
@Override
2
protected void onStop() {
3
super.onStop();
4
if (cameraTrack != null) {
5
cameraTrack.enable(false);
6
}
7
}
8
9
@Override
10
protected void onStart() {
11
super.onStart();
12
if (cameraTrack != null) {
13
cameraTrack.enable(true);
14
}
15
}

Handle audio interruptions

handle-audio-interruptions page anchor

When another app interrupts audio, such as receiving a phone call, audio recording and playback in the video app can be interrupted. If your app doesn't handle audio focus changes properly, it might continue to stream audio during the phone call, or it could lose audio entirely.

To handle audio interruptions, implement the OnAudioFocusChangeListener as described in the Manage audio focus section.


This section lists some of the important errors raised by the Android Video SDK and provides recommendations on how to handle them to provide the best user experience.

These errors are raised when the app can't connect to a Room. Use the Room.Listener to receive connection errors.

1
@Override
2
public void onConnectFailure(Room room, TwilioException twilioException) {
3
// Handle error
4
Log.e(TAG, "Failed to connect: " + twilioException.getMessage());
5
}

The following table describes the most common connection errors and proposes ways for the application to handle them:

ErrorCodeCauseSolution
SignalingConnectionError53000The client can't establish a connection to Twilio's signaling server.User should make sure to have a stable internet connection.
SignalingServerBusyError53006Twilio's signaling server can't accept new clients.User should try joining the Room again after some time.
RoomMaxParticipantsExceededError53105The Room can't accept additional Participants.Your app should notify the user that the Room is full.
RoomNotFoundError53106The client attempted to connect to a Room that doesn't exist.If you disable ad-hoc Room creation, your app must create a Room using the REST API before clients attempt to join.
MediaConnectionError53405The client can't establish a media connection with the Room.User requires a stable internet connection and media traffic allowed to and from Twilio.

When the app disconnects from the Room, this raises errors. To receive disconnect errors, use the Room.Listener.

1
@Override
2
public void onDisconnected(Room room, TwilioException twilioException) {
3
if (twilioException != null) {
4
// Handle error
5
Log.e(TAG, "Disconnected with error: " + twilioException.getMessage());
6
}
7
}

The following table describes the most common disconnection errors and proposes ways for the application to handle them:

ErrorCodeCauseSolution
SignalingConnectionDisconnectedError53001The client failed to reconnect to Twilio's signaling server after a network disruption or handoff.User needs a stable internet connection.
SignalingConnectionTimeoutError53002The liveliness checks for the connection to Twilio's signaling server failed or the current session expired.User should rejoin the Room
ParticipantDuplicateIdentityError53205Another client with the same identity joined the Room.Your app needs each client to create an AccessToken with a unique identity string.
MediaConnectionError53405The client failed to re-establish its media connection with the Room after a network disruption or handoff.User needs a stable internet connection and a firewall that allows media traffic to and from Twilio.
RoomNotFoundError53106After a network disruption or handoff, the client tried to reconnect to Twilio's signaling server but the Room ended during the disconnection.Your app should notify the user that the Room has ended.