LINE上で動くバーコードリーダーを作った


## 作ったもの
LIFFアプリにバーコード読み取り機能を付けたかったけど、公式ではサポートしてなかったので自作してみました。ご自身のスマホからお試し頂けます→[デモサイト](https://liff.line.me/1655194342-dJrw69Jr) 
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">LIFFのバーコードリーダーです <a href="https://t.co/zHKJ0Bb0NG">pic.twitter.com/zHKJ0Bb0NG</a></p>&mdash; かおなが (@kaonaga9) <a href="https://twitter.com/kaonaga9/status/1462689377323155456?ref_src=twsrc%5Etfw">November 22, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>




## ライブラリを選定

[LINE API Use Case](https://lineapiusecase.com/)というサイトに使えそうなプロジェクトがあったのですが、内部で[Barcode Scanner SDK](https://www.scandit.com/jp/products/barcode-scanner-sdk/)というライブラリを使っていて、こちらは有償のようなので別のものを探しました。

GitHubで探してみると **QuaggaJS** なるものを発見。ドキュメントも充実していて良さげだったのでこちらを使ってみました。

https://serratus.github.io/quaggaJS/


## 使い方を調べてみる
QuaggaJSは以下の2パターンの方法で使用できるようです。

1. 画像ファイルからバーコードを読み取る
2. カメラからライブストリームを取得してスキャンする

カメラから取得する場合は HTTPS のサイト（もしくはlocalhost）でないと利用できないので注意が必要です。これは内部で madiaDevices.getUserMedia というAPI を使っていて、その仕様によるものです。

:::note info
madiaDevices.getUserMedia ....デバイスのカメラやマイクなどの入力にアクセスしてメディアストリームを取得するWeb APIです。<b>ブラウザはカメラやマイクへのアクセス許可を要求します。</b>
<a href="https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia" target="_blank">MediaDevices.getUserMedia() - Web API | MDN</a>
:::


## 読み取りページを作成


#### HTMLファイル

LIFFを使うためにSDKを追加します。QuaggaJSはCDNが公開されているのでそちらも読み込みます。


```scanner.html
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>barcode scanner</title>
    <link rel="stylesheet" href="scanner.css" />
</head>
<body>
    <div>
        <div id="photo-area" class="viewport">
            <input id="result" size="24" value="バーコードをスキャンして下さい"></input>
        </div>
        <p id="log"></p>
    </div>
    <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/quagga/0.12.1/quagga.min.js"></script>
    <script src="scanner.js" type="text/javascript"></script>
</body>
</html>
```

#### スタイルシート

カメラから受け取った映像を映すキャンバスの位置やサイズを調整します。

```scanner.css
#photo-area.viewport {
   width: 100%;
   height: auto;
}
#photo-area.viewport canvas, video {
   width: 100%;
   height: auto;
}
#photo-area.viewport canvas.drawingBuffer, video.drawingBuffer {
   margin-left: -100%;
}
```

#### バーコードスキャンのスクリプト

誤検知を防ぐため、スキャン後にごにょごにょ処理しています。JANコードの読み取りに使う場合はチェックデジットの処理も入れた方が良いかもしれません。

```scanner.js
// 初期化
Quagga.init({
    inputStream: {
        name: "Live",
        type: "LiveStream",
        target: document.querySelector('#photo-area'),
        constraints: {
            decodeBarCodeRate: 3,
            successTimeout: 500,
            codeRepetition: true,
            tryVertical: false,
            frameRate: 15,
            width: 640,
            height: 480,
            facingMode: "environment"
        },
    },
    decoder: {
        readers: [
            "ean_reader"
        ]
    },
}, function (err) {
    if (err) {
        console.log(err);
        return;
    }
    Quagga.start();

});

// バーコードを検出したときの処理
Quagga.onProcessed(function (result) {
    var drawingCtx = Quagga.canvas.ctx.overlay,
        drawingCanvas = Quagga.canvas.dom.overlay;
    if (result) {
        // 認識したバーコードを囲む
        if (result.boxes) {
            drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
            result.boxes.filter(function (box) {
                return box !== result.box;
            }).forEach(function (box) {
                Quagga.ImageDebug.drawPath(box, {
                    x: 0,
                    y: 1
                }, drawingCtx, {
                    color: "white",
                    lineWidth: 2
                });
            });
        }
        // 読み取ったバーコードを囲む
        if (result.box) {
            Quagga.ImageDebug.drawPath(result.box, {
                x: 0,
                y: 1
            }, drawingCtx, {
                color: "#01f965",
                lineWidth: 2
            });
        }
        // 読み取ったバーコードに線を引く
        if (result.codeResult && result.codeResult.code) {
            Quagga.ImageDebug.drawPath(result.line, {
                x: 'x',
                y: 'y'
            }, drawingCtx, {
                color: '#00b900',
                lineWidth: 2
            });
        }
    }
});

let scanResults = [];
let resultBuffer = [];

// 検知後の処理
Quagga.onDetected(function (result) {
    // １つでもエラー率0.19以上があれば除外
    let isErr = false
    $.each(result.codeResult.decodedCodes, function (id, error) {
        if (error.error != undefined) {
            if (parseFloat(error.error) > 0.19) {
                isErr = true
            }
        }
    })
    if (isErr) return

    // エラー率の中央値が0.1以上なら除外
    const errors = result.codeResult.decodedCodes.filter((_) => _.error !== undefined).map((_) => _.error)
    const median = _getMedian(errors)
    if (median > 0.1) {
        return
    }

    // 3回連続で同じ値だった場合のみ採用
    scanResults.push(result.codeResult.code)
    if (scanResults.length < 3) {
        return
    }
    if (scanResults[0] !== scanResults[1]) {
        scanResults.shift()
        return
    }

    // 複数回目は前回と値が違う時だけ発火
    if (resultBuffer.length > 0) {
        if (resultBuffer.slice(-1)[0] === result.codeResult.code) {
            return
        }
    }

    resultBuffer.push(result.codeResult.code);
    $('#result')[0].value = result.codeResult.code;

    if (confirm(result.codeResult.code)) {
        document.dispatchEvent(event);
    }
});

// 中央値を取得
function _getMedian(arr) {
    arr.sort((a, b) => a - b)
    const half = Math.floor(arr.length / 2)
    if (arr.length % 2 === 1)
        return arr[half]
    return (arr[half - 1] + arr[half]) / 2.0
}
```



## まとめ
LINEアプリ内でサクッとバーコードを読めるようになりました。精度も良く、結構使い道がありそうです。

毎回カメラの許可を求められるところは何とかしたいですね。。永続的に許可する方法をご存知の方がいたら教えてください。

ソースコードはGitHubで公開してますのでよかったら使ってください。
[kota-imai/liff-barcode-scanner](https://github.com/kota-imai/liff-barcode-scanner)


## 宣伝
LINEDCでハンズオンします。ご興味ある方はぜひ！

https://linedevelopercommunity.connpass.com/event/231243/