デスクトップブラウザでgetUserMediaを使用してカメラからの映像を表示しつつ、任意のタイミングで画像として保存する

## 目的

デスクトップブラウザで、スマホのカメラアプリ的な動作を実装します。

PCに接続しているカメラの動画をvideoタグに映しつつ、
videoタグの表示領域をクリックするとその時の映像をPNGファイルとしてダウンロードします。

## デモ

Chrome推奨 https://geekduck.github.io/getUserMedia/

![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/16039/ce6312a4-ec6c-64d6-7d4f-7842c72a9a12.gif)


## 機能

1. MediaDevices.enumerateDevices APIを使用してカメラの情報を取得する(複数カメラ接続にも対応)
2. MediaDevices.getUserMedia APIを使用してユーザが選んだデバイスIDのカメラ映像をvideoタグに表示する
3. videoタグから画像を生成してダウンロードさせる

## MediaDevices.enumerateDevices APIを使用してカメラの情報を取得する(複数カメラ接続にも対応)

```js
// 接続されているカメラとマイクのMediaStreamオブジェクトを取得する
navigator.mediaDevices.enumerateDevices().then(function(sourcesInfo) {
  // 取得できたカメラとマイクを含むデバイスからカメラだけをフィルターする
  var videoSroucesArray = sourcesInfo.filter(function(elem) {
      return elem.kind == 'videoinput';
  });
  render(videoSroucesArray);
});

/**
 * カメラを選択するセレクトボックスを組み立てる
 */
function render(videoSroucesArray) {
    var $selectBox = document.querySelector('#select')
    videoSroucesArray.forEach(function(source, idx) {
        var label = source.label !== "" ? source.label : ("カメラ" + idx);
        $selectBox.insertAdjacentHTML("beforeend", "<option value='" + source.deviceId + "'>" + label + "</option>");
    });
    return this;
}
```

[MediaDevices.enumerateDevices](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/enumerateDevices) APIを使用することで、
[MediaDeviceInfo](https://developer.mozilla.org/ja/docs/Web/API/MediaDeviceInfo)オブジェクトの配列が取得できます。

このMediaDeviceInfoオブジェクトは接続されているデバイスの種類(カメラorマイク)やデバイスID、ラベル名などの情報を持ったオブジェクトです。
具体的には以下のようなプロパティ名を持ったオブジェクトです。

```json:マイクの場合
{
  deviceId: "61f2432c64f7d3c9ebb1be1c754bdaf0d555df7b83a4a7d2c68aec92f15829c5"
  groupId: "3104884528"
  kind: "audioinput"
  label: ""
}
```

```json:カメラの場合
{
  deviceId: "3052d5f08d08eb5a356080db8fce7efb4602cd1774274f1eac4d32353ea98f9b"
  groupId: ""
  kind: "videoinput"
  label: "FaceTime HD カメラ"
}
```

実際にカメラと接続するgetUserMedia APIに必要な情報はデバイスIDだけです。
取得したデバイスIDとラベル名を使用してセレクトボックスを組み立てています。


## MediaDevices.getUserMedia APIを使用してユーザが選んだデバイスIDのカメラ映像をvideoタグに表示する

```js
var currentStream;
/**
 * カメラの再生を開始する
 */
function start(e) {
    // 既にカメラと接続していたら停止
    if (currentStream) {
        currentStream.getVideoTracks().forEach(function(devise) {
            devise.stop();
        });
        currentStream = null;
    }

    if (e.target.value === "") {
        return;
    }

    var constraints = {
        video: {
            optional: [{
                sourceId: e.target.value
            }]
        }
    };
    // Video と Audioのキャプチャを開始
    navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
        currentStream = stream; // カメラを切り替えた時に停止するため格納
        var $video = document.querySelector('#video');
        $video.src = window.URL.createObjectURL(stream);
        $video.play(); // firefox用
    }, function() {
        console.log("error:" + arguments);
    })
}

var selectBox = document.querySelector('#select');
selectBox.addEventListener('change', start, false);
```

セレクトボックスで選ばれたデバイスIDを引数に[MediaDevices.getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) APIを実行するとカメラからの映像を取得できます。
第一引数はMediaStreamConstraintsオブジェクトと呼ばれていて以下のような形式で指定します。
sourceIdに指定しているのはMediaDevices.enumerateDevicesで取得したデバイスIDです。

```json:MediaStreamConstraintsオブジェクト
{
  video: {
    width: 1280,
    height: 720,
    optional: [{
      sourceId: "3052d5f08d08eb5a356080db8fce7efb4602cd1774274f1eac4d32353ea98f9b"
    }]
  },
  audio: true
}
```

## videoタグから画像を生成してダウンロードさせる

```js
function download() {
    var cEle = document.createElement('canvas');
    var cCtx = cEle.getContext('2d');
    var vEle = document.querySelector('#video');

    cEle.width = vEle.videoWidth; // canvasの幅と高さを、動画の幅と高さに合わせる
    cEle.height = vEle.videoHeight;

    cCtx.drawImage(vEle, 0, 0); // canvasに関数実行時の動画のフレームを描画
    var aTag = document.createElement('a');
    aTag.setAttribute('href', cEle.toDataURL());
    aTag.setAttribute('download', "video.png");
    aTag.click(); // Firefoxでは動かない
}

var video = document.querySelector('#video');
video.addEventListener('click', download, false);
```

canvas要素のtoDataURLメソッドを使って、videoタグの映像を切り出してBase64エンコード文字列に変換します。
その文字列をaタグのhrefに指定してダウンロードさせています。

## getUserMediaのハマりポイント
1. (少なくともChromeは)localhost接続か、https接続でしか使えない
    https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins

2. 古いAPIが非推奨から廃止になった
    https://developers.google.com/web/updates/2015/07/mediastream-deprecations