WebRTCを使ってブラウザでバーコードのスキャンをしてみる

# はじめに
- QRではなく一次元バーコードの読み取りをブラウザで行う
- WebRTC(getUserMedia)を利用したリアルタイムのライブプレビューでスキャンする
- 使い慣れているVueで実装する

# やりたいこと
WebRTCでカメラを利用してブラウザ上でバーコードを読み取り、読み取った瞬間の画像と読み取り結果（ISBN）を画面上に表示する。

# WebRTCとは
Web Real Time Communicationsの略で、ブラウザ上で音声や映像やファイルのやり取りをリアルタイムで行うことができる技術です。
また、それらをブラウザで簡単に扱えるようにしたAPI群です。
`HTTPS`か`localhost`でしか動かないので注意。

今回はライブラリに頼りきりの実装となっているので細かくは説明しませんが、ざっと説明すると上記のような感じです。

[WebRTC API - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/WebRTC_API)
[いろんなデモ](https://webrtc.github.io/samples/)

# デモ
https://barcode-scanner-demo.netlify.com/

Scanボタンをクリックしてカメラを許可するだけで使えます。
PWAに対応しているので、インストールしてアプリのように使うこともできます。

# セットアップ
まずはVue CLIでプロジェクトを立ち上げます。
アプリっぽく使える方が便利なので、PWAにもチェックをいれておきます。

```sh
$ vue create barcode_scanner


Vue CLI v4.2.3
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, PWA, Linter
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
```

# バーコードスキャナーを実装する
[QuaggaJS](https://serratus.github.io/quaggaJS/)を使って実装します。
このライブラリに`getUserMedia`を使って検出ができる設定があるのでそれを使います。

```sh
$ yarn add quagga
```


スキャン部分の全体的なコードはこんな感じ。

```src/components/BarcodeScanner.vue
<template>
  <div>
    <div id="cameraArea">
      <img v-if="code.length" src="" alt="result" class="resultImg" />
    </div>
    <p v-if="code.length" class="getMessage">取得できました</p>
    <p class="resultCode">{{ code }}</p>
    <button @click="startScan">Scan</button>
    <button aria-label="close" @click.prevent.stop="stopScan">
      Stop
    </button>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

export type DataType = {
  Quagga: any;
  code: string;
};

export default Vue.extend({
  name: "BarcodeScanner",
  data(): DataType {
    return {
      Quagga: null,
      code: ""
    };
  },
  methods: {
    startScan() {
      this.code = "";
      this.initQuagga();
    },
    stopScan() {
      this.Quagga.offProcessed(this.onProcessed)
      this.Quagga.offDetected(this.onDetected)
      this.Quagga.stop();
    },
    initQuagga() {
      this.Quagga = require("quagga");
      this.Quagga.onProcessed(this.onProcessed);
      this.Quagga.onDetected(this.onDetected);

      // 設定
      const config = {
        inputStream: {
          name: "Live",
          type: "LiveStream",
          target: document.querySelector("#cameraArea"),
          constraints: { facingMode: "environment" }
        },
        numOfWorkers: navigator.hardwareConcurrency || 4,
        decoder: { readers: ["ean_reader", "ean_8_reader"] }
      };
      this.Quagga.init(config, this.onInit);
    },
    onInit(err: any) {
      if (err) {
        console.log(err);
        return;
      }
      console.info("Initialization finished. Ready to start");
      this.Quagga.start();
    },
    onDetected(success: any) {
      this.code = success.codeResult.code;
      // 取得時の画像を表示
      const $resultImg: any = document.querySelector(".resultImg");
      $resultImg.setAttribute("src", this.Quagga.canvas.dom.image.toDataURL());
      this.Quagga.stop();
    },
    onProcessed(result: any) {
      const drawingCtx = this.Quagga.canvas.ctx.overlay;
      const drawingCanvas = this.Quagga.canvas.dom.overlay;

      if (result) {
        // 検出中の緑の線の枠
        if (result.boxes) {
          drawingCtx.clearRect(0, 0, drawingCanvas.width, drawingCanvas.height);
          const hasNotRead = (box: number[][]) => box !== result.box;
          result.boxes.filter(hasNotRead).forEach((box: number[][]) => {
            this.Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, {
              color: "green",
              lineWidth: 2
            });
          });
        }

        // 検出に成功した瞬間の青い線の枠
        if (result.box) {
          this.Quagga.ImageDebug.drawPath(
            result.box,
            { x: 0, y: 1 },
            drawingCtx,
            {
              color: "blue",
              lineWidth: 2
            }
          );
        }

        // 検出に成功した瞬間の水平の赤い線
        if (result.codeResult && result.codeResult.code) {
          this.Quagga.ImageDebug.drawPath(
            result.line,
            { x: "x", y: "y" },
            drawingCtx,
            {
              color: "red",
              lineWidth: 3
            }
          );
        }
      }
    }
  }
});
</script>

<style>
#cameraArea {
  overflow: hidden;
  width: 320px;
  height: 240px;
  margin: auto;
  position: relative;
  display: flex;
  align-items: center;
}
#cameraArea video,
#cameraArea canvas {
  width: 320px;
  height: 240px;
}
button {
  width: 100px;
  height: 40px;
  background-color: #fff;
  border: 1px solid #333;
  margin-top: 30px;
}
.resultImg {
  width: 100%;
}
.resultCode {
  font-size: 24px;
  font-weight: bold;
  text-align: center;
}
.getMessage {
  color: red;
}
</style>
```

# 検出画面
読み取り中はバーコードを緑の線で囲み、読み取りに成功するとバーコードを青い線で囲み真ん中に水平に赤い線を引いた画面（QRコードリーダーアプリとかでよく見るやつ）になります。
線を非表示にすることも可能です。

## 検出中と検出結果gif
![barcode_scan_result_sm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243815/fd7a1e22-263c-70e0-80dd-037964d3aee9.gif)

## 検出結果
<img width="466" alt="barcode_result.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243815/9cc3414b-2927-0b16-6853-03eb121a8913.png">

# まとめ
ライブプレビューでバーコードをスキャンして読み取った結果を表示するまでをブラウザ上で完結することができました。
今回はただ読み取った結果を表示するだけの単純なデモアプリを作成しましたが、要件によって様々な応用ができそうです。
例えば、APIを叩いて何かしらのデータを表示させたり、実際の店頭で商品のバーコードを読み取り商品ページに遷移させたりといったことも可能です。
UIデザインに落とし込み便利な機能を持たせることでUXを向上させることができそうです。

# 参考
[Nuxt.jsでバーコードリーダを作ったら、いろいろハマった上にiOSのPWAでカメラにアクセスできなかった話](https://qiita.com/kira_puka/items/03dc5c01bbbaffdb6e83)
[quaggaJS/live_w_locator.js at master · serratus/quaggaJS](https://github.com/serratus/quaggaJS/blob/master/example/live_w_locator.js)
[QuaggaJS, an advanced barcode-reader written in JavaScript](https://serratus.github.io/quaggaJS/v1.0.0-beta.1/examples/live_w_locator/)
[WebRTC](https://webrtc.org/)