【React】端末に接続されている機器を取得する
概要
WebRTC
を実現する上で、デバイスの操作は避けては通れません。
Amazon Chime
やZoom API
などのライブラリ・コンポーネントはデバイス操作をラップして提供してくれていたりしますが、自前で行う場合は普段使わないようなAPIを用います。
今回は自分の学習メモも兼ねて、その辺りをまとめたいと思います。
端末の取得
enumerateDevices
で取得する
React
においては、navigator.mediaDevices
ではじまるMediaDevices
というAPIにアクセスすることで大体が実現できます。
端末一覧を取得するにはenumerateDevices()
を用います。
レスポンスはPromise<MediaDeviceInfo[]>
で、MediaDeviceInfo
は下記のような構造です。
{
deviceId: 'xxxxx',
groupId: 'yyyyyy',
kind: 'audiooutput',
label: 'ほげほげ'
}
UI
上で表示されるのはlabel
の値ですが、内部的に端末のスイッチを行う際はdeviceId
を用います。
またkind
のプロパティがあることから、 マイク・スピーカー・カメラが合わせて配列として返ってきます。
なので用途にもよりますが、表示する際にはkind
でfilter
をかけて使用するのが一般的です。
useSpeakerDeviceList
例.例えば自分は以下のようにカスタムフックを作成して各ロジック上で使用しています。
import {useState, useEffect, useCallback} from 'react';
// スピーカー機器のリストを取得するカスタムフック
const useSpeakerDeviceList = () => {
const [devices, setDevices] = useState<MediaDeviceInfo[]>([]);
// 機器の更新処理
const refreshDevices = useCallback(async () => {
const latestDevices = (
await navigator.mediaDevices.enumerateDevices()
).filter((d) => d.kind === "audiooutput");
setDevices(latestDevices);
}, []);
useEffect(() => {
// 初回(=componentDidMount)で最新の機器一覧を取得
refreshDevices();
// 以降は機器の着脱が行われるタイミングで更新処理を実施
navigator.mediaDevices.addEventListener("devicechange", refreshDevices);
return () => {
navigator.mediaDevices.removeEventListener(
"devicechange",
refreshDevices
);
};
}, []);
return devices;
};
ポイントはMediaDevices
に対してイベントリスナーを設定して、機器の着脱を検知して常に最新の配列を返すようにしている点です。
他の機器(カメラ・マイク)についてはfilter
をかける際の条件を変えればOKです。
端末の使用
端末の一覧が取得できたので、後はそれを使用する方法について記載します。
この辺りから機器の種類によって実装方法が分かれてきます。
カメラ(マイク)
カメラの出力は<video>
タグなので、基本はそのref
を操作していくイメージです。
MediaDevices
にはgetUserMedia
というメソッドがあり、カメラとマイクに関してはこれで設定ができます。
※マイクに関しては、音の大小をUIとして表示したい場合のみこのような書き方をする必要があるので割愛します。
import { useEffect, useRef } from "react";
const SampleVideo = (selectedVideo: MediaDeviceInfo) => {
// <video>のrefを取得しておく
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (videoRef) {
navigator.mediaDevices
.getUserMedia({
// ここでvideoに選択しているデバイスの情報をそのまま渡す
video: selectedDevice,
})
.then((stream) => {
if (videoRef?.current) {
videoRef.current.srcObject = stream;
videoRef.current.play();
}
})
.catch((e) => {
// エラー処理
});
}
}, [videoRef, selectedDevice]);
return (
<div>
<Video ref={videoRef} id="video"></Video>
</div>
);
}
スピーカー
スピーカーについてはUI
に何か表示されるより、任意の音声を流すようなユースケースが多いと思うので、その方法について記載します。
Audio
インスタンス(HTMLAudioElement
)のコンストラクタに音声ファイルのパスを指定して、その後でsetSinkId
を実行します。
※一旦any
にキャストしている理由ですが、HTMLAudioElement
はHTMLMediaElement
を継承しているためHTMLMediaElement
の持つsetSinkId
は使えるはずですが、自分の環境の型定義(@types/react-dom@*
の17.0.11
)だと型エラーになったためです。動作自体は行われるので一旦問題なしとしています。
const play = (selectedDevice: MediaDeviceInfo) => {
let audio = new Audio(`https://example.com/sample.mp3`);
await (audio as any)?.setSinkId(selectedDevice?.deviceId);
audio.play();
// 音が流れた後で何かしたい場合はイベントリスナーを設定する
audio.addEventListener("ended", () => {
// 何かしらの処理...
});
};
まとめ
今回はReact + Typescript
の環境において、特定のライブラリに依存することなくカメラ・マイク・スピーカーを扱うTips
について紹介しました。
微妙な書き方をしている箇所もあるため、もう少しブラッシュアップは必要そうですが、概ね今回のようなAPIを扱って実装していくかと思います。
今回の内容が役立ちましたら幸いです。
Discussion