ID Analyzer Developer ID Analyzer Developer
  • Home
  • Portal
  • Support
ID Analyzer Developer ID Analyzer Developer
ID Analyzer Developer
  • Home
  • Portal
  • Support
  • Home
  • Core API
    • Overview
    • Quick Start
    • API Reference
  • DocuPass API
    • Overview
    • Quick Start
    • API Reference
  • Vault API
    • Overview
    • Quick Start
    • API Reference
  • AML API
    • Overview
    • Quick Start
    • API Reference
  • Client Library
    • PHP
    • .NET
    • NodeJS
    • Python
  • Guide
    • Web-based ID Scanner
    • DocuPass Custom UI Design
  • Change Log
  • ID Fort On-Premise

Javascript web-based ID scanner using camera

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.

This demo make use of browser's getUserMedia API, which is only available if the web page is accessed via HTTPS. It will not work from non-encrypted server or local drive, you must upload the web page to a server with valid SSL certificate.

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 &amp; 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 = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;'
    };
    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.

Live Demo

Launch Live Demo

On this page:
HTML Javascript Live Demo

© 2023 All Rights Reserved by ID Analyzer