初心者向けgetUserMedia解説:Webブラウザでカメラとマイクを使う方法

2025-05-27

より具体的に説明すると、以下のようになります。

    • ウェブアプリケーションがカメラやマイクを利用したい場合、この関数を呼び出します。
    • ブラウザはユーザーに対して、どのデバイスへのアクセスを許可するかどうかを確認するプロンプトを表示します(初回アクセス時など)。
  1. 制約の指定 (Specifying Constraints)

    • getUserMedia() を呼び出す際には、どのようなメディアストリームが必要かを指定するための「制約 (constraints)」オブジェクトを引数として渡すことができます。
    • この制約によって、例えば以下のような指定が可能です。
      • 映像 (video)
        • 特定の解像度(例: 640x480, 1280x720)
        • 特定のフレームレート(例: 30fps, 60fps)
        • 特定のカメラ(前面カメラ、背面カメラなど)
      • 音声 (audio)
        • エコーキャンセレーションの有無
        • 特定のオーディオ入力デバイス
  2. Promiseベースの非同期処理 (Promise-based Asynchronous Operation)

    • getUserMedia()Promise オブジェクトを返します。これは、メディアデバイスへのアクセス許可の結果が非同期的に得られることを意味します。
    • 成功 (Resolution)
      ユーザーがアクセスを許可した場合、Promiseはメディアストリームを表す MediaStream オブジェクトで解決 (resolve) されます。この MediaStream オブジェクトは、<video> 要素や <audio> 要素の srcObject プロパティに設定することで、ブラウザ上で映像や音声を再生したり、WebRTCなどの技術で他のユーザーに送信したりするために利用できます。
    • 失敗 (Rejection)
      ユーザーがアクセスを拒否した場合、または指定された制約を満たすメディアデバイスが見つからなかった場合、Promiseはエラーで拒否 (reject) されます。考えられるエラーとしては、NotAllowedError(ユーザーが許可しなかった)、NotFoundError(要求されたデバイスが見つからなかった)、NotReadableError(ハードウェアの問題などでデバイスが使用できなかった)などがあります。エラー処理を行うためには、Promiseの .catch() メソッドを使用します。

基本的なコード例

navigator.mediaDevices.getUserMedia({ video: true, audio: false })
  .then(function(stream) {
    // メディアストリームを取得成功
    const videoElement = document.getElementById('myVideo');
    videoElement.srcObject = stream;
  })
  .catch(function(error) {
    // メディアストリームの取得失敗
    console.error('Error accessing media devices:', error);
  });

この例では、映像のみ(音声はなし)のメディアストリームを要求しています。成功した場合は、取得したストリームを idmyVideo<video> 要素に表示します。失敗した場合は、エラーメッセージをコンソールに出力します。



NotAllowedError (許可されなかったエラー)

  • トラブルシューティング
    • ユーザーへの再確認
      ユーザーにアクセス許可を再度促すメッセージを表示し、許可が必要な理由を説明します。
    • ブラウザの設定確認
      ユーザーにブラウザの設定でサイトへのカメラやマイクのアクセスが許可されているか確認してもらうよう案内します。
    • iframeの権限確認
      iframe内で利用する場合は、親ドキュメントのPermissions Policyを確認し、必要な権限が付与されているか確認します。
  • 原因
    ユーザーがカメラやマイクへのアクセスを明示的に拒否した場合に発生します。また、iframe内で getUserMedia() を呼び出す際に、親ドキュメントで適切な権限が付与されていない場合にも発生することがあります。

NotFoundError (見つからなかったエラー)

  • トラブルシューティング
    • 制約の緩和
      より緩やかな制約で再度 getUserMedia() を試みます。例えば、特定の解像度を要求している場合は、解像度の指定を削除したり、より一般的な解像度に変更したりします。
    • デバイスの確認
      ユーザーにカメラやマイクが正しく接続され、システムに認識されているか確認してもらいます。
    • 利用可能なデバイスの列挙
      navigator.mediaDevices.enumerateDevices() を使用して利用可能なメディアデバイスを列挙し、ユーザーに選択肢を提示することを検討します。
  • 原因
    要求された種類のメディアデバイス(例えば、指定した解像度のカメラや特定のマイク)がシステムに存在しない場合に発生します。

