Webで行う切り替え可能なカメラコンポーネントの開発

ブログメインビジュアル

こんにちは、フロントエンドエンジニアの峯です。
QRコードの読み取りや写真撮影など、Webサービスでカメラを利用することがありますが、ユーザーが利用するカメラを選択することができるインターフェースを提供することで、ユーザビリティを向上させることができます。


そこで今回は、Reactを使って、ユーザーが利用するカメラを切り替えることができるカメラコンポーネントを作成します。

このコンポーネントでは、<video>要素を使用してカメラからの映像を表示します。また、MediaDevicesenumerateDevices()メソッドを使用して、利用可能なカメラのリストを作成し、カメラを切り替える機能を実装します。

DEMOはこちら | CodeSandbox


カメラコンポーネントの作成

まずは、基本的なカメラコンポーネントを作成します。


カメラからの映像を表示するには、<video>要素を使用します。<video>要素にメディアデバイス情報を渡すことで、カメラからの映像を表示できます。

import React, { useEffect, useRef } from 'react'

const Camera = () => {
  const refVideo = useRef<HTMLVideoElement>(null)

  useEffect(() => {
    const constraints = { video: { facingMode: "user" }}
    navigator.mediaDevices.getUserMedia(constraints)
      .then(stream => {
        if(refVideo?.current) {
          // videoに対して、メディアデバイス情報を渡す
          refVideo.current.srcObject = stream
        }
      })
      .catch(err => console.error("Error", err))
  }, [])

  return (
    <video ref={refVideo} autoPlay muted playInline />
  )
}

export default Camera

このカメラコンポーネントを読み込むと、ブラウザからカメラの起動リクエストが送信されます。ユーザーがカメラ使用を許可すると、ブラウザ上にカメラからの映像が表示されます。


ポイント

iPhoneなどの一部の端末で、カメラを使用する際に以下のエラーが発生することがあります。

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

これは、JavaScriptから音声や動画を扱おうとする場合に発生するエラーです。このエラーを回避するために、<video>要素にautoPlay、muted属性を追加します。


利用可能なカメラ一覧を返すカスタムフックの作成

カメラの切り替えには、MediaDevicesenumerateDevices()メソッドを利用します。
利用可能なカメラを返すカスタムフックを作成します。

import { useState, useEffect, useCallback } from "react";

export const useVideoDeviceList = () => {
  const [devices, setDevices] = useState<MediaDeviceInfo[] | []>([]);

  const refreshDevices = useCallback(async () => {
    await navigator.mediaDevices
      .enumerateDevices()
      .then((devices) => {
        const videoDevices = devices.filter(
          (device) => device.kind === "videoinput"
        );
        setDevices(videoDevices);
      })
      .catch((error) => console.error(error));
  }, []);

  useEffect(() => {
    refreshDevices();

    navigator.mediaDevices.addEventListener("devicechange", refreshDevices);
    return () => {
      navigator.mediaDevices.removeEventListener(
        "devicechange",
        refreshDevices
      );
    };
  }, [refreshDevices]);

  return { devices };
};

デバイスの接続情報の変更をトリガーに、ビデオデバイスを返すrefreshDevices()メソッドを作成し、これをstateに格納して返却するカスタムフックを作成しました。


ポイント

enumerateDevices()の返り値はPromiseを返すため、async/awaitを使って非同期処理を行いました。

カメラの切り替えを有効にする

作成したuseVideoDeviceListをカメラコンポーネントに組み込んで、カメラの切り替えを有効にしていきます。

import React, { useState, useEffect, useRef } from "react";
import { useVideoDeviceList } from "../hooks/useVideoDeviceList";

const Camera = () => {
  const refVideo = useRef(null);
  const [selectedDevice, setSelectedDevice] = useState<string | null>(null);
  const { devices } = useVideoDeviceList();

  const getDevice =
    devices &&
      selectedDevice &&
        devices.find((v) => v.label === selectedDevice);

  useEffect(() => {
    // カメラ情報が取得できない場合はフロントカメラを利用する
    const constraints = getDevice
      ? { video: { deviceId: getDevice.deviceId } }
      : { video: { facingMode: "user" } };

    selectedDevice &&
      navigator.mediaDevices
        .getUserMedia(constraints)
        .then((stream) => {
          if (refVideo?.current) {
            refVideo.current.srcObject = stream;
          }
        })
        .catch((err) => {
          console.error("Error", err);
        });
  }, [getDevice, selectedDevice]);

  useEffect(() => {
    // 利用デバイスの初期設定
    devices && devices?.[0] && setSelectedDevice(devices[0]);
  }, [devices]);

  return (
    <div>
      <video
        ref={refVideo}
        autoPlay
        muted
        playsInline
        style=
      />
      <div>
        <select onChange={(e) => setSelectedDevice(e.target.value)}>
          {devices.map((v) => (
            <option key={v.deviceId}>{v.label}</option>
          ))}
        </select>
      </div>
    </div>
  );
};

export default Camera;

これで、端末に接続されているビデオデバイスの切り替えが可能なカメラコンポーネントができました!

iPhoneで利用すると、フロントカメラ、リアカメラ、ウルトラワイドカメラなどの切り替えることができます。また、PCに接続されたWebカメラにアクセスすることも可能です。


DEMOはこちら | CodeSandbox

さいごに

今回は、ReactでCameraコンポーネントの作成を行いました。ただし、NextやGatsbyのSSR環境下でこのコンポーネントを利用する場合は、別途注意が必要です。今回利用したnavigator APIはクライアントサイドのAPIであることに注意が必要です。

ブラウザAPIには便利で知られていない機能がまだまだありますが、これらを使用する場合はアクセス先などの仕組みを考慮して開発を行うことが重要です。

この記事を書いた人 mine 2019年1月 中途入社のゴルフにはまっているフロントエンドエンジニア
COBOL開発経験がありますが平成生まれです。
TOP