HTML5 で音声と動画をキャプチャ

音声/動画のキャプチャは、長い間ウェブ開発の「聖杯」でした。長年にわたり、ブラウザ プラグイン(FlashSilverlight)に頼らざるを得ませんでした。さあ、始めましょう。

HTML5 が解決策です。一見するとわかりにくいかもしれませんが、HTML5 の普及に伴い、デバイスのハードウェアへのアクセスが急増しています。位置情報(GPS)、Orientation API(加速度計)、WebGL(GPU)、Web Audio API(オーディオ ハードウェア)などがその例です。これらの機能は非常に強力で、システムの基盤となるハードウェア機能の上に構築された高レベルの JavaScript API を公開します。

このチュートリアルでは、ウェブアプリがユーザーのカメラとマイクにアクセスできるようにする新しい API である GetUserMedia について説明します。

getUserMedia() API の誕生には興味深い経緯があります。

この数年間で、「Media Capture API」の複数のバリエーションが進化しました。多くの人が、ウェブでネイティブ デバイスにアクセスする必要があることを認識していましたが、その結果、誰もが新しい仕様を作成しました。状況が混乱したため、W3C は最終的にワーキング グループを結成することにしました。唯一の目的は混乱を解消しましょう。デバイス API ポリシー(DAP)ワーキング グループは、多数の提案を統合して標準化することを任されています。

2011 年に発生した事象を簡単にまとめると、

ラウンド 1: HTML メディア キャプチャ

HTML Media Capture は、DAP がウェブでのメディア キャプチャの標準化に初めて取り組んだものです。これは、<input type="file"> をオーバーロードし、accept パラメータに新しい値を追加することで機能します。

ユーザーがウェブカメラで自分のスナップショットを撮れるようにするには、capture=camera を使用します。

<input type="file" accept="image/*;capture=camera">

動画や音声の録画も同様です。

<input type="file" accept="video/*;capture=camcorder">
<input type="file" accept="audio/*;capture=microphone">

便利ですよね。特に、ファイル入力を再利用できることが気に入っています。意味的には、これは非常に理にかなっています。この「API」の欠点は、リアルタイム エフェクト(ライブ ウェブカメラ データを <canvas> にレンダリングして WebGL フィルタを適用するなど)を実行できないことです。HTML Media Capture では、メディア ファイルを録画するか、スナップショットを撮影するのみです。

サポート:

  • Android 3.0 ブラウザ - 最初の実装の 1 つ。実際の動作を確認するには、こちらの動画をご覧ください。
  • Chrome for Android(0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari と Chrome(一部サポート)

ラウンド 2: デバイス要素

HTML メディア キャプチャは制限が多すぎると多くの人が考えたため、あらゆる種類の(将来の)デバイスをサポートする新しい仕様が登場しました。当然のことながら、この設計では新しい要素(<device> 要素)が必要となり、これが getUserMedia() の前身となりました。

Opera は、<device> 要素に基づく動画キャプチャの初期実装を作成した最初のブラウザの 1 つです。その後まもなく(正確には同じ日)、WhatWG は <device> タグを廃止し、別の新興の JavaScript API である navigator.getUserMedia() に置き換えることを決定しました。1 週間後、Opera は更新された getUserMedia() 仕様のサポートを含む新しいビルドをリリースしました。その年の後半、Microsoft も新しい仕様をサポートする Lab for IE9 をリリースしてこの動きに加わりました。

<device> は次のように表示されます。

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

サポート:

残念ながら、リリースされたブラウザには <device> が含まれていませんでした。<device> には 2 つの大きな利点がありました。1 つはセマンティックであること、もう 1 つは音声/動画デバイス以外のデバイスもサポートできるように簡単に拡張できることです。

深呼吸しましょう。状況は急速に変化しています。

ラウンド 3: WebRTC

<device> 要素は最終的に絶滅しました。

WebRTC(ウェブ リアルタイム通信)の取り組みが拡大したことで、適切なキャプチャ API を見つけるペースが加速しました。この仕様は W3C WebRTC ワーキング グループによって管理されています。Google、Opera、Mozilla、その他のいくつかのブラウザで実装されています。

getUserMedia() は、その一連の API へのゲートウェイであるため、WebRTC に関連しています。ユーザーのローカル カメラ/マイク ストリームにアクセスする手段を提供します。

サポート:

getUserMedia() は、Chrome 21、Opera 18、Firefox 17 以降でサポートされています。

スタートガイド

navigator.mediaDevices.getUserMedia() を使用すると、プラグインなしでウェブカメラとマイクの入力を利用できるようになります。カメラへのアクセスは、インストールではなく、電話でリクエストできるようになりました。ブラウザに直接組み込まれています。ワクワクしてきましたか?

特徴検出

機能の検出は、navigator.mediaDevices.getUserMedia が存在するかどうかを簡単に確認するものです。

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

入力デバイスへのアクセス権の取得

ウェブカメラまたはマイクを利用するには、権限をリクエストする必要があります。navigator.mediaDevices.getUserMedia() の最初のパラメータは、アクセスする各メディア タイプの詳細と要件を指定するオブジェクトです。たとえば、ウェブカメラにアクセスする場合、最初のパラメータは {video: true} にする必要があります。マイクとカメラの両方を使用するには、{video: true, audio: true} を渡します。

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

注意が必要です。何が起きているのでしょうか。メディア キャプチャは、新しい HTML5 API が連携する完璧な例です。他の HTML5 の仲間である <audio><video> と連携して機能します。src 属性を設定したり、<video> 要素に <source> 要素を含めたりしていないことに注意してください。動画にメディア ファイルの URL を渡す代わりに、srcObject をウェブカメラを表す LocalMediaStream オブジェクトに設定しています。

また、<video>autoplay を指定しています。指定しないと、最初のフレームでフリーズします。controls を追加しても、想定どおりに動作します。

メディアの制約(解像度、高さ、幅)を設定する

getUserMedia() の最初のパラメータを使用して、返されるメディア ストリームに関するその他の要件(または制約)を指定することもできます。たとえば、動画への基本的なアクセス({video: true} など)を希望するだけでなく、ストリーミングが HD であることを追加で指定することもできます。

const hdConstraints = {
  video: { width: { exact:  1280} , height: { exact: 720 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

その他の構成については、constraints API をご覧ください。

メディアソースを選択する

MediaDevices インターフェースの enumerateDevices() メソッドは、マイク、カメラ、ヘッドセットなど、使用可能なメディア入力デバイスと出力デバイスのリストをリクエストします。返された Promise は、デバイスを記述する MediaDeviceInfo オブジェクトの配列で解決されます。

この例では、最後に検出されたマイクとカメラがメディア ストリーム ソースとして選択されています。

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

ユーザーがメディアソースを選択できるようにする方法については、Sam Dutton の素晴らしいデモをご覧ください。

セキュリティ

ブラウザは navigator.mediaDevices.getUserMedia() を呼び出すと権限ダイアログを表示します。これにより、ユーザーはカメラ/マイクへのアクセスを許可または拒否できます。たとえば、Chrome の権限ダイアログは次のとおりです。

Chrome の権限ダイアログ
Chrome の権限ダイアログ

フォールバックを提供

navigator.mediaDevices.getUserMedia() をサポートしていないユーザーの場合、API がサポートされていない場合や、なんらかの理由で呼び出しが失敗した場合は、既存の動画ファイルにフォールバックできます。

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}