ユーザーの動画を撮影する

マットスケール

多くのブラウザで、ユーザーの映像と音声入力にアクセスできるようになりました。ただし、ブラウザによっては、完全に動的なインライン エクスペリエンスになる場合や、ユーザーのデバイス上の別のアプリに委任される場合があります。

シンプルかつ段階的に始める

最も簡単な方法は、事前に録音したファイルをユーザーに尋ねることです。そのためには、シンプルなファイル入力要素を作成し、動画ファイルのみを受け入れることを示す accept フィルタと、カメラから直接取得する必要があることを示す capture 属性を追加します。

<input type="file" accept="video/*" capture />

この方法はすべてのプラットフォームで機能します。パソコンの場合、ファイル システムからファイルをアップロードするように求められます(capture 属性は無視します)。iOS の Safari ではカメラアプリが開き、動画を録画してウェブページに送り返すことができます。Android では、ウェブページに送り返す前に、どのアプリで動画を撮影するかをユーザーが選択できます。

多くのモバイル デバイスには複数のカメラが搭載されています。必要に応じて、カメラをユーザーに向けて設定する場合は capture 属性を user に設定し、カメラを外部に向けて設定する場合は environment に設定します。

<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />

これは単なるヒントです。ブラウザがこのオプションをサポートしていない場合や、指定したカメラタイプが利用できない場合、ブラウザは別のカメラを選択することがあります。

ユーザーが録画を終了してウェブサイトに戻ったら、なんらかの方法でファイルデータを取得する必要があります。onchange イベントを入力要素にアタッチし、イベント オブジェクトの files プロパティを読み取ると、すばやくアクセスできます。

<input type="file" accept="video/*" capture="camera" id="recorder" />
<video id="player" controls></video>
<script>
  var recorder = document.getElementById('recorder');
  var player = document.getElementById('player');

  recorder.addEventListener('change', function (e) {
    var file = e.target.files[0];
    // Do something with the video file.
    player.src = URL.createObjectURL(file);
  });
</script>

ファイルにアクセスできるようになったら、そのファイルでさまざまな操作を行うことができます。たとえば、下記の設定が可能です。

  • <video> 要素に直接アタッチして、再生できるようにします。
  • ユーザーのデバイスにダウンロードする
  • XMLHttpRequest にアタッチしてサーバーにアップロードします。
  • フレームをキャンバスに描画してフィルタを適用する

入力要素方式を使用して動画データにアクセスする方法は広く使用されていますが、この方法は最も魅力的ではありません。そこで私たちは、カメラにアクセスして、ページ上で直接快適なエクスペリエンスを提供したいと考えています。

カメラにインタラクティブにアクセスする

最新のブラウザではカメラに直接接続できるため、ウェブページと完全に統合されたエクスペリエンスを構築でき、ユーザーはブラウザから離れることはありません。

カメラへのアクセス権を取得する

getUserMedia() という WebRTC 仕様の API を使用して、カメラに直接アクセスできます。getUserMedia() は、接続されているマイクとカメラへのアクセス権をユーザーに求めます。

成功すると、API はカメラまたはマイクのデータを含む Stream を返します。その後、<video> 要素にアタッチするか、WebRTC ストリームにアタッチするか、MediaRecorder API を使用して保存できます。

カメラからデータを取得するには、getUserMedia() API に渡される constraints オブジェクトに video: true を設定します。

<video id="player" controls></video>
<script>
  var player = document.getElementById('player');

  var handleSuccess = function (stream) {
    player.srcObject = stream;
  };

  navigator.mediaDevices
    .getUserMedia({audio: true, video: true})
    .then(handleSuccess);
</script>

特定のカメラを選択する場合は、最初に利用可能なカメラを列挙できます。

navigator.mediaDevices.enumerateDevices().then((devices) => {
  devices = devices.filter((d) => d.kind === 'videoinput');
});

その後、getUserMedia を呼び出すときに使用する deviceId を渡すことができます。

navigator.mediaDevices.getUserMedia({
  audio: true,
  video: {
    deviceId: devices[0].deviceId,
  },
});

単独ではあまり役に立ちません。動画データを取得して再生することしかできません。

カメラの元データにアクセスする

カメラの未加工の動画データにアクセスするには、各フレームを <canvas> に描画して、ピクセルを直接操作します。

2D キャンバスの場合、コンテキストの drawImage メソッドを使用して、<video> 要素の現在のフレームをキャンバスに描画できます。

context.drawImage(myVideoElement, 0, 0);

WebGL キャンバスでは、<video> 要素をテクスチャのソースとして使用できます。

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  myVideoElement,
);

いずれの場合も、再生中の動画の現在のフレームが使用されます。複数のフレームを処理するには、毎回動画をキャンバスに再描画する必要があります。

詳しくは、画像や動画へのリアルタイム エフェクトの適用に関する記事をご覧ください。

カメラのデータを保存する

カメラのデータを保存する場合、MediaRecorder API を使用するのが最も簡単な方法です。

MediaRecorder API は、getUserMedia によって作成されたストリームを取得し、ストリームのデータを任意の宛先に段階的に保存します。

<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
  let shouldStop = false;
  let stopped = false;
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');

  stopButton.addEventListener('click', function() {
    shouldStop = true;
  })

  var handleSuccess = function(stream) {
    const options = {mimeType: 'video/webm'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) {
        recordedChunks.push(e.data);
      }

      if(shouldStop === true && stopped === false) {
        mediaRecorder.stop();
        stopped = true;
      }
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.webm';
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
      .then(handleSuccess);
</script>

この例では、データを配列に直接保存し、それを後で Blob に変換できます。この配列は、ウェブサーバーへの保存や、ユーザーのデバイスのストレージに直接保存するために使用できます。

カメラを責任を持って使用する許可を求める

ユーザーがサイトにカメラへのアクセスを許可していない場合、getUserMedia を呼び出した時点で、ブラウザはサイトに対してカメラへのアクセスを許可するよう求められます。

ユーザーは、自分のマシン上の高機能なデバイスへのアクセスを要求されることを嫌います。また、多くの場合、そのリクエストをブロックするか、プロンプトが作成されたコンテキストを理解していない場合はそれを無視します。カメラへのアクセスを要求するのは、初めて必要になったときのみにすることをおすすめします。ユーザーがアクセス権を付与すると、再度アクセスを求められることはありませんが、ユーザーがアクセスを拒否した場合、再度アクセスしてユーザーに権限をリクエストすることはできません。

権限 API を使用して、すでにアクセス権があるかどうかを確認する

getUserMedia API では、すでにカメラにアクセスできるかどうかを知ることはできません。これにより問題が発生します。ユーザーにカメラへのアクセスを許可してもらうための適切な UI を提供するには、カメラへのアクセスを要求する必要があります。

一部のブラウザでは、Permission API を使用して問題を解決できます。navigator.permission API を使用すると、プロンプトを再度表示することなく、特定の API にアクセスできる状態をクエリできます。

ユーザーのカメラにアクセスできるかどうかを照会するには、{name: 'camera'} をクエリメソッドに渡すと、次のいずれかが返されます。

  • granted - ユーザーが以前にカメラへのアクセスを許可しています。
  • prompt - ユーザーがアクセス権を付与していません。getUserMedia を呼び出すとメッセージが表示されます。
  • denied - システムまたはユーザーがカメラへのアクセスを明示的にブロックしているため、カメラにアクセスできません。

これで、ユーザーが行う必要があるアクションに対応するためにユーザー インターフェースを変更する必要があるかどうかを簡単に確認できます。

navigator.permissions.query({name: 'camera'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});

フィードバック