ブラウザからQRコードデコード

## はじめに

ブラウザ上からカメラを起動し、QRコードを読み取る必要性が出てきそうなので実験してみることにしました。
WebRTC＋[jsqrcode](https://github.com/LazarSoft/jsqrcode)を使用して、「カメラ起動＆QRコード読み取り」を行ってみたいと思います。

## WebRTCとは？

WebRTC(Web Real-Time Communication”)とは、ビデオや音声データをブラウザ間でやり取り可能にする規格です。
WebRTCの機能を利用することで、Webサイト上でビデオ・音声チャットやファイルをやり取りすることが可能になります。
大きく、以下のようなことができるようになります。

* カメラやマイクへのアクセス（UserMedia)
* PeerToPeer(RTCPeerConnection)

~~また、iOS11からカメラ等へのアクセスが可能となるようです。~~
iOS11にアップデートしてみたところ、safariの設定メニューに「「カメラとマイクのアクセス」欄が増えていたので、
カメラへはアクセスできそうです。まだ試せていないので、試してみたいと思います。

## 環境

今回の実験に使用した環境を以下に記載します。
（dockerを使用して、rails環境を構築しています）

* docker-compose version 1.14.0, build c7bdf9e
* Rails 5.0.0.1
* MySQL 5.7.17
* jsqrcode[^1]
* Google Chrome バージョン: 60.0.3112.113（Official Build） （64 ビット）
* macOS Sierra 10.12.6

## カメラを起動し、QRコードを読み取る!

では実際に、カメラを起動し、QRコードを解析してみたいと思います。

```html
<h1>サンプル</h1>

<video id="video" width="300" height="200" autoplay="1" ></video>
<img id="img">
<div style="display:none">
    <canvas id="canvas"></canvas>
</div>
<input type="button" id="action" value="decode"/>
```

上記「videoタグ」内にカメラで撮影した内容が表示されます。
「canvasタグ」部分は、video領域に表示している内容のすスナップショットを取り込む領域で、
この領域に入ってきた内容をjsqrcodeで読み取ることになります。

```html
<script type="text/javascript">
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia;
    window.URL = window.URL || window.webkitURL;

    var video = document.getElementById('myVideo');
    var localStream = null;
    navigator.getUserMedia({video: true, audio: false},
        function(stream) {
         video.src = window.URL.createObjectURL(stream);
         localStream = stream;
        },
        function(err) {
        }
    );

    function decodeImageFromBase64(data, callback){
        qrcode.callback = callback;
        qrcode.decode(data)
    }

    document.getElementById("action").addEventListener('click',function(){
        if(localStream) {
            var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');
            var img = document.getElementById('img');

            var w = video.offsetWidth;
            var h = video.offsetHeight;

            canvas.setAttribute("width", w);
            canvas.setAttribute("height", h);

            //canvasにコピー
            ctx.drawImage(video, 0, 0, w, h);

            // デコード.
            decodeImageFromBase64(canvas.toDataURL('image/png'), function(decodedInformation){
                alert(decodedInformation);
            });
        }
   },false);
</script>
```

`navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia;
`部分で、ブラウザ間の差異を吸収します。

「decodeボタン」を押下することにより、「videoタグ」内に表示されている内容のスクリーンショットをcanvas領域にコピーします。
そのコピーされた内容をjsqrcodeを使用して、解析を行います。

実際にブラウザ上でカメラを起動し、QRコードを解析した際のスクリーンショットです。
（[無料のWEBサービス](https://www.cman.jp/QRcode/)で、「qrcode sample」という文字列をQRコードに設定し、作成しました）

![qrcode_screen.png](https://qiita-image-store.s3.amazonaws.com/0/48703/cd6a840c-6bb1-e1e6-deeb-62ee531be74d.png)

精度が悪いですが、正常にQRコードの内容が読み取れています。
但し、日本語でQRコードを作成すると、解析した内容が化けてしまい、うまくQRコード内容を表示出来ませんでした。

## まとめ

実際に使用してみた印象として、すごく簡単にカメラ起動＆QRコード解析が出来た印象です。
まだまだWebRTCに関する知識が浅いので、実運用する際はもっと理解を深めて実装する必要があります。
（それにしても簡単に実装できるので、ビデオチャットなどを作成してみようかとも思います）

## iOS11で検証（2017/09/21日追記）

先日20日、iOS11へのアップデートを実施したので、iPhone7 plus(Safari)からカメラ起動＆QRコード解析ができるかどうか試してみました。
結論、**「カメラの起動はできたが、QRコードの解析ができなかった」**になります。

QRコードの解析ができない理由は分かりませんが、iPhoneのカメラでQRコードを撮影しようとするとピントを合わすことができず、
またピントを合わせようとするとQRコード以外の部分がかなり写り込んでしまい、そのあたりも原因しているのかな？、と思いました。

いずれにしても、本番運用するには色々と検証してみる必要があります。

以下、コードになります。
※ Safari用に修正した部分には、コメントを入れています。
<font color="deeppink">※ 動作させるためには、https環境が必要。</font>

```html
<html>
    <head>
        <meta charset="UTF-8" />
        <title>カメラ</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no" />
    </head>
    <body>
        <h1>サンプル</h1>
        <!-- mobile safariでは動作しなかった -->
        <!-- <video id="video" width="300" height="200" autoplay="1" ></video> -->
        <video id="video" width="400" height="300" autoplay muted playsinline></video>
        <img id="img">
        <div style="display:none">
            <canvas id="canvas"></canvas>
        </div>
        <input type="button" id="action" value="解析"/>
    </body>

    <script type="text/javascript">
        navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia;
        var video = document.getElementById('video');
        var localStream = null;

        // リアカメラを使用.
        //navigator.getUserMedia({video: true, audio: false},
        navigator.getUserMedia(
            {
                audio: false,
                video: { facingMode: { exact: "environment" } }
            },
            function(stream) {
                // mobile safariでは動作しない.
                //window.URL = window.URL || window.webkitURL;
                //video.src = window.URL.createObjectURL(stream);
                video.srcObject = stream;
                localStream = stream;
            },
            function(err) {
                console.log(err);
            }
        );

        function decodeImageFromBase64(data, callback){
            qrcode.callback = callback;
            qrcode.decode(data)
        }

        document.getElementById("action").addEventListener('click', function() {
            if(localStream) {
                var canvas = document.getElementById('canvas');
                var ctx = canvas.getContext('2d');
                var img = document.getElementById('img');

                //videoの縦幅横幅を取得
                var w = video.offsetWidth;
                var h = video.offsetHeight;

                //同じサイズをcanvasに指定
                canvas.setAttribute("width", w);
                canvas.setAttribute("height", h);

                //canvasにコピー
                ctx.drawImage(video, 0, 0, w, h);

                decodeImageFromBase64(canvas.toDataURL('image/png'), function(result) {
                    alert(result);
                });
            }
       },false);
    </script>
</html>
```

## 参考サイト
- [WebRTC](https://ja.wikipedia.org/wiki/WebRTC)
- [jsqrcode](https://github.com/LazarSoft/jsqrcode)
- [QuickStart: Compose and Rails](https://docs.docker.com/compose/rails/)

[^1]: サーバー側でQRコードを解析しようと思ったのですが、[Zxing](https://github.com/zxing/zxing)等の環境設定が面倒だったので、JSで処理してみました。