Skip to main content
Version: 3.x.x

Developer Experience Guidelines - Android

These practices cover when to start features, when to release custom tracks, and how to handle errors.

Initiate Key Features After Room Join Event​

Start specific features only after the onRoomJoined() event has been triggered.

  • Trigger Key Actions After Joining the Room : Room actions called before onRoomJoined() fail with 3035 (ERROR_ACTION_PERFORMED_BEFORE_ROOM_JOINED). If you use any of the following features, or want to act as soon as the room joins, start them from onRoomJoined():

    • Chat Subscription: To enable in-room chat functionality, subscribe to the chat topic after the onRoomJoined() event is triggered. This ensures that messages are reliably received by participants. subscribe() is a suspend function, so call it from a coroutine. Called before the join completes, it throws a VideoSDKException with code 3035.

      private val chatListener = object : PubSubMessageListener {
      override fun onMessageReceived(message: PubSubMessage) {
      Log.d("VideoSDK", "${message.senderName} says: ${message.message}")
      }
      }

      private val roomEventListener = object : RoomEventListener() {
      override fun onRoomJoined(switchRoomId: String?) {
      lifecycleScope.launch {
      try {
      // Subscribe once the room is joined
      room.pubSub.subscribe("CHAT", chatListener)
      } catch (e: VideoSDKException) {
      Log.e("VideoSDK", "subscribe failed: ${e.code} ${e.name}")
      }
      }
      }
      }

      onRoomJoined() fires again after a reconnect that needed a fresh join and after switchTo(). The SDK keeps your subscription through both, so subscribing again with the same listener throws 3073, which the catch above handles. Always pass the same listener object: with a new one, your app receives every message twice.

    • Device Management: If you need users to use specific audio or video devices when the room is first joined, use the setAudioDevice() method of the Room class for audio. It works before join() too. For the camera, create a track for the selected camera with createCameraVideoTrack() and pass it as customCameraTrack to VideoSDK.createRoom(), or call changeCam() once the room is joined. setSelectedVideoDevice() only records the selection and does not switch the camera.

    • Recording and Transcription: To automatically start recording or transcription as soon as the room begins, configure the autoStartConfig in the Create Room API. For detailed information, refer to the documentation here.

Dispose Custom Tracks When Necessary​

In most cases the SDK disposes of tracks itself. In the cases below, dispose of custom tracks yourself:

  1. When Enabling/Disabling the Camera on a Precall Screen:
    • If your application includes a precall screen and you want to ensure that the device's camera is not used when the camera is disabled, you must call stop() on the custom video track. Otherwise, the device’s camera will continue to be used even when the camera is off.
    • Additionally, remember to create a new track when the user enables the camera again.
    • Call VideoSDK.initialize() before you create the track. Otherwise createCameraVideoTrack() throws an IllegalStateException.
    • createCameraVideoTrack() is a suspend function, so call it from a coroutine. It throws a VideoSDKException when the camera can't be opened.
    • Clear the preview with removeTrack(). Call releaseSurfaceViewRenderer() only when the view is destroyed, because the view can't render again after it.
    • If you don’t need to manage the camera's usage on the device level (i.e., you’re okay with the camera being used whether it’s enabled or disabled), you can skip this step.
    • Here's how you can manage a custom track on a precall screen:
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import live.videosdk.rtc.android.CustomCameraTrackConfig
import live.videosdk.rtc.android.CustomStreamTrack
import live.videosdk.rtc.android.VideoSDK
import live.videosdk.rtc.android.VideoView
import live.videosdk.rtc.android.lib.VideoSDKException
import live.videosdk.rtc.android.mediaDevice.CameraDeviceInfo
import live.videosdk.rtc.android.mediaDevice.FacingMode
import videosdk.org.webrtc.VideoTrack