NotReadableError (読み取れないエラー)

  • トラブルシューティング
    • 他のアプリケーションの確認
      カメラやマイクを使用している可能性のある他のアプリケーション(例: ビデオ会議ツール、録音ソフト)を終了してもらいます。
    • デバイスの再起動
      カメラやマイクを一度取り外し、再度接続してみるようユーザーに依頼します。
    • ドライバの更新
      ユーザーにカメラやマイクのドライバが最新の状態であることを確認してもらいます。
    • ブラウザの再起動
      ブラウザを一度閉じてから再度開き、試してみます。
    • OSの確認
      OSレベルでカメラやマイクが正常に動作しているか確認します。
  • 原因
    ハードウェアの問題、他のアプリケーションによるデバイスの占有、またはブラウザやOSのドライバの問題などにより、メディアデバイスからのデータストリームを読み取ることができない場合に発生します。

OverconstrainedError (制約が満たされないエラー)

  • トラブルシューティング
    • 制約の見直し
      より実現可能な制約の組み合わせに変更します。どの制約が問題を引き起こしているかを特定するために、少しずつ制約を減らして試してみるのが有効です。
    • 利用可能な機能の確認
      MediaTrackCapabilities APIを使用して、接続されているデバイスがどのような機能をサポートしているかを確認し、その情報に基づいて制約を設定することを検討します。
  • 原因
    指定された制約の組み合わせを満たすメディアデバイスが存在しない場合に発生します。例えば、特定の解像度とフレームレートを同時に要求したが、それを両方サポートするカメラがない場合などです。

TypeError (型エラー)

  • トラブルシューティング
    • 制約オブジェクトの確認
      getUserMedia() に渡している制約オブジェクトのプロパティ名や値の型が正しいか、ドキュメントなどを参照して確認します。
  • 原因
    getUserMedia() に渡す引数の形式が間違っている場合に発生します。通常は、制約オブジェクトの形式が正しくないことが原因です。
  • Permissions APIの利用
    navigator.permissions.query() APIを使用して、事前にカメラやマイクの権限の状態を確認し、必要に応じてユーザーに許可を促すことができます。
  • ブラウザの互換性
    getUserMedia() は比較的新しいAPIであるため、古いブラウザではサポートされていない可能性があります。対応状況を確認し、必要に応じてポリフィル(polyfill)の利用を検討します。
  • シンプルな構成でテスト
    まずは最も基本的な制約(例: { video: true, audio: true })で getUserMedia() が成功するかどうかを試し、問題がなければ徐々に制約を追加していくことで、問題の原因を特定しやすくなります。
  • コンソール出力の活用
    エラーオブジェクトには、より詳細な情報が含まれている場合があります。コンソールに出力して確認します。
  • エラーメッセージの確認
    発生したエラーの具体的なメッセージをよく確認し、それがどのエラータイプに該当するかを特定します。


基本的な例1: カメラとマイクのストリームを取得して<video>と<audio>要素に表示する

<!DOCTYPE html>
<html>
<head>
<title>getUserMedia Basic Example</title>
</head>
<body>
  <video id="cameraView" width="320" height="240" autoplay muted></video>
  <audio id="microphoneInput" autoplay muted></audio>

  <script>
    async function startMedia() {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
        const videoElement = document.getElementById('cameraView');
        const audioElement = document.getElementById('microphoneInput');

        videoElement.srcObject = stream;
        audioElement.srcObject = stream; // 同じストリームにビデオとオーディオの両方のトラックが含まれています
      } catch (error) {
        console.error('メディアデバイスへのアクセスエラー:', error);
      }
    }

    startMedia();
  </script>
</body>
</html>

この例では、getUserMedia()async/await を使って呼び出し、カメラとマイクの両方のストリームを要求しています。成功した場合、取得した MediaStream オブジェクトを <video> 要素と <audio> 要素の srcObject プロパティに設定することで、ブラウザ上にカメラの映像とマイクの音声が表示(または再生)されます。<video> 要素の muted 属性は、初期状態で音声が出力されないようにするために設定されています。

