Skip to main content
Version: 5.x.x

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 the Participant class, 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 a base64 string of a JPEG image.
  • captureImage() throws a VideoSDKError when it cannot capture the image: 3062 when the camera is off, and 3069 when the capture fails. A 3069 error also reaches your Events.error handler.
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.ON to captureImage() 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 ?​

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_CAPTURE pubsub topic is used.
  • Also, you will be using the sendOnly property of the PubSubPublishOptions class. 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 the captureImage() method of the Participant class.
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 the Events.roomJoined handler.

  • The captured image is then stored in VideoSDK's temporary file storage system using the uploadBase64File() function of the Room class. This operation returns a unique fileUrl of 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 fileUrl is sent back to the participant who initiated the request using the IMAGE_TRANSFER topic.
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 the IMAGE_TRANSFER topic with a PubSubMessageListener to receive the fileUrl associated with the captured image.

  • Call showCapturedImage() after the room is joined, for example from your Events.roomJoined handler, as with the IMAGE_CAPTURE subscription in Step 2.

  • Once the fileUrl is obtained, use the fetchBase64File() method from the Room class to retrieve the file in base64 format 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 base64 data in hand. we can convert it into Uint8List using Dart's base64Decode() method. This Uint8List can then be used to render the image on the screen using Image.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..."),
),
);
}
}
note

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