class JoinActivity : AppCompatActivity() {
private var videoTrack: CustomStreamTrack? = null
private var joinView: VideoView? = null
private var isCamEnabled = false

private fun toggleCam(cameraDevice: CameraDeviceInfo?) {
if (isCamEnabled) {
videoTrack?.stop() // Release the camera
videoTrack = null
joinView!!.removeTrack() // Remove the video track from the view
joinView!!.visibility = View.INVISIBLE
isCamEnabled = false
} else {
lifecycleScope.launch {
try {
// Re-enabling the camera by creating a new track
val track = VideoSDK.createCameraVideoTrack(
encoderConfig = CustomCameraTrackConfig.H720P_W960P,
facingMode = FacingMode.front,
multiStream = true,
cameraId = cameraDevice?.deviceId, // the user's selected camera
)
videoTrack = track
// display in localView
joinView!!.addTrack(track.track as? VideoTrack)
joinView!!.visibility = View.VISIBLE
isCamEnabled = true
} catch (e: VideoSDKException) {
Log.e("VideoSDK", "Camera failed: ${e.code} ${e.name}")
}
}
}
}
}

Listen for Error Events​

Listening to error events enables your application to handle unexpected issues efficiently, providing users with clear feedback and potential solutions. Error codes pinpoint specific problems, whether from configuration settings, account restrictions, permission limitations, or device constraints. Here are recommended solutions based on common error categories:

  1. Errors associated with Organization: If you encounter errors related to your organization (e.g., account status or participant limits), reach out to support at support@videosdk.live or reach out to us on Discord for assistance.

  2. Errors associated with Token: For errors related to authentication tokens, ensure the token is valid and hasn’t expired, then try the request again.

  3. Errors associated with Room and Participant: Check that roomId and participantId are correctly passed and valid. Also, ensure each participant has a unique participantId: when the same participantId joins from another device, the earlier session is ended.

  4. Errors associated with Add-on Service: If you encounter errors with add-on services (such as recording or streaming), try restarting the service after receiving a failure event. For example, if a START_RECORDING_FAILED error event occurs, attempt to call the startRecording() method again. If you're using webhooks, you can also retry when the recording-failed webhook arrives.

  5. Errors associated with Media: Inform the user about media access issues, such as microphone or camera permissions. Design the UI to clearly indicate what is preventing the mic or camera from enabling, helping the user understand the problem.

  6. Errors associated with Track & Codec: Ensure that the track you’ve created and passed to enable the mic or camera methods meets the required specifications.

  7. Errors associated with Actions: If you need to perform actions as soon as a room joins, only initiate them after receiving the onRoomJoined() event, otherwise they fail with 3035. While the room is reconnecting, actions are refused with 3065, so try them again once onRoomStateChanged() reports CONNECTED.

  8. Errors associated with Participant and Room: The server refused a request that targets a remote participant or changes the room, such as remove() or changeMode(). Nothing changed, so you can try the action again.

  9. Errors associated with Collaboration: A PubSub, data message, file sharing, realtime store, whiteboard or translation request failed. Check that the room is connected and that what you send is within the size limits, then try again.

  10. Other errors: The cause is not known. If the error keeps coming back, reach out to support at support@videosdk.live.

  • Here's how to listen for the error event. onError can be called off the main thread, so post to the main thread before you update the UI.
private val roomEventListener: RoomEventListener = object : RoomEventListener() {
//..
override fun onError(error: VideoSDKError) {
// compare with == : errors are equal when their codes are equal
if (error == VideoSDKError.ERROR_ROOM_RECONNECTING) {
// try the action again once the room is CONNECTED
}
Log.d("#error", "Error is: ${error.code} ${error.name} ${error.message}")
}
}
  • Most actions are suspend functions that throw a VideoSDKException when they are refused or fail, and some of these errors never reach onError. An exception you don't catch crashes the app, so catch it where you call the action:
lifecycleScope.launch {
try {
room.unmuteMic()
} catch (e: VideoSDKException) {
Log.d("#error", "Error is: ${e.code} ${e.name} ${e.message}")
}
}

Got a Question? Ask us on discord