12周年記念パーティ開催! 2024/5/10(金) 19:00

ブラウザからメディアデバイスを操る 前編 getUserMedia()の基本

getUserMedia()は、カメラやマイクなどメディアデバイスにアクセスできるAPIです。今回はもっともシンプルなデモで、引数の概要をおさえ、このAPIの使い方を掴んでみましょう。

発行

著者 杉浦 有右嗣 シニアエンジニア
ブラウザからメディアデバイスを操る シリーズの記事一覧

はじめに

今回の記事では、Media Capture and Streamsという仕様の中から、getUserMedia()というAPIを紹介します。

Media Capture and Streamsは大まかにいうと、ローカルデバイス上のカメラやマイクにアクセスするためのJavaScript APIがまとまっている仕様です。今回の記事ではMedia Capureの部分を、次回の記事ではStreamsの部分に焦点をあてて解説していきます。

この仕様に含まれるgetUserMedia()というAPIを使うことで、端末のカメラやマイクの入力ストリームを、JavaScriptから取得・操作することができます。

APIやコードの解説をしていく前に、このAPIを使うと何ができるようになるのか、簡単な例をデモで示します。なお、このシリーズの以降のデモは、お使いの端末にカメラやマイクが付いていない場合、またはこのAPIに対応していない環境(対応環境は後述)では確認できません。

ボタンをクリックするとカメラの利用許可が求められます。それを許可すると、あらかじめ設置したvideoタグにカメラの映像が映し出されるデモです。

このように取得した音や映像のストリームはさまざまな使い方ができます。

そのほかにもアイデア次第でいろいろなことに利用できますし、それらはすべて、JavaScriptで記述でき、ブラウザ上で動きます。

利用できる環境

前提として、対応ブラウザについて触れておきます。

対応ブラウザ:Can I use... Support tables for HTML5, CSS3, etc

  • Edge
  • Firefox
  • Chrome
  • Safari

つい先日ですが、Safariのバージョン11がリリースされ、WebRTCの対応とあわせて実装されています。そのため現時点では、IEを除くすべてのモダンブラウザで利用が可能となっています。

Safari 11はiOSでも利用可能なため、iPhoneやiPadからも利用が可能です。

また開発時に注意したいこととして、プロトコルはhttpsである必要があります。端末のカメラにアクセスできてしまうため、セキュリティには配慮すべきという理由からです。次の仕様書にその旨が書いてあります。

localhostfile://で開発するだけなら問題ないブラウザもありますが、サービスを公開する場合には、必ず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()に渡せる唯一の引数についてです。オブジェクトでvideoaudioそれぞれのプロパティを指定します。

navigator.mediaDevices.getUserMedia({ video: true, audio: true })

先の例ではこのように、videoaudioにそれぞれtrueを指定していただけでした。

dictionary MediaStreamConstraints {
    (boolean or MediaTrackConstraints) video = false;
    (boolean or MediaTrackConstraints) audio = false;
};

IDLによると、videoaudioそれぞれ、booleanMediaTrackConstraintsの指定が可能となっています。

どちらもtrueにすると、カメラとマイク両方の許可を求めるようになりますし、falseを設定した場合や未指定の場合は許可を求めません(利用しません)。

指定できる値とその意味

実は、この設定値はMediaTrackConstraintsという値で、よりさまざまな指定ができます。

たとえばこのような指定が可能です。これは、カメラの解像度を指定しています。

{ audio: true, video: { width: 1280, height: 720 } }

もうひとつの指定の例です。こうすることで、リアカメラではなくフロントカメラを利用したい旨を示すことができます。

{ audio: true, video: { facingMode: 'user' } }

そのほかにもaspectRatioframeRateなど、仕様上は、さまざまな値の指定が可能となっています。

ただ、指定できる値は「設定値」というよりも、その名のとおり「制約」と捉えるとわかりやすいです。というのも、必ずその指定になるというわけではなく、最終的にはブラウザが判断した結果になるからです。

これら詳細な指定の方法については、仕様書に詳しく記載されているので、そちらを確認してみてください。

デモのコード解説

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要素に表示するだけでしたが、次回の記事ではさらに応用したデモを紹介する予定です。

杉浦 有右嗣
PixelGrid Inc.
シニアエンジニア

SIerとしてシステム開発の上流工程を経験した後、大手インターネット企業でモバイルブラウザ向けソーシャルゲーム開発を数多く経験した。2015年にピクセルグリッドへ入社し、フロントエンド・エンジニアとして数々のWebアプリ制作を手掛ける。2018年に大手通信会社に転職し、低遅延配信の技術やプロトコルを使ったプラットフォームの開発と運用に携わっていたが、2020年ピクセルグリッドに再び入社。プライベートでのOSS公開やコントリビュート経験を活かしながら、実務ではクライアントにとって、ちょうどいいエンジニアリングを日々探求している。

この記事についてのご意見・ご感想 この記事をXにポストする

全記事アクセス+月4回配信、月額880円(税込)

CodeGridを購読する

初めてお申し込みの方には、30日間無料でお使いいただけます