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.
- Android Video Collaboration App
- Android Video Quickstart
- Android Getting Started guide
- Android Video SDK API Reference
- Guide for Developing High Quality Video Applications
- Error and Warning Dictionary
To choose the right ConnectOptions 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.
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 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.
1private AudioManager audioManager;2private AudioFocusRequest audioFocusRequest;34private void requestAudioFocus() {5audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);67AudioAttributes audioAttributes = new AudioAttributes.Builder()8.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)9.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)10.build();1112audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)13.setAudioAttributes(audioAttributes)14.setOnAudioFocusChangeListener(focusChangeListener)15.build();1617int result = audioManager.requestAudioFocus(audioFocusRequest);18if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {19// Connect to the Room20}21}
When another app requests audio focus, your app receives a callback through the OnAudioFocusChangeListener. Handle each focus change to provide the best user experience.
1private AudioManager.OnAudioFocusChangeListener focusChangeListener =2new AudioManager.OnAudioFocusChangeListener() {3@Override4public void onAudioFocusChange(int focusChange) {5switch (focusChange) {6case AudioManager.AUDIOFOCUS_GAIN:7// Regained focus. Resume audio if it was paused.8break;9case AudioManager.AUDIOFOCUS_LOSS:10// Permanent loss. Another app has taken focus.11// Consider disconnecting from the Room or notifying the user.12break;13case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:14// Temporary loss, such as an incoming phone call.15// Consider muting the microphone track.16break;17case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:18// Another app requested focus but your app can continue19// at a lower volume. For voice communication, you may20// want to mute instead of ducking.21break;22}23}24};
When the user disconnects from the Room, abandon audio focus so other apps can resume normal audio playback.
1private void abandonAudioFocus() {2if (audioManager != null && audioFocusRequest != null) {3audioManager.abandonAudioFocusRequest(audioFocusRequest);4}5}
When the local participant mutes the microphone, unpublish the microphone track instead of disabling it. When the microphone track is only disabled, the system still shows the microphone indicator, which can confuse users.
1private Room room; // Set when connected to a video room2private LocalAudioTrack micTrack;34private void setMicEnabled(boolean enabled) {5LocalParticipant localParticipant = room.getLocalParticipant();6if (localParticipant == null) {7return;8}910if (enabled) {11micTrack = LocalAudioTrack.create(this, true, "mic");12if (micTrack != null) {13localParticipant.publishTrack(micTrack);14}15} else {16if (micTrack != null) {17localParticipant.unpublishTrack(micTrack);18micTrack.release();19micTrack = null;20}21}22}
To review a similar implementation working in an app, see the reference Android video collaboration app.
When the local participant turns off the camera, unpublish the camera track instead of disabling it. Unpublishing the camera track minimizes resources consumed and there is no impact to the user experience.
1private Room room; // Set when connected to a video room2private Camera2Capturer cameraCapturer;3private LocalVideoTrack cameraTrack;45private void setCameraEnabled(boolean enabled) {6LocalParticipant localParticipant = room.getLocalParticipant();7if (localParticipant == null) {8return;9}1011if (enabled) {12Camera2Enumerator enumerator = new Camera2Enumerator(this);13String frontCameraId = null;14for (String cameraId : enumerator.getDeviceNames()) {15if (enumerator.isFrontFacing(cameraId)) {16frontCameraId = cameraId;17break;18}19}2021if (frontCameraId != null) {22cameraCapturer = new Camera2Capturer(this, frontCameraId);23cameraTrack = LocalVideoTrack.create(this, true, cameraCapturer, "camera");24if (cameraTrack != null) {25localParticipant.publishTrack(cameraTrack);26}27}28} else {29if (cameraTrack != null) {30localParticipant.unpublishTrack(cameraTrack);31cameraTrack.release();32cameraTrack = null;33}34if (cameraCapturer != null) {35cameraCapturer.dispose();36cameraCapturer = null;37}38}39}
To review a similar implementation working in an app, see the reference Android video collaboration app.
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.
1private boolean isRemoteParticipantMicOn(RemoteParticipant participant) {2for (RemoteAudioTrackPublication publication : participant.getRemoteAudioTracks()) {3if ("mic".equals(publication.getTrackName())) {4return publication.isTrackSubscribed() && publication.isTrackEnabled();5}6}7return false;8}910private boolean isRemoteParticipantCameraOn(RemoteParticipant participant) {11for (RemoteVideoTrackPublication publication : participant.getRemoteVideoTracks()) {12if ("camera".equals(publication.getTrackName())) {13return publication.isTrackSubscribed() && publication.isTrackEnabled();14}15}16return false;17}
To review a similar implementation working in an app, see the reference Android video collaboration app.
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@Override2protected void onStop() {3super.onStop();4if (cameraTrack != null) {5cameraTrack.enable(false);6}7}89@Override10protected void onStart() {11super.onStart();12if (cameraTrack != null) {13cameraTrack.enable(true);14}15}
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@Override2public void onConnectFailure(Room room, TwilioException twilioException) {3// Handle error4Log.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:
| Error | Code | Cause | Solution |
|---|---|---|---|
| SignalingConnectionError | 53000 | The client can't establish a connection to Twilio's signaling server. | User should make sure to have a stable internet connection. |
| SignalingServerBusyError | 53006 | Twilio's signaling server can't accept new clients. | User should try joining the Room again after some time. |
| RoomMaxParticipantsExceededError | 53105 | The Room can't accept additional Participants. | Your app should notify the user that the Room is full. |
| RoomNotFoundError | 53106 | The 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. |
| MediaConnectionError | 53405 | The 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@Override2public void onDisconnected(Room room, TwilioException twilioException) {3if (twilioException != null) {4// Handle error5Log.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:
| Error | Code | Cause | Solution |
|---|---|---|---|
| SignalingConnectionDisconnectedError | 53001 | The client failed to reconnect to Twilio's signaling server after a network disruption or handoff. | User needs a stable internet connection. |
| SignalingConnectionTimeoutError | 53002 | The liveliness checks for the connection to Twilio's signaling server failed or the current session expired. | User should rejoin the Room |
| ParticipantDuplicateIdentityError | 53205 | Another client with the same identity joined the Room. | Your app needs each client to create an AccessToken with a unique identity string. |
| MediaConnectionError | 53405 | The 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. |
| RoomNotFoundError | 53106 | After 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. |