こんにちは、フロントエンドエンジニアの峯です。
QRコードの読み取りや写真撮影など、Webサービスでカメラを利用することがありますが、ユーザーが利用するカメラを選択することができるインターフェースを提供することで、ユーザビリティを向上させることができます。
そこで今回は、Reactを使って、ユーザーが利用するカメラを切り替えることができるカメラコンポーネントを作成します。
このコンポーネントでは、<video>
要素を使用してカメラからの映像を表示します。また、MediaDevices
のenumerateDevices()
メソッドを使用して、利用可能なカメラのリストを作成し、カメラを切り替える機能を実装します。
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属性を追加します。
利用可能なカメラ一覧を返すカスタムフックの作成
カメラの切り替えには、MediaDevices
のenumerateDevices()
メソッドを利用します。
利用可能なカメラを返すカスタムフックを作成します。
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カメラにアクセスすることも可能です。
さいごに
今回は、ReactでCameraコンポーネントの作成を行いました。ただし、NextやGatsbyのSSR環境下でこのコンポーネントを利用する場合は、別途注意が必要です。今回利用したnavigator APIはクライアントサイドのAPIであることに注意が必要です。
ブラウザAPIには便利で知られていない機能がまだまだありますが、これらを使用する場合はアクセス先などの仕組みを考慮して開発を行うことが重要です。