TypeScriptで"手っ取り早く"ブラウザ搭載のBarcodeDetectorを使いたい

## BarcodeDetectorとは？

一部のブラウザ(AndroidのChrome等)に標準搭載されている1次元/2次元(QRコード)のバーコード読み取り機能です。
**現時点では<font color="Red">"Experimental"(実験的な機能)</font>との事です。**
今の所で使えそうなのはAndroidな感じですね。

※対応ブラウザ等、詳しくは下記のMDNの解説ページをご確認下さい。

・MDNの[BarcodeDetector](https://developer.mozilla.org/ja/docs/Web/API/BarcodeDetector)ページ

## 前提条件

要HTTPSです。

## 使い方？

```TS
const detector = new window.BarcodeDetector();
const list = await detector.detect(imagedata, canvas.width, canvas.height);
```

まずwindowからBarcodeDetectorを生成して...
ですが、TypeScriptではBarcodeDetector()部分がエラーとなります。

＃正式な機能になったらサポートされるんですよね？　きっと...

## "手っ取り早く"使えるようにする！

windowに存在しないBarcodeDetector()を、とりあえず存在する事にしましょう。
という訳で同じソースファイル上に、

```TS
//windowにBarcodeDetectorを生やす
declare global {
  interface Window {
      BarcodeDetector: any;
  }
}
```

...を追加します。
**ここは"手っ取り早く"なので<font color="Red">any</font>でお茶を濁します！**

するとBarcodeDetector()のエラーが消えました！

※DevelopersIOの解説記事が大変参考になりました！
　今回は記事中の"パターン4"の方法になります。
　もしご自身の環境で上手く行かないヨという場合は、他パターンに代えてお試し下さい m(__)m

・[TypeScript で window 直下にいろいろ生やしたりグローバル変数を定義する](https://dev.classmethod.jp/articles/typings-of-window-object/)

## 改めて使い方！

```TS
//BarcodeDetector生成
const detector = new window.BarcodeDetector();

//BarcodeDetectorでimagedataを認識させる
//(imagedata,戻り値については上記MDNページのメソッド"detect()"を参照して下さい)
const list = await detector.detect(imagedata, canvas.width, canvas.height);

//認識できた場合、listに要素が入ります
//(ここではバーコードの内容をアラートで表示)
for (const detected of list) {
    alert(`${detected.rawValue}`);
}

```

## ReactのApp.tsx全文

videoを回して200ms毎に判定させて居ます。
"終了はブラウザを閉じる"、"エラー処理なし"という動く環境でしか動かない適当コードですがご参考まで...(それでもチョット長い)

<details><summary>←クリックで展開(長いので畳みました)</summary>

```TS:App.tsx
import React, { useRef, useEffect, useCallback } from 'react';
import './App.css';

//windowにBarcodeDetectorを生やす
declare global {
    interface Window {
        BarcodeDetector: any;
    }
}

//横・縦幅(てきとう)
const width = 300;
const height = 300;

function App() {
    const videoRef = useRef<HTMLVideoElement>(null);

    async function checkImage() {
        let canvas = document.createElement("canvas");
        if (videoRef === null || videoRef.current === null) {
            return null;
        }

        const { videoWidth, videoHeight } = videoRef.current;

        canvas.width = videoWidth;
        canvas.height = videoHeight;

        const context = canvas.getContext("2d");

        if (context === null || videoRef.current === null) {
            return null;
        }

        context.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);

        if (canvas.width === 0) {
            setTimeout(() => { checkImage() }, 200);
            return null;
        }

        const imagedata = context.getImageData(0, 0, canvas.width, canvas.height);

        const detector = new window.BarcodeDetector();

        const list = await detector.detect(imagedata, canvas.width, canvas.height);

        let cnt = 0;

        //認識できた場合、listに要素が入ります
        //(ここではバーコードの内容をアラートで表示)
        for (const detected of list) {
            cnt++;
            alert(`[${cnt}/${list.length}]${detected.rawValue}`);
        }

        setTimeout(() => { checkImage() }, 200);
    }

    useEffect(() => {
        checkImage();
    }, []);

    //カメラのstreamを取得して返すメソッド
    const getStream = useCallback(async () => {
        const aspectRatio = width / height;

        return await navigator.mediaDevices.getUserMedia({
            video: {
                facingMode: "environment",
                width: {
                    ideal: 1024
                },
                aspectRatio
            },
            audio: false
        });
    }, [width, height]);

    useEffect(() => {
        let stream: MediaStream | null = null;
        let video = videoRef.current;

        //取得したstreamをvideo要素に流す
        const setVideo = async () => {
            stream = await getStream();

            if (video === null || !stream) {
                return;
            }

            video.srcObject = stream;
            video.play();
        };

        setVideo();

        //streamを停止させる
        const cleanupVideo = () => {
            if (!stream) {
                return;
            }

            stream.getTracks().forEach((track) => track.stop());

            if (video === null) {
                return;
            }

            video.srcObject = null;
        };
        return cleanupVideo
    }, [getStream]);

    return (
        <div className="App">
            <body>
                <video ref={videoRef} playsInline width={width} height={height} />
            </body>
        </div>
    );
}

export default App;
```

</details>

## 使ってみた感想

手元にあったLenovoやTECLASTのタブレットやMotorolaのスマホで試しましたが、反応は中々良いと思いました。

またタブレットの箱に貼ってあった1次元、QRコード合計5個を画面に収まるように入れて読ませた所、上手く解像するタイミングでは5個を一括で認識しました。

環境は限られますが、現時点でも遊べる印象です。
"実験的"では仕事では採用できないとは思いますが、引き続き注目して行きたいと思います。

早く"Experimental"が取れて、環境を気にせず使えるようになって欲しいです！

※個人の感想です。

## 参考(本文に登場しない分)

videoタグの使い方について、
dashi296さんの
・[Reactでカメラを使って画像を撮影するダイアログを実装してみた](https://zenn.dev/dashi296/articles/20b80f153fa481)

ブラウザでのバーコード読み取りについて、
@kan_dai(TAM)さんの
・[Webの技術だけで作るQRコードリーダー](https://qiita.com/kan_dai/items/4331aae12f5f2d3ad18d)、
・[続・Webの技術だけで作るQRコードリーダー](https://qiita.com/kan_dai/items/3486880236a2fcd9b527)

...その他、色々な情報を参考にさせて頂きました m(__)m