基本的な例2: カメラの映像ストリームのみを取得して<video>要素に表示する

<!DOCTYPE html>
<html>
<head>
<title>getUserMedia Video Only</title>
</head>
<body>
  <video id="cameraView" width="640" height="480" autoplay></video>

  <script>
    navigator.mediaDevices.getUserMedia({ video: true })
      .then(function(stream) {
        const videoElement = document.getElementById('cameraView');
        videoElement.srcObject = stream;
      })
      .catch(function(error) {
        console.error('カメラへのアクセスエラー:', error);
      });
  </script>
</body>
</html>

この例では、カメラの映像ストリームのみを要求しています。Promiseベースで処理しており、成功した場合は取得したストリームを <video> 要素に設定して映像を表示します。エラーが発生した場合は、エラーメッセージをコンソールに出力します。

制約の例1: 特定の解像度を要求する

navigator.mediaDevices.getUserMedia({ video: { width: { min: 640, ideal: 1280 }, height: { min: 480, ideal: 720 } } })
  .then(function(stream) {
    // ストリームを処理
  })
  .catch(function(error) {
    console.error('解像度に関するエラー:', error);
  });

この例では、映像の幅が最低640ピクセル、理想的には1280ピクセル、高さが最低480ピクセル、理想的には720ピクセルであるストリームを要求しています。ブラウザは、これらの制約を満たす最適なカメラを選択しようとします。

制約の例2: 特定のカメラ(前面または背面)を要求する

async function getFacingModeStream(facingMode) {
  const constraints = {
    video: { facingMode: facingMode }
  };
  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    const videoElement = document.getElementById('cameraView');
    videoElement.srcObject = stream;
  } catch (error) {
    console.error(`"${facingMode}" カメラへのアクセスエラー:`, error);
  }
}

// 背面カメラを試す
getFacingModeStream('environment');

// 前面カメラを試す(背面カメラが利用できない場合に)
// getFacingModeStream('user');

この例では、facingMode 制約を使用して、特定のカメラ('environment' は背面カメラ、'user' は前面カメラを意味することが多いです)を要求しています。非同期関数 getFacingModeStream を定義し、異なる facingMode の値で呼び出すことで、特定のカメラへのアクセスを試みます。

応用的な例: 利用可能なメディアデバイスを列挙し、ユーザーに選択させる

async function listMediaDevices() {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const videoSelect = document.getElementById('videoSource');
    const audioSelect = document.getElementById('audioSource');

    videoSelect.innerHTML = '<option value="">-- 映像デバイスを選択 --</option>';
    audioSelect.innerHTML = '<option value="">-- 音声デバイスを選択 --</option>';

    devices.forEach(device => {
      const option = document.createElement('option');
      option.value = device.deviceId;
      option.textContent = device.label || `Device ${videoSelect.length + audioSelect.length}`;
      if (device.kind === 'videoinput') {
        videoSelect.appendChild(option);
      } else if (device.kind === 'audioinput') {
        audioSelect.appendChild(option);
      }
    });
  } catch (error) {
    console.error('メディアデバイスの列挙エラー:', error);
  }
}

async function startSelectedMedia() {
  const videoSource = document.getElementById('videoSource').value;
  const audioSource = document.getElementById('audioSource').value;

  const constraints = {
    video: videoSource ? { deviceId: videoSource } : false,
    audio: audioSource ? { deviceId: audioSource } : false
  };

  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    const videoElement = document.getElementById('cameraView');
    const audioElement = document.getElementById('microphoneInput');

    videoElement.srcObject = stream;
    audioElement.srcObject = stream;
  } catch (error) {
    console.error('選択されたメディアデバイスへのアクセスエラー:', error);
  }
}

// HTML部分
// <select id="videoSource"></select>
// <select id="audioSource"></select>
// <button onclick="startSelectedMedia()">メディアを開始</button>

listMediaDevices();

