In this tutorial, we will be creating a simple web page to capture documents from mobile camera or webcam, and send the image to ID Analyzer Core API.
HTML
Starting from a blank HTML page, we will first include jQuery plugin inside <head>
to make it easier to access DOM objects and make AJAX request to Core API.
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
Next, we will add some basic elements into the page <body>
tag: a video
element to display camera stream, button
to start the camera and capture image, select
to store a list of available camera, table
to store results and a hidden canvas
to store captured image.
<video id="cameraDisplay" autoplay style="max-width: 800px; background-color: #000"></video>
<button id="btnStartCamera" type="button" onclick="getCameraDevices()">Start Camera</button>
<select id="cameraList" onchange="startCamera()"></select>
<button id="btnCapture" type="button" onclick="captureImage()" disabled >Capture & Scan</button>
<table border="1">
<thead>
<tr>
<th>Image</th>
<th>Scan Result</th>
</tr>
</thead>
<tbody id="result"></tbody>
</table>
<canvas id="documentcanvas" style="display: none"></canvas>
You could style the elements with some CSS or Bootstrap, but we will skip it for now.
Javascript
We will include the following Javascript declarations and helper functions right before </body>
tag.
You should change the API key to you own Restricted API Key, restricted API key allows Core API access only so your users will not be able to access DocuPass or Vault API.
const RestrictedAPIKey = "Your Restricted API Key";
const CoreAPIEndpoint = "https://api.idanalyzer.com";
const documentcanvas = document.getElementById('documentcanvas');
const documentctx = documentcanvas.getContext('2d');
let currentStream;
let currentDeviceID;
let cameraLoading = false;
// escape Core API JSON so we can print them in HTML
String.prototype.escape = function() {
var tagsToReplace = {
'&': '&',
'<': '<',
'>': '>'
};
return this.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
};
// workaround iOS Safari bug when switching camera
function setVideoAttr(){
$('#cameraDisplay')[0].setAttribute('autoplay', '');
$('#cameraDisplay')[0].setAttribute('muted', '');
$('#cameraDisplay')[0].setAttribute('playsinline', '');
}
// stop camera stream
function stopCamera() {
if (typeof currentStream !== 'undefined' && currentStream !== false) {
// Workaround Android 11 Chrome camera freeze when switching camera
$('#cameraDisplay')[0].srcObject = null;
currentStream.getTracks().forEach(track => {
track.stop();
});
currentStream = false;
}
}
Getting camera devices
When the Start Camera button is clicked, we need to get a list of camera devices available and populate select#cameraList
with device name and ID. This can be achieved with mediaDevices.getUserMedia
and mediaDevices.enumerateDevices
APIs.
function getCameraDevices(){
if ('mediaDevices' in navigator) {
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
}).then((stream) => {
currentStream = stream;
const getCameraSelection = async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
const options = videoDevices.map(videoDevice => {
$("#cameraList").append(`<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`);
});
stopCamera();
if ($("#cameraList option").length === 0) {
alert("Sorry, your device does not have a camera.");
} else{
$('#btnStartCamera').prop("disabled", true);
startCamera();
}
};
getCameraSelection();
}).catch((error) => {
alert("Failed to get camera list!");
});
} else {
alert("Browser does not support mediaDevices API.");
}
}
After calling the getCameraDevices()
function above, a permission to access camera dialog should popup, if the user grants permission the select#cameraList
will be populated with a list of camera. The function will then call startCamera()
to actually start the camera stream.
Starting camera stream
When the camera devices have been successfully listed, or when user changes the camera device from the camera selection list, we will get the device id from selected value and start the camera stream.
function startCamera() {
// get device id from selected item
if(currentDeviceID === $("#cameraList").val()) return;
currentDeviceID = $("#cameraList").val();
// start camera stream with device id
startCameraStream(currentDeviceID);
}
function startCameraStream(deviceID) {
if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
if (cameraLoading === true) return;
cameraLoading = true;
// build a constraint
let constraints = {
video: {
width: {ideal: 1920},
height: {ideal: 1080},
deviceId: {
exact: deviceID
}
}
};
// stop current stream if there is one
stopCamera();
setVideoAttr();
// delay the stream for a bit to prevent browser bugging out when switching camera
setTimeout(function () {
navigator.mediaDevices.getUserMedia(constraints).then(function (mediastream) {
currentStream = mediastream;
// show the camera stream inside our video element
$('#cameraDisplay')[0].srcObject = mediastream;
cameraLoading = false;
$('#btnCapture').prop("disabled", false);
}).catch(function (err) {
cameraLoading = false;
alert("Camera Error!");
});
}, 100);
}
}
The startCameraStream
function above accepts a device identifier from enumerateDevices
API, we then build a constraint and pass it to getUserMedia
to receive a media stream object, and finally the function displays the media stream received inside our video element.
Capturing document image
We will use the function below to capture the current video frame into our canvas and convert the image to base64 string.
function captureImage(){
// Copy the video frame to canvas
documentcanvas.width = $('#cameraDisplay')[0].videoWidth;
documentcanvas.height = $('#cameraDisplay')[0].videoHeight;
documentctx.drawImage($('#cameraDisplay')[0], 0, 0, documentcanvas.width, documentcanvas.height, 0, 0, documentcanvas.width, documentcanvas.height);
// convert canvas content to base64 image
let imageBase64 = documentcanvas.toDataURL("image/jpeg");
// send the base64 content to Core API
CoreAPIScan(imageBase64);
}
Uploading image to Core API
The final step is to upload the image to Core API.
function CoreAPIScan(imageContent) {
// generate an unique ID to identify the current request
let scanID = "scan" + Date.now();
// Add a new row into our result table and assign our unique ID to a cell so we can change its content when we receive AJAX result
$("#result").append(`<tr><td><img src="${imageContent}" style="max-width: 300px"></td><td id="${scanID}">Scanning...</td></tr>`)
// build a request JSON, you can add your own parameters by checking out Core API Reference
let requestJSON = {
apikey: $("#apikey").val(),
file_base64: imageContent
}
let requestString = JSON.stringify(requestJSON);
// upload document image to ID Analyzer Core API
$.ajax({
url: CoreAPIEndpoint,
data: requestString,
type: "POST",
contentType: "application/json",
timeout: 30000,
success: function (output) {
// convert html entities to code
let jsonResult = JSON.stringify(output, null, 2).escape();
// display result in the table cell
$("#"+scanID).html(`<code><pre>${jsonResult}</pre></code>`);
},
error: function (xhr, textStatus, thrownError) {
// handle errors
if (textStatus === 'timeout') {
$("#"+scanID).html("Error: Connection time out");
} else {
$("#"+scanID).html(`Error: ${xhr.status} ${textStatus}`);
}
}
});
}
The API may produce poor or inaccurate result due to poorly taken photos or bad camera focus. To obtain the best result, you should use a camera that comes with autofocus so that when a document is held close to the camera, the camera would focus on the document thus preventing blurry text.