ブラウザからメディアデバイスを操る 前編 getUserMedia()の基本
getUserMedia()は、カメラやマイクなどメディアデバイスにアクセスできるAPIです。今回はもっともシンプルなデモで、引数の概要をおさえ、このAPIの使い方を掴んでみましょう。
- カテゴリー
- JavaScript >
- ブラウザAPI
発行
はじめに
今回の記事では、Media Capture and Streamsという仕様の中から、getUserMedia()
というAPIを紹介します。
Media Capture and Streamsは大まかにいうと、ローカルデバイス上のカメラやマイクにアクセスするためのJavaScript APIがまとまっている仕様です。今回の記事ではMedia Capureの部分を、次回の記事ではStreamsの部分に焦点をあてて解説していきます。
この仕様に含まれるgetUserMedia()
というAPIを使うことで、端末のカメラやマイクの入力ストリームを、JavaScriptから取得・操作することができます。
APIやコードの解説をしていく前に、このAPIを使うと何ができるようになるのか、簡単な例をデモで示します。なお、このシリーズの以降のデモは、お使いの端末にカメラやマイクが付いていない場合、またはこのAPIに対応していない環境(対応環境は後述)では確認できません。
ボタンをクリックするとカメラの利用許可が求められます。それを許可すると、あらかじめ設置したvideo
タグにカメラの映像が映し出されるデモです。
このように取得した音や映像のストリームはさまざまな使い方ができます。
- WebRTCのAPIを使って送受信し、ビデオチャットをする
- Image Capture APIに渡してキャプチャ
- マイクから音を拾って、WebAudio APIで利用
- Media Recorder APIで録画
そのほかにもアイデア次第でいろいろなことに利用できますし、それらはすべて、JavaScriptで記述でき、ブラウザ上で動きます。
利用できる環境
前提として、対応ブラウザについて触れておきます。
対応ブラウザ:Can I use... Support tables for HTML5, CSS3, etc
- Edge
- Firefox
- Chrome
- Safari
つい先日ですが、Safariのバージョン11がリリースされ、WebRTCの対応とあわせて実装されています。そのため現時点では、IEを除くすべてのモダンブラウザで利用が可能となっています。
Safari 11はiOSでも利用可能なため、iPhoneやiPadからも利用が可能です。
また開発時に注意したいこととして、プロトコルはhttps
である必要があります。端末のカメラにアクセスできてしまうため、セキュリティには配慮すべきという理由からです。次の仕様書にその旨が書いてあります。
localhost
やfile://
で開発するだけなら問題ないブラウザもありますが、サービスを公開する場合には、必ずhttps
で配信するようにしてください。
シンタックス
それではさっそくコードを見ていきます。getUserMedia()
は、グローバルオブジェクトであるNavigator
オブジェクトのmediaDevices
クラスから利用できます。
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
// ...
})
.catch(err => {
// ...
});
IDLは次のとおりです。
Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
このようにPromise
を返すAPIとなっており、resolve
された場合は、MediaStream
が取得できます。reject
された場合は、一般的なほかのPromise
と同じくエラーが渡され、それでエラーハンドリングをすることになります。
【ワンポイント】非推奨のシンタックス
最新の仕様だとPromise
が返されますが、次のようにコールバックを渡す方式だったことも昔はありました。またかつてはmediaDevices
ではなくNavigator
オブジェクトから直接利用していました。インターネットで検索する場合、古い記事が見つかることも多いため注意が必要です。
非推奨の古いシンタックス
// これは古いシンタックスのため非推奨
navigator.getUserMedia(constraints, successCallback, errorCallback);
このAPIを実行すると、最初は必ずユーザーにマイク・カメラの利用許可を確認するダイアログが表示されます。
Firefoxの場合は、どのマイク・カメラを使用するかの選択もできるようになっています。
これらのダイアログで許可をすることで、そのオリジンでの利用が許可できます。
次のような場合は、ストリームの取得ができずreject
されます。
- 許可ダイアログを拒否した場合
- 許可ダイアログをキャンセルした場合
- 利用可能なカメラ・マイクがない場合
- 引数で指定した条件に見合うカメラ・マイクが見つからない場合(後述)
ブラウザにもよりますが、ブラウザがカメラやマイクを利用している間はタブにマークがつくものもあります。またハードウェアの機能として、カメラの横にあるインジケータが点灯する端末もあります(Macなど)。
一度与えた許可を取り消す方法もありますが、ブラウザごとに詳細は異なります。Chromeの場合、アドレスバーのサイト情報の部分や、右側のカメラアイコンをクリックして表示されるコントロール画面から、与えた許可を取り消すことができます。
引数で指定できる値
getUserMedia()
に渡せる唯一の引数についてです。オブジェクトでvideo
とaudio
それぞれのプロパティを指定します。
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
先の例ではこのように、video
とaudio
にそれぞれtrue
を指定していただけでした。
dictionary MediaStreamConstraints {
(boolean or MediaTrackConstraints) video = false;
(boolean or MediaTrackConstraints) audio = false;
};
IDLによると、video
とaudio
それぞれ、boolean
かMediaTrackConstraints
の指定が可能となっています。
どちらもtrue
にすると、カメラとマイク両方の許可を求めるようになりますし、false
を設定した場合や未指定の場合は許可を求めません(利用しません)。
指定できる値とその意味
実は、この設定値はMediaTrackConstraints
という値で、よりさまざまな指定ができます。
たとえばこのような指定が可能です。これは、カメラの解像度を指定しています。
{ audio: true, video: { width: 1280, height: 720 } }
もうひとつの指定の例です。こうすることで、リアカメラではなくフロントカメラを利用したい旨を示すことができます。
{ audio: true, video: { facingMode: 'user' } }
そのほかにもaspectRatio
やframeRate
など、仕様上は、さまざまな値の指定が可能となっています。
ただ、指定できる値は「設定値」というよりも、その名のとおり「制約」と捉えるとわかりやすいです。というのも、必ずその指定になるというわけではなく、最終的にはブラウザが判断した結果になるからです。
これら詳細な指定の方法については、仕様書に詳しく記載されているので、そちらを確認してみてください。
デモのコード解説
getUserMedia()
の使い方がわかったところで、冒頭のデモのコードを抜粋して、解説していきます。
// スタートのボタン
const $start = document.getElementById('s');
// video要素
const $video = document.getElementById('v');
$start.addEventListener('click', () => {
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(stream => $video.srcObject = stream)
.catch(err => alert(`${err.name} ${err.message}`));
}, false);
getUserMedia()
でカメラのストリームのみを取得しています。取得できたストリームは、video
要素のsrcObject
に代入することで、そのまま再生することができます。
.then(stream => $video.srcObject = stream)
HTML側でvideo
要素にautoplay
属性を指定しておくか、JavaScriptからplay()
メソッドを実行して再生する必要があることに注意してください(今回のデモではautoplay
属性を利用します)。
<video id="v" autoplay></video>
また、iOSのSafariで利用する場合、playsinline
属性を指定することも合わせて検討してください。playsinline
属性が指定されていない場合、ストリームはフルスクリーンで再生されます。
getUserMedia()
で取得したMediaStream
の詳細や、取得した入力ストリームをストップする方法など詳しくは次回の記事で紹介します。
また、この例ではカメラを使いましたが、マイクだけを利用することももちろん可能です。
// audioのみ
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
// なのでaudio要素で
const $a = document.createElement('audio');
// 自動再生
$a.autoplay = true;
// video要素と同じくsrcObjectに
$a.srcObject = stream;
});
これは端末マイクから拾った音をそのまま端末スピーカーから出力するコードなので、試す際にはハウリングに注意してください。
入力デバイスを切り替える
Firefoxの場合、カメラ・マイクの利用許可を求めるダイアログで、どのデバイスを利用するかを選択することができました。Chromeなどのブラウザではその選択肢が提示されないため、デバイスを切り替える場合は自分でコードを書く必要があります。
利用可能なデバイスは、navigator.mediaDevices.enumerateDevices()
メソッドから取得できます。
navigator.mediaDevices.enumerateDevices()
.then(devices => {
// ...
});
このように利用可能なデバイス(MediaDeviceInfo
)配列が、Promiseで取得できます。これは筆者の端末のChromeでの実行結果の例です。
[
{
"deviceId": "default",
"kind": "audioinput",
"label": "既定",
"groupId": "0bb7f3d82698e289d1ab1b99de7139c99215c65a5dc77fd578c6fdabe6e3eb5f"
},
{
"deviceId": "0c58cf01fb6731adf5da0ab582e71495883441f6c097fa85f19404c21cf82e68",
"kind": "audioinput",
"label": "内蔵マイク",
"groupId": "f844fc6895c779ff9b667fddf332196db77ff21595c61e1be6cab5b9aedd3537"
},
{
"deviceId": "12306dec1d94e5a71133e20f38d65b9c40436621f86cdecdc77d4a864983e56c",
"kind": "audioinput",
"label": "C-Media USB Headphone Set ",
"groupId": "0bb7f3d82698e289d1ab1b99de7139c99215c65a5dc77fd578c6fdabe6e3eb5f"
},
{
"deviceId": "default",
"kind": "audiooutput",
"label": "既定",
"groupId": "da17c863b782c096e3566e2ac6659fcf21eb88dae74969743235f0c058cc984b"
},
{
"deviceId": "3246e74d1aba9726d23951954896725cef3849b97668f70efab25042847d2191",
"kind": "audiooutput",
"label": "内蔵出力",
"groupId": "f844fc6895c779ff9b667fddf332196db77ff21595c61e1be6cab5b9aedd3537"
},
{
"deviceId": "01b28862a79a4def59cf36e4df30e551fc6264ab072333349b19c756de4b7ff6",
"kind": "audiooutput",
"label": "HDMI",
"groupId": "da17c863b782c096e3566e2ac6659fcf21eb88dae74969743235f0c058cc984b"
},
{
"deviceId": "93e831e4aa052c2cce2bcff714a48466de8114849cbd98f18e29bbee49255daa",
"kind": "audiooutput",
"label": "C-Media USB Headphone Set ",
"groupId": "0bb7f3d82698e289d1ab1b99de7139c99215c65a5dc77fd578c6fdabe6e3eb5f"
}
]
MacBook ProをHDMIで外部モニタに接続しつつ、外付けのUSBヘッドセットを接続しているため、たくさんの候補があります。
こうして取得したdeviceId
を、getUserMedia()
の引数で指定します。
{
audio: { deviceId: audioDeviceId },
video: { deviceId: videoDeviceId },
}
こうすることで、任意の入力デバイスを使用することができます*。
*注:Chromeの相違点
現時点ではChromeのみ、enumerateDevices()
の結果に入力デバイスだけでなく出力デバイスも含まれるようです。詳しくは触れませんが、出力デバイスのdeviceId
は、Audio Output Devices APIで定義されるsetSinkId()
というメソッドを使うことで、出力デバイスの制御に利用できます。
おわりに
今回の記事では、ブラウザから端末のカメラ・マイクにアクセスできるgetUserMedia()
について紹介しました。こんなにも簡単なコードで実現することができ、何か試しにコードを書いてみたくなったのではないでしょうか。
また、getUserMedia()
が受け取る引数についての指定についても紹介しました。
今回は単に取得したストリームをvideo
要素に表示するだけでしたが、次回の記事ではさらに応用したデモを紹介する予定です。