mediaDevices.getUserMedia() と iOS の Safari でハマった

# はじめに

* [Quagga.js でバーコード読取してみた](https://qiita.com/tinymouse/items/6aa75c30f828fe3f0c42)
* [Tesseract.js で OCR してみた](https://qiita.com/tinymouse/items/ec3a6dfae0b334c95894)

これらの記事で紹介したように、ウェブブラウザで動作するアプリでカメラ画像を扱うプログラムを書きました。
このとき、自分は Windows 機の Edge ブラウザで確認していて問題なかったのですが、他の環境で上手く動作しないことが分かりました。

# 最初のエラー

上記の記事で使ったのが、以下のコードです。

```app.html
    <video id="video"></video>
```
```app.js
    var video = document.querySelector('#video');
    navigator.mediaDevices.getUserMedia({
        video: {
            facingMode: 'environment'
        },
        audio: false
    })
    .then(function(stream){
        video.srcObject = stream;
        video.play();
    })
    .catch(function(e){

    });
```

Android 端末の Firefox は OK 。
Android 端末の Edge および Chrome は NG 。以下のエラーになりました。

> Uncaught (in promise) DOMException: play() can only be initiated by a user gesture.

以下のコードにして回避しました。

```app.html
    <video id="video"></video>
    <button id="start">開始</button>
```
```app.js
    document.querySelector('#start').addEventListener('click', function(){
        var video = document.querySelector('#video');
        navigator.mediaDevices.getUserMedia({
            video: {
                facingMode: 'environment'
            },
            audio: false
        })
        .then(function(stream){
            video.srcObject = stream;
            video.play();
        })
        .catch(function(e){

        });
    });
```

# 次のエラー

上記のコードはどちらも、iOS の Safari で NG でした。以下のエラーになりました。

> Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

同じ Safari でも Mac OS は OK でした。
iOS の Chrome も NG でした。以下のような訳だったんですね。

* [iPhoneではあらゆるWEBブラウザのエンジンがSafariと同じ? - いまさら聞けないiPhoneのなぜ | マイナビニュース ](https://news.mynavi.jp/article/20190331-iphone_why/)

調べていくと、以下のような記事を見つけられました。

* [【iPhone】【Safari】getUserMediaで取ったMediaStreamをvideoタグにセットしても最初のフレームだけ描画されて止まってしまう問題【React】 - DRYな備忘録](https://otiai10.hatenablog.com/entry/2019/11/11/090124)
* [javascript - HTML Video Element sometimes can't play stream from getUserMedia in PWA mode on iOS - Stack Overflow](https://stackoverflow.com/questions/72483168/html-video-element-sometimes-cant-play-stream-from-getusermedia-in-pwa-mode-on)
* [176843 – getUserMedia results in black screen on iPhone 7 plus - WebKit Bugzilla](https://bugs.webkit.org/show_bug.cgi?id=176843)

これらの記事を参考に、以下のコードにして回避しました。

```app.html
    <video id="video" autoplay muted playsinline></video>
```

# 次のエラー

上記のコードは ```video``` タグにカメラ画像を表示していますが、その内容を ```canvas``` タグにコピーして表示するようにします。

```app.html
    <canvas id="canvas"></canvas>
```
```javascript:app.js
    var video = document.createElement('video');
    var canvas = document.querySelector('#canvas');
    navigator.mediaDevices.getUserMedia({
        video: {
            facingMode: 'environment'
        },
        audio: false
    })
    .then(function(stream){
        video.srcObject = stream;
        video.play();
        setInterval(function(){
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            var ctx = canvas.getContext('2d');
            ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);
        }, 200);
    })
    .catch(function(e){

    });
```

これも iOS の Safari で NG でした。
以下のコードにすればどうでしょうか。

```javascript:app.js
    var video = document.createElement('video');
    video.autoplay = true;
    video.muted = true;
    video.playsInline = true;
    以下略
```

これでよさそうです。