JavaScriptでカメラ使うアプリケーションを実装するぞ！

# 利用するぞ!
下のようなコードで `cameraStream`でカメラ映像のmediaStreamが手に入る。
`mediaDevices.getUserMedia`に渡すオブジェクトでカメラに限らずデバイスに存在するメディアからなにをどう使うかを指定できる。
[MediaDevices.getUserMedia() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia)

```javascript

const cameraStream = await mediaDevices.getUserMedia({
  video: {
    facingMode: 'environment',

    aspectRatio: {
      exact: 1.6,
    },
  },
  audio: false,
});
```

上のコードだと`facingMode: 'environment'`で背面カメラ、`aspectRatio`でカメラのアスペクト比を指定している。

カメラの幅をwidthやheightで指定したときにはクセのある挙動をするので、詳しい方の解説を読んだ方が良い [getUserMedia()で指定できるMediaTrackConstraintsのよもやま - console.lealog();](https://lealog.hateblo.jp/entry/2017/08/21/155211)

# videoタグでカメラの様子をうつす
突然HTMLだが、このままでサクっと動くはず。

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <script>
      async function startCamera() {
        const cameraStream = await navigator.mediaDevices.getUserMedia({
          video: {
            facingMode: 'environment',

            aspectRatio: {
              exact: 1.6,
            },
          },
          audio: false,
        });
        const videoElement = document.createElement("video")
        videoElement.autoplay = true;
        videoElement.srcObject = cameraStream;
        document.body.append(videoElement);
        videoElement.addEventListener("resize", () => {
          videoElement.width = videoElement.videoWidth;
          videoElement.height = videoElement.videoHeight;
        });
      }
      startCamera()
    </script>
  </body>
</html>



```

```javascript
videoElement.autoplay = true;
videoElement.srcObject = cameraStream;
```
srcObjectにmediaStreamをセットすればvideoタグにカメラで写している範囲が再生される。

# カメラから画像をキャプチャする

```javascript

function captureImage(videoElement) {
	const canvas = document.createElement('canvas');
	const context = canvas.getContext('2d');
	if (context === null) {
	  throw new Error("canvas.getContext('2d') return null");
	}

	canvas.width = videoElement.videoWidth;
	canvas.height = videoElement.videoHeight;
	context.drawImage(this.videoElement, 0, 0, canvas.width, 		canvas.height);
	return canvas.toDataURL('image/jpeg', 0.95);
}

const capturedImageUrl = captureImage(videoElement)

document.getElementsByTagName("img").src = capturedImageUrl
```

canvasのdrawImageにvideo要素を渡し、canvasからdata-urlとして画像を抜き出すことができる。

よしこれで最高のアプリをつくるぞ！！！

ちょっとまって！そのまえに！

## カメラが起動できない状況を判別する

スクリプトのカメラ起動をユーザは承認/否認することができる。
承認されたときはうまくいきそうだけど、否認された時はどうなるんだろう？
ちゃんとハンドリングしたいよね。

```javascript


function isPermissionDenied(err: Error): boolean {
  return (
    err.message.includes('Permission denied') || // for chrome
    err.message.includes('denied permission.') // for safari
  );
}

try {
	const stream = mediaDevices.getUserMedia(constraints);
} catch (e) {
	if (isPermissionDenied(e)) {
		alert("カメラ利用を承認してもらえないと利用できないんです！")
	}
	// and so on
}

```

Errorのもつメッセージから判別する他方法がない(ﾊｽﾞ)なので`err.message.includes(...)`で判定している。


## カメラを切断する
接続したら切断まで面倒を見よう。

```javascript

function cameraStop(videoElement, cameraStream.getTracks) {
	videoElement.pause();
	videoElement.src = '';
	cameraStream.getTracks()[0].stop();
}
```


```javascript

cameraStream.getTracks()[0].stop();
```

でカメラは切断されます。


とくに気にしておきたいのはスクリプトが存在するページからブラウザのタブのフォーカスが離れたときに切断するか否か。

```javascript

const visibleHandler = () => {
  if (document.visibilityState === 'hidden') {
   	cameraStop(videoElement, cameraStream.getTracks);
  }
};
document.addEventListener('visibilitychange', visibleHandler);
```

タブが非アクティブになったときにはカメラを切断させるようなら上のように`visibilitychange` イベントをトリガーにできる。

## おわりに

タブが非アクティブになったらカメラは閉じるべきか否か、決めよう。
見てないページで思いがけずカメラが起動しているとびっくりしちゃうね。