Image Capturer - Android
This guide shows how to capture a still image of a participant's camera during a call.
A typical use is Video KYC, where the user holds up an identity document to the camera.
captureImage()
- Call
room.localParticipant.captureImage()to capture an image from your own camera. With video effects on, the image includes them: it is the frame other participants see. captureImage()is asuspendfunction. Call it from a coroutine, such aslifecycleScope.launch(fromandroidx.lifecycle:lifecycle-runtime-ktx). It returns the image as abase64JPEG string.- You can pass a maximum
widthandheightfor the image. Both are optional.widthcomes beforeheight, so pass them as named arguments. If not provided, or0, the image keeps the camera's own size. - The camera must be on. Otherwise
captureImage()throwsVideoSDKExceptionwith3062(ERROR_WEBCAM_NOT_ENABLED). If the capture fails, it throwsVideoSDKExceptionwith3069(ERROR_CAPTURE_IMAGE_FAILED), which is also reported ononError.
private fun imageCapture() {
lifecycleScope.launch {
try {
val base64Image = room!!.localParticipant.captureImage(width = 400, height = 400)
Log.d("VideoSDK", "capturedImage in base64 format: $base64Image")
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in imageCapture: ${e.code} ${e.message}")
}
}
}
Capture with flash
- To light the scene, pass
flash = CaptureFlash.ON. The light stays on for about half a second while the image is captured. - The camera's flash is used when it has one. A front camera without a flash uses the screen flash you set with
VideoSDK.setScreenFlash().ScreenFlash.whiteOverlay(activity)covers the screen with a bright white view. With no screen flash set, the image is captured unlit.
private fun imageCaptureWithFlash() {
VideoSDK.setScreenFlash(ScreenFlash.whiteOverlay(this))
lifecycleScope.launch {
try {
val base64Image = room!!.localParticipant.captureImage(flash = CaptureFlash.ON)
Log.d("VideoSDK", "capturedImage in base64 format: $base64Image")
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in imageCapture: ${e.code} ${e.message}")
}
}
}
You can only capture an image of the local participant. Calling captureImage() on a remote participant throws a java.lang.Error, which catch (e: VideoSDKException) does not catch. To capture an image of a remote participant, refer to the documentation below.
How to capture image of a remote participant ?
-
Before proceeding, read about VideoSDK's temporary file storage system and PubSub.
-
Here's a breakdown of the steps, using the names Participant A and Participant B for clarity:
Step 1 : Initiate Image Capture Request
- In this step, you have to first send a request to Participant B, whose image you want to capture, using pubsub.
- To do that, publish a message. This example uses the
IMAGE_CAPTUREPubSub topic. - Also, you will be passing
sendOnlyto thePubSubPublishOptionsclass. Therefore, the request will be sent to that participant only. publish()is asuspendfunction, so call it from a coroutine. It throwsVideoSDKExceptionwhen the room is not joined, is reconnecting, or the message could not be sent.
class MainActivity : AppCompatActivity() {
//..
private fun sendRequest(participantId: String) {
if (participantId != room!!.localParticipant.id) {
// Pass the participantId of the participant whose image you want to capture
// Here, it will be Participant B's id, as you want to capture the image of Participant B
val sendOnly = arrayOf(participantId)
val publishOptions = PubSubPublishOptions(persist = false, sendOnly = sendOnly)
// send request to participant using the publish method
lifecycleScope.launch {
try {
room!!.pubSub.publish("IMAGE_CAPTURE", "Sending request to capture image", publishOptions)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "publish failed: ${e.code} ${e.message}")
}
}
}
}
}
Step 2 : Capture and Upload File
- To capture an image from a remote participant [Participant B], you will filter out the request from the
onMessageReceivedevent of thePubSubMessageListener. For that, you need to subscribe to a topic calledIMAGE_CAPTUREonce the room is joined. When you receive an image capture request, you will capture the image using thecaptureImage()method of theParticipantclass. - Keep the listener in a property.
onRoomJoined(switchRoomId)runs again when the room rejoins after a connection loss, and subscribing the same listener again throws3073(ERROR_INVALID_PARAMETER) instead of adding a second one.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//...
// add listener to room
room!!.addEventListener(roomEventListener)
}
private val imageCaptureListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
val senderId = message.senderId ?: return
if (senderId != room!!.localParticipant.id) {
// capture and store image when message received
captureAndStoreImage(senderId)
}
}
}
private val roomEventListener: RoomEventListener = object : RoomEventListener() {
override fun onRoomJoined(switchRoomId: String?) {
Log.d("#room", "onRoomJoined()")
// subscribe for 'IMAGE_CAPTURE' topic to receive request
lifecycleScope.launch {
try {
room!!.pubSub.subscribe("IMAGE_CAPTURE", imageCaptureListener)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "subscribe failed: ${e.code} ${e.message}")
}
}
}
//...
}
private fun captureAndStoreImage(senderId: String) {
// capture image
lifecycleScope.launch {
try {
val base64Data = room!!.localParticipant.captureImage()
Log.d("VideoSDK", "capturedImage in base64 format: $base64Data")
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in image capture: ${e.code} ${e.message}")
}
}
}
}
- After capturing the image of the remote participant, you need to store this image in VideoSDK's temporary file storage system. This will allow you to send the image to the participant who initiated the request.
- Utilize the
uploadBase64File()method of theRoomclass to store the captured image in VideoSDK's temporary file storage system. uploadBase64File()is asuspendfunction. When the upload is complete, it returns the file URL. Pass it tofetchBase64File()to get the file back.- If the upload fails, it throws
VideoSDKExceptionwith3074(ERROR_FILE_UPLOAD_FAILED) and also reports it ononError.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//...
}
private fun captureAndStoreImage(senderId: String) {
lifecycleScope.launch {
try {
// capture image
val base64Data = room!!.localParticipant.captureImage()
Log.d("VideoSDK", "capturedImage in base64 format: $base64Data")
val token = "<VIDEOSDK_TOKEN>"
val fileName = "myCapture.jpeg" // specify a name for image file with extension
// upload image to videosdk storage system
val fileUrl = room!!.uploadBase64File(base64Data, token, fileName)
Log.d("VideoSDK", "Uploaded file url: $fileUrl")
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in image capture or upload: ${e.code} ${e.message}")
}
}
}
}
- Next, the
fileUrlis sent back to the participant who initiated the request using theIMAGE_TRANSFERtopic.
class MainActivity : AppCompatActivity() {
//..
private fun captureAndStoreImage(senderId: String) {
lifecycleScope.launch {
try {
// capture image
val base64Data = room!!.localParticipant.captureImage()
val token = "<VIDEOSDK_TOKEN>"
val fileName = "myCapture.jpeg" // specify a name for image file with extension
// upload image to videosdk storage system
val fileUrl = room!!.uploadBase64File(base64Data, token, fileName)
Log.d("VideoSDK", "Uploaded file url: $fileUrl")
imageTransfer(fileUrl, senderId)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in image capture or upload: ${e.code} ${e.message}")
}
}
}
private suspend fun imageTransfer(fileUrl: String, senderId: String) {
// Pass the participantId who initiated the request
val sendOnly = arrayOf(senderId)
val publishOptions = PubSubPublishOptions(persist = false, sendOnly = sendOnly)
// send fileUrl to the participant who request for image using the publish method
try {
room!!.pubSub.publish("IMAGE_TRANSFER", fileUrl, publishOptions)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "publish failed: ${e.code} ${e.message}")
}
}
}
Step 3 : Fetch and Display Image
-
To display a captured image, the
showCapturedImage()method is used. Here's how it works: -
Within the
showCapturedImage()method, subscribe to theIMAGE_TRANSFERtopic to receive thefileUrlassociated with the captured image.subscribe()is asuspendfunction, so call it from a coroutine. Call it once the room is joined, becausesubscribe()throwsVideoSDKExceptionbefore that. -
Once the
fileUrlis obtained, use thefetchBase64File()method from theRoomclass to retrieve the file inbase64format from VideoSDK's temporary storage.fetchBase64File()is asuspendfunction that returns the file content, and throwsVideoSDKExceptionwith3075(ERROR_FILE_FETCH_FAILED) if the fetch fails, also reporting it ononError.
class MainActivity : AppCompatActivity() {
private val roomEventListener: RoomEventListener = object : RoomEventListener() {
override fun onRoomJoined(switchRoomId: String?) {
//...
showCapturedImage()
}
}
private val imageTransferListener = object : PubSubMessageListener {
override fun onMessageReceived(message: PubSubMessage) {
if (message.senderId != room!!.localParticipant.id) {
// fetch the image when message received
val url = message.message ?: return
val token = "<VIDEOSDK_TOKEN>"
lifecycleScope.launch {
try {
val base64Data = room!!.fetchBase64File(url, token)
// here is your image in the form of base64
Log.d("VideoSDK", "Fetched file in base64:$base64Data")
// converting base64 to bitmap to display an image in the imageView
val capturedImage =
findViewById<ImageView>(R.id.capturedImage)
val decodedString: ByteArray =
Base64.decode(base64Data, Base64.DEFAULT)
val decodedByte =
BitmapFactory.decodeByteArray(
decodedString,
0,
decodedString.size
)
capturedImage.setImageBitmap(decodedByte)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "Error in fetch file: ${e.code} ${e.message}")
}
}
}
}
}
private fun showCapturedImage() {
// Subscribe for 'IMAGE_TRANSFER' topic to receive fileUrl
lifecycleScope.launch {
try {
room!!.pubSub.subscribe("IMAGE_TRANSFER", imageTransferListener)
} catch (e: VideoSDKException) {
Log.d("VideoSDK", "subscribe failed: ${e.code} ${e.message}")
}
}
}
}
The file stored in the VideoSDK's temporary file storage system will be automatically deleted once the current room comes to an end.
API Reference
The API references for all the methods utilized in this guide are provided below.
Got a Question? Ask us on discord