この例では、まず navigator.mediaDevices.enumerateDevices() を使用して利用可能なビデオ入力デバイスとオーディオ入力デバイスを列挙し、それぞれ <select> 要素に表示します。ユーザーがデバイスを選択し、「メディアを開始」ボタンをクリックすると、選択された deviceId を制約に含めて getUserMedia() を呼び出し、選択したデバイスのストリームを取得して表示します。



<input type="file" accept="image/*, video/*, audio/*"> 要素の利用

  • コード例
  • 欠点
    • リアルタイムの映像や音声処理には適していません。
    • ユーザーがファイルを選択する手間が必要です。
  • 利点
    • ユーザーはどのファイルを提供するかを完全に制御できます。
    • リアルタイムのアクセス許可を毎回求める必要がありません。
  • getUserMedia() との違い
    リアルタイムのメディアストリームを取得するのではなく、ユーザーが事前に録画・撮影したファイルや保存された音声ファイルを扱います。
<!DOCTYPE html>
<html>
<head>
<title>File Input Example</title>
</head>
<body>
  <input type="file" id="mediaFile" accept="image/*, video/*, audio/*">
  <video id="videoPreview" width="320" height="240" controls></video>
  <audio id="audioPreview" controls></audio>
  <img id="imagePreview" style="max-width: 320px; max-height: 240px;">

  <script>
    const mediaFile = document.getElementById('mediaFile');
    const videoPreview = document.getElementById('videoPreview');
    const audioPreview = document.getElementById('audioPreview');
    const imagePreview = document.getElementById('imagePreview');

    mediaFile.addEventListener('change', function() {
      const file = this.files[0];
      if (file) {
        const fileURL = URL.createObjectURL(file);
        const fileType = file.type.split('/')[0];

        if (fileType === 'image') {
          imagePreview.src = fileURL;
          videoPreview.style.display = 'none';
          audioPreview.style.display = 'none';
          imagePreview.style.display = 'block';
        } else if (fileType === 'video') {
          videoPreview.src = fileURL;
          imagePreview.style.display = 'none';
          audioPreview.style.display = 'none';
          videoPreview.style.display = 'block';
        } else if (fileType === 'audio') {
          audioPreview.src = fileURL;
          imagePreview.style.display = 'none';
          videoPreview.style.display = 'none';
          audioPreview.style.display = 'block';
        }
      }
    });
  </script>
</body>
</html>

既存のメディアストリームの利用

  • コード例 (WebRTCの受信ストリームを<video>要素に表示する例)
  • 欠点
    • ストリームの取得元となる別の仕組みが必要です。
  • 利点
    • ユーザーに改めて許可を求める必要がありません。
    • 複数のメディアストリームを統合したり、処理したりする際に便利です。
  • getUserMedia() との違い
    新たにメディアデバイスへのアクセスを要求するのではなく、既存のストリームを再利用します。
// (WebRTCのピア接続が確立され、リモートストリームが `remoteStream` 変数に格納されていると仮定)

const remoteVideoElement = document.getElementById('remoteVideo');
remoteVideoElement.srcObject = remoteStream;
  • コード例 (カメラ映像を録画してダウンロードする例)
  • 欠点
    • 記録とデータ処理のステップが追加されます。
  • 利点
    • リアルタイム処理と記録データの利用の両方が可能です。
    • 記録されたデータをダウンロードしたり、サーバーにアップロードしたりできます。
  • getUserMedia() との違い
    リアルタイムストリームを直接利用するだけでなく、記録されたデータを後から利用したり、サーバーに送信したりできます。
async function startRecording() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
    const mediaRecorder = new MediaRecorder(stream);
    const chunks = [];

    mediaRecorder.ondataavailable = function(event) {
      if (event.data.size > 0) {
        chunks.push(event.data);
      }
    };

    mediaRecorder.onstop = function() {
      const blob = new Blob(chunks, { type: 'video/webm' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'recorded-video.webm';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      stream.getTracks().forEach(track => track.stop()); // ストリームの停止
    };

    mediaRecorder.start();
    setTimeout(() => {
      mediaRecorder.stop();
    }, 5000); // 5秒後に録画停止
  } catch (error) {
    console.error('録画エラー:', error);
  }
}

// HTML: <button onclick="startRecording()">録画開始</button>