Image Capturer - Flutter
This guide provides instructions on capturing images of participants from a video stream.
This capability proves particularly valuable in Video KYC scenarios, enabling the capture of images where users can hold up their identity for verification.
captureImage()
- By using the
captureImage()method of theParticipantclass, you can capture image of a local participant from their video stream. The camera must be on. - You have the option to specify the desired height and width in the
captureImage()method; however, these parameters are optional. They set the largest size of the image: the image keeps the camera's aspect ratio and is never enlarged. If not provided, the VideoSDK will automatically use the dimensions of the local participant's webcamStream. - The
captureImage()function returns the image in the form of abase64string of a JPEG image. captureImage()throws aVideoSDKErrorwhen it cannot capture the image:3062when the camera is off, and3069when the capture fails. A3069error also reaches yourEvents.errorhandler.
void imageCapture() async {
try {
// captureImage will return base64 string
String? base64Image = await room.localParticipant.captureImage(
height: 400,
width: 400,
);
print("Base64 Image captured $base64Image");
} on VideoSDKError catch (error) {
// For example 3062 when the camera is off.
print("captureImage failed: ${error.code} ${error.name}");
}
}
Capture with flash
- Pass
flash: CaptureFlash.ONtocaptureImage()to light the scene before the image is captured. It adds about half a second to the capture. - The SDK uses the camera's torch when it has one, which covers most back cameras.
- For a front camera without a torch, call
VideoSDK.setScreenFlash(true)first. The screen then turns white at full brightness while the image is captured. Without it, the image is captured without the extra light.
void imageCaptureWithFlash() async {
// Lets a front camera use the screen as its flash.
await VideoSDK.setScreenFlash(true);
try {
String? base64Image = await room.localParticipant.captureImage(
flash: CaptureFlash.ON,
);
print("Base64 Image captured $base64Image");
} on VideoSDKError catch (error) {
print("captureImage failed: ${error.code} ${error.name}");
}
}
How to capture image of a remote participant ?
-
Before proceeding, it's crucial to understand VideoSDK's temporary file storage system and the underlying pubsub mechanism.
-
Here's a break down 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.
- In order to do that, you have to publish a message. Here,
IMAGE_CAPTUREpubsub topic is used. - Also, you will be using the
sendOnlyproperty of thePubSubPublishOptionsclass. Therefore, the request will be sent to that participant only.
class RoomScreen extends StatefulWidget {
const RoomScreen({Key? key}) : super(key: key);
@override
_RoomScreenState createState() => _RoomScreenState();
}
class _RoomScreenState extends State<RoomScreen> {
late Room _room;
// send Request to participant
Future<void> sendRequest(String participantId) async {
// 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 the image of Participant B
if (participantId != _room.localParticipant.id) {
var publishOptions = PubSubPublishOptions(
persist: false,
sendOnly: [participantId],
);
// create pubsub topic to send Request
try {
await _room.pubSub.publish(
"IMAGE_CAPTURE",
"Sending request to capture image",
publishOptions,
);
} on VideoSDKError catch (e) {
// The request never reached the other participant.
debugPrint("Capture request failed: ${e.code} ${e.message}");
}
}
}
@override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () async {
await sendRequest("enter the participant ID to send image capture request");
},
child: Text("Capture Image"),
),
]);
}
}
Step 2 : Capture and Upload File
- To capture an image from a remote participant [Participant B] . you need to subscribe to a topic called
IMAGE_CAPTURE. When you receive an image capture request, you will capture the image using thecaptureImage()method of theParticipantclass.
class RoomScreen extends StatefulWidget {
const RoomScreen({Key? key}) : super(key: key);
@override
_RoomScreenState createState() => _RoomScreenState();
}
class _RoomScreenState extends State<RoomScreen> {
late Room _room;
void messageHandler(PubSubMessage message) {
if (message.senderId != _room.localParticipant.id) {
captureAndStoreImage(message.senderId);
}
}
// capture and store image when message received
void captureAndStoreImage(String senderId) async {
try {
// capture image
String? base64Data = await _room.localParticipant.captureImage(
height: 400,
width: 400,
);
print("base64Data : $base64Data");
} on VideoSDKError catch (e) {
// For example 3062 when the camera is off.
debugPrint("Capture failed: ${e.code} ${e.message}");
}
}
// subscribe to receive request
void registerRoomEvents(Room _room) {
_room.on(
Events.roomJoined,
() async {
try {
await _room.pubSub.subscribe(
"IMAGE_CAPTURE",
PubSubMessageListener(onMessageReceived: messageHandler),
);
} on VideoSDKError catch (e) {
debugPrint("Subscribe failed: ${e.code} ${e.message}");
}
},
);
}
@override
Widget build(BuildContext context) {
return YourRoomWidget();
}
}
-
pubSub.subscribe()works only after the room is joined, so the example subscribes from theEvents.roomJoinedhandler. -
The captured image is then stored in VideoSDK's temporary file storage system using the
uploadBase64File()function of theRoomclass. This operation returns a uniquefileUrlof the stored image.
void captureAndStoreImage(String senderId) async {
try {
// capture image
String? base64Data = await _room.localParticipant.captureImage(
height: 400,
width: 400,
);
String token = "<VIDEOSDK_TOKEN>";
String fileName = "myCapture.jpeg"; // specify a name for image file with extension
// upload image to videosdk storage system
String? url = await _room.uploadBase64File(
base64Data: base64Data!,
token: token,
fileName: fileName,
);
print("Image URL: $url");
} on VideoSDKError catch (e) {
debugPrint("Capture or upload failed: ${e.code} ${e.message}");
}
}
- Next, the
fileUrlis sent back to the participant who initiated the request using theIMAGE_TRANSFERtopic.
void captureAndStoreImage(String senderId) async {
try {
String? base64Data = await _room.localParticipant.captureImage(
height: 400,
width: 400,
);
String token = "<VIDEOSDK_TOKEN>";
String fileName = "myCapture.jpeg";
String? url = await _room.uploadBase64File(
base64Data: base64Data!,
token: token,
fileName: fileName,
);
// publish image Transfer
PubSubPublishOptions options = PubSubPublishOptions(
persist: false,
sendOnly: [senderId],
);
await _room.pubSub.publish(
"IMAGE_TRANSFER",
url!,
options,
);
} on VideoSDKError catch (e) {
// The requester will never receive this image.
debugPrint("Image transfer 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 with aPubSubMessageListenerto receive thefileUrlassociated with the captured image. -
Call
showCapturedImage()after the room is joined, for example from yourEvents.roomJoinedhandler, as with theIMAGE_CAPTUREsubscription in Step 2. -
Once the
fileUrlis obtained, use thefetchBase64File()method from theRoomclass to retrieve the file inbase64format from VideoSDK's temporary storage.
void showCapturedImage() async {
try {
await _room.pubSub.subscribe(
"IMAGE_TRANSFER",
PubSubMessageListener(
onMessageReceived: (PubSubMessage message) async {
if (message.senderId != _room.localParticipant.id) {
String url = message.message;
String token = "<VIDEOSDK_TOKEN>";
try {
String? base64 = await _room.fetchBase64File(
url: url, // pass fileUrl to fetch the file
token: token,
);
print("base64 $base64"); // here is your image in the form of base64
} on VideoSDKError catch (e) {
debugPrint("Fetch failed: ${e.code} ${e.message}");
}
}
},
),
);
} on VideoSDKError catch (e) {
// Without this subscription no captured image ever arrives.
debugPrint("Subscribe failed: ${e.code} ${e.message}");
}
}
- With the
base64data in hand. we can convert it intoUint8Listusing Dart'sbase64Decode()method. ThisUint8Listcan then be used to render the image on the screen usingImage.memory()widget.
class RoomScreen extends StatefulWidget {
const RoomScreen({Key? key}) : super(key: key);
@override
_RoomScreenState createState() => _RoomScreenState();
}
class _RoomScreenState extends State<RoomScreen> {
late Room _room;
Uint8List? _capturedImage;
void showCapturedImage() async {
try {
await _room.pubSub.subscribe(
"IMAGE_TRANSFER",
PubSubMessageListener(
onMessageReceived: (PubSubMessage message) async {
if (message.senderId != _room.localParticipant.id) {
String url = message.message;
String token = "<VIDEOSDK_TOKEN>";
try {
String? base64 = await _room.fetchBase64File(
url: url, // pass fileUrl to fetch the file
token: token,
);
// set state to set _capturedImage to the obtain base64 string
setState(() {
_capturedImage = base64Decode(base64!);
});
} on VideoSDKError catch (e) {
debugPrint("Fetch failed: ${e.code} ${e.message}");
}
}
},
),
);
} on VideoSDKError catch (e) {
// Without this subscription no captured image ever arrives.
debugPrint("Subscribe failed: ${e.code} ${e.message}");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Captured Image")),
body: Center(
// Display the image
child: _capturedImage != null
? Image.memory(_capturedImage!)
: const Text("Waiting for image..."),
),
);
}
}
The file stored in the 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

