Build a Web QR Code Scanner using the Barcode Detection API

The Web Platform Incubator Community Group has published a Shape Detection API specification. The API aims at detecting features like faces and QR codes from images. It is still in draft status and has limited browser support. In this article, we are going to use the Barcode Detection API included in the Shape Detection API to create a web QR code scanner.

Supported Browsers

You can check out the browser support here: https://caniuse.com/?search=BarcodeDetector%20API

The API mainly works on Chrome 83+ for macOS and Android. In addition, it requires localhost or HTTPS to provide a secure context.

Build a Web QR Code Scanner using the Barcode Detection API

Create a BarcodeDetector Object

We can create a barcode detector with the following code:

  1. Check whether it is supported:

     if (!('BarcodeDetector' in window)) {
       console.log('Barcode Detector is not supported by this browser.');
     } else {
       console.log('Barcode Detector supported!');
     }
    
  2. Create a barcode detector:

     var barcodeDetector = new BarcodeDetector();
    
  3. Create a barcode detector which only detects specified barcode formats:

     var barcodeDetector = new BarcodeDetector({formats: ['code_39', 'codabar', 'ean_13']})
    

    You can get the supported formats with the getSupportedFormats method:

     // check supported types
     await BarcodeDetector.getSupportedFormats();
     // ['aztec', 'code_128', 'code_39', 'code_93', 'data_matrix', 'ean_13', 'ean_8', 'itf', 'pdf417', 'qr_code', 'upc_e']
    

Open Camera using getUserMedia

We can access the camera using the getUserMedia API and then detect QR codes from the camera video stream. Here, we load camera devices list to a select and play the first device in a video element:

var localStream;
function loadDevicesAndPlay(){
  var constraints = {video: true, audio: false};
  navigator.mediaDevices.getUserMedia(constraints).then(stream => {
      localStream = stream;
      var cameraselect = document.getElementById("cameraSelect");
      cameraselect.innerHTML="";
      navigator.mediaDevices.enumerateDevices().then(function(devices) {
          var count = 0;
          var cameraDevices = [];
          var defaultIndex = 0;
          for (var i=0;i<devices.length;i++){
              var device = devices[i];
              if (device.kind == 'videoinput'){
                  cameraDevices.push(device);
                  var label = device.label || `Camera ${count++}`;
                  cameraselect.add(new Option(label,device.deviceId));
                  if (label.toLowerCase().indexOf("back") != -1) { //select the back camera as the default
                    defaultIndex = cameraDevices.length - 1;
                  }
              }
          }

          if (cameraDevices.length>0) {
            cameraselect.selectedIndex = defaultIndex;
            play(cameraDevices[defaultIndex].deviceId);
          }else{
            alert("No camera detected.");
          }
      });

  });
}

function play(deviceId) {
  stop(); // close before play
  var constraints = {};

  if (!!deviceId){
      constraints = {
          video: {deviceId: deviceId},
          audio: false
      }
  }else{
      constraints = {
          video: true,
          audio: false
      }
  }

  navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
      localStream = stream;
      var cameraVideo = document.getElementsByClassName("camera")[0];
      // Attach local stream to video element
      cameraVideo.srcObject = stream;

  }).catch(function(err) {
      console.error('getUserMediaError', err, err.stack);
      alert(err.message);
  });
}

function stop(){
  try{
      if (localStream){
          localStream.getTracks().forEach(track => track.stop());
      }
  } catch (e){
      alert(e.message);
  }
}

Detect QR Codes from the Camera Video Stream

After the camera is opened, we can use the Barcode Detection API to detect QR codes and barcodes.

  1. Add loadeddata event to the video element. This event will be fired when the camera is opened.

     document.getElementsByClassName("camera")[0].addEventListener('loadeddata',onOpened, false);
    
  2. In the onOpened event, set an interval to detect barcodes from the video.

     var interval;
     var decoding = false;
        
     function onOpened() {
       startDecoding();
     }
        
     function startDecoding(){
       clearInterval(interval);
       interval = setInterval(decode, 500);
     }
    
     async function decode(){
       if (decoding === false) {
         console.log("decoding");
         var cameraVideo = document.getElementsByClassName("camera")[0];
         decoding = true;
         var barcodes = await barcodeDetector.detect(cameraVideo);
         decoding = false;
         console.log(barcodes);
       }
     }
    

Here is an example of the detection results:

[
  {
    "boundingBox": {
      "x": 36,
      "y": 36,
      "width": 325,
      "height": 327,
      "top": 36,
      "right": 362,
      "bottom": 364,
      "left": 36
    },
    "cornerPoints": [
      {
        "x": 36,
        "y": 36
      },
      {
        "x": 362,
        "y": 36
      },
      {
        "x": 360,
        "y": 360
      },
      {
        "x": 36,
        "y": 364
      }
    ],
    "format": "qr_code",
    "rawValue": "dynamsoft"
  }
]

Conclusion

The Barcode Detector API is easy to use and has a fairly good performance. But it has a severe compatibility issue. In the next article, we are going to create a polyfill for the Barcode Detection API with Dynamsoft Barcode Reader JavaScript as its backend so that it can work on all major platforms.

Source Code

A demo has been created, which can draw QR code overlays and read QR codes from image elements and video elements. You can find the online demo and its source code here: https://github.com/xulihang/barcode-detection-api-demo