BarcodeDetector API for LE Audio

Lars Knudsen 🇩🇰 - Jul 14 - - Dev Community

As mentioned in my post on building a Broadcast Audio Source the Broadcast Audio URI (BAU) spec allows sharing information about broadcast sources through QR codes, NFC tags and more.

In this post, I'll show how you can make a web application that can read and parse the Broadcast Audio URI QR codes.

BarcodeDetector API

Reading barcodes directly in the browser is made possible with the introduction of the BarcodeDetector API and in this post, I will show an example of how it can be used to read the Broadcast Audio URI QR code with a few lines of JavaScript.

NOTE: The BarcodeDetector API is not yet available in all browsers. For this reason, a polyfill will be used where support is still missing.

Reading barcodes with this API is extremely simple. It only requires passing an image source to the detect() function, which returns a Promise that fulfills with an array of DetectedBarcode objects.

Even though we are only focused on QR codes in this post, the API supports many commonly used formats.

Lights, Camera, Action!

First, the camera needs to be switched on and streaming a video feed to an HTMLVideoElement:

...
<video id="camera"
       muted
       autoplay="autoplay"
       playsinline="playsinline">
</video>
...
Enter fullscreen mode Exit fullscreen mode

and

...
const camera = document.querySelector('#camera');
const constraints = {video: true, audio: false};
let stream = await navigator.mediaDevices.getUserMedia(constraints);

camera.srcObject = stream;
...
Enter fullscreen mode Exit fullscreen mode

We also need to make a barcode detector object:

const barcodeDetector = new window.BarcodeDetector();
Enter fullscreen mode Exit fullscreen mode

In order for the detection to work as expected, the detect function needs to be called at regular intervals to scan images captured with the camera:

let lastCode = "";

async function decodeQr(){
  const barcodes = await barcodeDetector.detect(camera);

  if (barcodes?.length) {
    for (const barcode of barcodes) {
      // Try to parse the URI string
      const decoded = parseBroadcastURI(barcode.rawValue);

      if (decoded?.length) {
        // Avoid repainting if the data is already shown
        if (lastCode == barcode.rawValue) {
          break;
        }

        lastCode = barcode.rawValue;

        // Display the parsed info
        showBroadcastInfo(decoded);

        break; // Only use the first code found
      }
    }
  } else {
    lastCode = "";
  }

  // Try again in 100ms
  setTimeout(this.decodeQr, 100);
}
Enter fullscreen mode Exit fullscreen mode

Parsing the Broadcast Audio URI

The scanned code should contain a string, starting with BLUETOOTH:, followed by a number of fields as listed in the Broadcast Audio URI spec.

A QR code example from the spec:

Image description

, containing the following data:

BLUETOOTH:UUID:184F;BN:SG9ja2V5;SQ:1;AT:0;AD:AABBCC001122;AS:1;BI:DE51E9;PI:F
FFF;NS:1;BS:1;;
Enter fullscreen mode Exit fullscreen mode

roughly translates to:

"Related to the 0x184F UUID (Broadcast Audio Scan Service), there is a Standard Quality mono channel broadcast at addresss AA:BB:CC:00:11:22 with Broadcast ID 0x0E51E9 and the Broadcast name 'Hockey'"

For this PoC, I created a very simple function to parse the most common fields:

const BROADCAST_AUDIO_URI_SCHEME = 'BLUETOOTH:';

const parseBroadcastURI = (str) => {
  if (!str.startsWith(BROADCAST_AUDIO_URI_SCHEME)) {
    return [];
  }

  const result = [];

  // split sections (;)
  const sections = str.substring(BROADCAST_AUDIO_URI_SCHEME.length).split(';');

  sections.forEach(section => {
    const [key, value] = section.split(':');

    switch (key) {
      case 'UUID':
      result.push({
        type: key,
        name: 'UUID',
        value: `0x${value}`
      });
      break;
      case 'BI': // Broadcast ID
      result.push({
        type: key,
        name: 'Broadcast ID',
        value: `0x${value.padStart(6,0)}`
      });
      case 'BN': // Broadcast name
      result.push({
        type: key,
        name: 'Broadcast Name',
        value: new TextDecoder().decode(base64ToBytes(value))
      });
      break;
      // ... (more fields in full scouce)
    }
  });

  return result;
}
Enter fullscreen mode Exit fullscreen mode

QR Scanner component

In order to make it possible for others to more easily embed the BAU QR scanner in a web application, I decided to create a web component that encapsulates the logic to control the video feed and display the information found in an overlay info box.

In order to use it in a page, just add the following:

<bau-scanner></bau-scanner>
<script type="module" src="./bau-scanner.js"></script>
Enter fullscreen mode Exit fullscreen mode

The element has functions to start and stop the scanning (and camera feed), which can be hooked up to button events, e.g.:

...
<button id="start_scanner">Start scanner</button>
<button id='stop_scanner'>Stop scanner</button>
...
Enter fullscreen mode Exit fullscreen mode

and

...
const startScan = document.querySelector('#start_scanner');
const stopScan = document.querySelector('#stop_scanner');

scanner = document.querySelector('bau-scanner');

startScan.addEventListener('click', scanner.startCamera);
stopScan.addEventListener('click', scanner.stopCamera);
...
Enter fullscreen mode Exit fullscreen mode

An example index.html together with the bau-scanner.js component can be found here: https://github.com/larsgk/bau-source

See it in action

For demonstration purposes, I decided to use two QR codes from the Broadcast Audio URI spec plus a dynamic one, generated with the Broadcast Audio Source sample covered in another post.

Here is the scanner in action, running on a mobile phone:

You can try it yourself by opening this link, where the demo page is hosted: https://larsgk.github.io/bau-source/

Enjoy ;)

. . . . . . . . . . . .