p5.js で利用する内蔵／Webカメラ映像を特定のデバイス名のものに設定する（配列メソッドと MediaDevices.enumerateDevices() を利用）

## はじめに
この記事の内容は、「p5.js で内蔵／Webカメラの映像を使う処理を行う場合に、その映像の取得元を特定のデバイス名を使って指定する方法」に関する話です。

ここで言うデバイスという話は、例えば自分がよく使っている PC内蔵／Webカメラの例では、「Mac の内蔵カメラ（FaceTime HDカメラ）」や「Logitech StreamCam」などがあります。

もし複数のカメラが接続された PC で、JavaScript を使ってカメラ映像を取得した場合、基本的にはブラウザのカメラ利用設定など、プログラム外の設定に依存したものが選ばれたりします。それを意図的に指定する方法に関する話が、今回の記事の内容です。

それに加えて、「カメラ映像を取得できるデバイス名を、Webサイト上やブラウザ上で確認する方法」も、合わせて書いています。

### 試したきっかけ
今回の内容を試したきっかけは、[MediaDevices.getUserMedia()](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/getUserMedia) でカメラ映像を扱う処理で、[デバイス指定を行っていた記事](https://zukucode.com/2020/05/javascript-camera-device.html)を見かけたことです。
配列メソッドを使って、良い感じにデバイス情報を取得していたやり方などを見て、自分も試してみたくなりました。

## カメラのデバイス名を確認する
本編に入っていく前に、まずは内蔵／Webカメラのデバイス名（JavaScript で扱うためのもの）を取得する方法を ２つ掲載します。
※ これらの方法を試すにあたり、デバイス名を確認したいカメラを PC につないだ状態にしておいてください

[今回の内容を試すきっかけになった参照元](https://zukucode.com/2020/05/javascript-camera-device.html)に書かれていた処理をベースにしたものと、既存の Webサイト上で試せる方法を紹介してみます。

### JavaScript の処理で取得する
方法の 1つは、[MediaDevices.enumerateDevices()](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/enumerateDevices) を実行して、得られるデバイスのリストから確認する方法です。具体的には、以下の処理です。

```javascript
navigator.mediaDevices.enumerateDevices().then((devices) => {
  // console.log(devices);
  console.log(devices.filter((device) => device.kind === "videoinput"));
});
```

上記でコメントアウトしている `console.log(devices)` は、出力としてカメラ映像に関するものだけでなく、マイクなど音声に関わるものなども含め全てが出てきます。
今回は、映像を取得するデバイスだけが分かれば良いので、その全ての出力ではなく `kind === "videoinput"` という条件でフィルタしたデバイスのみを出力して、ログ出力をしています（※ [今回参照した記事](https://zukucode.com/2020/05/javascript-camera-device.html)の処理を、ほぼそのまま使っています）。

この処理を、ブラウザの開発者ツールのコンソールで実行して、結果を確かめることもできます。また、何らか JavaScript を実行できる環境で実行しても問題ありません。
自分は、ブラウザ上で処理が実行できる「[p5.js Web Editor](https://editor.p5js.org/)」で試して、以下のように出力を確認したりしました。

https://twitter.com/youtoy/status/1642883234257162240

出力された配列の各要素の「label」を見ると、デバイス名を確認できます。

### WebRTC samples のページで確認する
別の方法の 1つに、WebRTC samples として公開されているサンプルを使う方法があります。そのページのアクセス先は以下になります。

●Select audio and video sources
　https://webrtc.github.io/samples/src/content/devices/input-output/

ページ上部にある「Video source:」のプルダウンメニューを展開すると、以下の画像のように、利用可能なカメラの名称を確認することができます。
![WebRTC samples のページで確認する](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/0d8148bb-24af-208d-e768-d362de2a84ee.png)

### デバイス名に関する注意事項
以下の MDN のページにも記載がありますが、ユーザによる権限付与の設定状況などによって、デバイス名が書かれている部分（「label」の中身）が空欄になることがあるようです。

●MediaDeviceInfo - Web API | MDN
　https://developer.mozilla.org/ja/docs/Web/API/MediaDeviceInfo
![labelのフィールドについて](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/ab5500ef-7088-a1d9-1add-e85fa3bf6168.png)

## p5.js での実装内容について
ここから、p5.js で実装した内容について紹介していきます。
なお、以下では HTML・CSS に関する部分は省略し、p5.js（JavaScript）の処理部分だけ書いています。

以下で掲載したものは、p5.js Web Editor を使うと、簡単に動作を試すことができます（sketch.js を、以下のものに書きかえて実行すれば OK です）。

### ソースコード
以下が、p5.js で実装した内容です。
一部、デバッグ用の処理を残しています（※ コメントアウトしている console.log() の処理部分）。

```javascript
let capture;

const cameraLabel = "FaceTime HDカメラ";
// const cameraLabel = "Logitech StreamCam";

function setup() {
  createCanvas(640, 480);

  navigator.mediaDevices.enumerateDevices().then((devices) => {
    // console.log(devices);

    foundDevice = devices.find(
      (device) =>
        device.label.includes(cameraLabel) && device.kind === "videoinput"
    );

    if (foundDevice) {
      // console.log(foundDevice.label);

      const deviceId = foundDevice.deviceId;
      const constraints = {
        audio: false,
        video: { deviceId },
      };

      capture = createCapture(constraints);
      capture.hide();
    } else {
      console.log("Device not found");
    }
  });
}

function draw() {
  background(220);

  if (capture) {
    image(capture, 0, 0);
  }
}
```

### ソースコードに関する補足
上記の処理の内容を補足します。

setup() の中の主な流れは、以下のとおりです。

1. createCanvas() で p5.js用の描画領域を用意
1. [MediaDevices.enumerateDevices()](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/enumerateDevices) を使った処理で、デバイス名に特定の文字列を含むデバイスの ID を取得（※ それを foundDevice　で保持）
1. 取得したデバイスID を使い、p5.js の [createCapture()](https://p5js.org/reference/#/p5/createCapture) の処理で、特定のデバイスのカメラ映像を取得できるようにする

上記手順のデバイスID を指定するところ、 `video: { deviceId }` の部分は[分割代入](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)を使っています（ここは、 `video: { deviceId: deviceId }` と書いても同じです）。

#### deviceId に関する補足
上で用いている「deviceId」の仕様は、[MDN の MediaDeviceInfo のページ](https://developer.mozilla.org/ja/docs/Web/API/MediaDeviceInfo)などで確認できます。
![deviceId について](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/914953e3-4ed6-d7d1-3f2c-5766f742a524.png)
　
#### 今回の内容における制限事項
今回の内容の制限事項について補足しておきます。

今回、カメラ映像を取得するデバイスは「cameraLabel」で保持している文字列を含むもの、としています。具体的には、上記の例では「FaceTime HDカメラ」を含むもの（※ 完全一致ではなく、部分一致）となります。
そして、その条件を満たす最初の 1つを取得するという内容にしています。

そのため、例えば上記で「cameraLabel」を「Logitech StreamCam」のほうにした場合で、この「Logitech StreamCam」を 2台以上 PC につないだ場合は、意図しないデバイスが選ばれる可能性があるのでご注意ください。

これは、簡単化のために「同じ名前のデバイスが 1つだけ」というのを前提条件としていること、その前提にしたがって、指定条件を満たす配列内の最初の要素を返す [Array.prototype.find()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/find) を使っていることに起因します。

もし、同じ種類のデバイスが同時に 2つ以上接続されていても、それを意図して使い分けられるように対応したい場合は、デバイスごとに固有の情報を抽出条件にするなどといった対応を行ってください（今回は、その話は省略します）。

### 実行結果について
上記を自分の環境（MacBook Pro）で実行すると、内蔵カメラが用いられます。また、Logitech StreamCam を接続した状態で、コメントアウトしている「Logitech StreamCam」のほうを用いるようにすると、内蔵カメラではなく Logitech StreamCam が用いられます。

もし、Logitech StreamCam が接続されていない状態で、コメントアウトしている「Logitech StreamCam」のほうを用いると、内蔵カメラが用いられることはなく、カメラ映像は出力されません。

## 【追記】 余談
その後、2つのカメラデバイスを指定して、「それらの映像を同時取得 ＆ キャンバス上で混ぜ合わせる」というのを試してみたりしました。

https://twitter.com/youtoy/status/1642899060800770048

## 関連する内容が書かれたサイト
### 実装の参考にしたサイト
今回の実装のために参考にしたページを掲載します。

- [JavaScript 使用可能なカメラを列挙して撮影するカメラを指定する](https://zukucode.com/2020/05/javascript-camera-device.html)
- [MediaDevices.enumerateDevices() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/MediaDevices/enumerateDevices)
- [MediaDeviceInfo - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/MediaDeviceInfo)

### p5.js ＋ MediaDevices.enumerateDevices() の利用を試されていた話
「p5.js」と「MediaDevices.enumerateDevices()」のキーワードで検索したら、「mediaDevices.enumerateDevices()」を使って「deviceId」を取得する話を書かれている記事が出てきたので、こちらも掲載してみます。

●【JavaScript】USB接続のWebカメラから映像を取得する方法【p5.js】 - ダークサイドにようこそ!
　https://hahaeatora.hateblo.jp/entry/2019/11/11/200000