みかづきブログ・カスタム

基本的にはちょちょいのほいです。

MediaDevices.getUserMediaを使って、PCに接続しているカメラの映像を複数同時に表示する 📷

いままで、MediaDevices.getUserMediaで取得できるストリームはひとつだけ。と思い込んでいましたが、ひょんなことから複数取得できることを知りました。
なので、さっそく、MediaDevices.getUserMediaを使って、PCに接続しているカメラの映像を複数同時に表示するWebサイトを作成してみました。

ソースコード

(async () => {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const streams = await getStreams(devices);

    for (let stream of streams) {
      const video = document.createElement('video');

      video.setAttribute('playsinline', 'playsinline');
      video.srcObject = stream;
      video.style.width = `calc(100% / ${ streams.length })`;
      video.play();

      document.body.appendChild(video);
    }
  } catch (err) {
    alert(err);
  }

  async function getStreams(devices) {
    const streams = [];

    return new Promise(async (resolve) => {
      for (let device of devices) {
        if (device.kind === 'videoinput') {
          streams.push(await navigator.mediaDevices.getUserMedia({
            audio: false,
            video: {
              deviceId: device.deviceId
            }
          }));
        }
      }

      resolve(streams);
    });
  }
})();

MacBookに2台ウェブカメラを繋げてみたのですが、内蔵のカメラを含め無事に3台同時にプレビューできました。
(ときどき、無線でiPhoneのカメラにも繋がって、4台同時プレビューになったときもあったのですが、発生条件がよくわかりませんでした)

これを応用して、ウェブカメラの映像をスイッチできるようにしたものが、こちらです。

ソースコード

(async () => {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const streams = await getStreams(devices);
    const video = document.createElement('video');

    video.setAttribute('playsinline', 'playsinline');
    document.body.appendChild(video);

    for (const stream of streams) {
      ((stream) => {
        const button = document.createElement('button');

        button.innerText = stream.id;

        button.onclick = () => {
          video.srcObject = stream;
          video.play();
        };

        document.body.appendChild(button);
      })(stream);
    }
  } catch (err) {
    alert(err);
  }

  async function getStreams(devices) {
    const streams = [];

    return new Promise(async (resolve) => {
      for (const device of devices) {
        console.log(device)
        if (device.kind === 'videoinput') {
          streams.push(await navigator.mediaDevices.getUserMedia({
            audio: false,
            video: {
              deviceId: device.deviceId
            }
          }));
        }
      }

      resolve(streams);
    });
  }
})();

DEMO

jsfiddle.net

また、iOSやAndroidで、リアカメラとインカメラを切り替えることができるのか検証するために、

(async () => {
  try {
    const facingModes = ['user', {
    	exact: 'environment'
    }];
    const streams = await getStreams(facingModes);
    const video = document.createElement('video');

    video.setAttribute('playsinline', 'playsinline');
		document.body.appendChild(video);

    for (const stream of streams) {
      ((stream) => {
        const button = document.createElement('button');

        button.innerText = stream.id;

        button.onclick = () => {
          video.srcObject = stream;
          video.play();
        };

        document.body.appendChild(button);
      })(stream);
    }
  } catch (err) {
    alert(err);
  }

  async function getStreams(facingModes) {
    const streams = [];

    return new Promise(async (resolve) => {
      for (let facingMode of facingModes) {
        streams.push(await navigator.mediaDevices.getUserMedia({
          audio: false,
          video: {
            facingMode
          }
        }));
      }

      resolve(streams);
    });
  }
})();

というコードを書いてみたのですが、最後に取得したstreamしかプレビューできませんでした。

DEMO

jsfiddle.net

挙動をみるに、streamを複数取得すると、最新のもの以外はstream.activeがfalseになってしまう仕様のようです。