はじめに
HTML5 では、MediaDevices API など、デバイスハードウェアにアクセスできる API を導入しています。この API により、オーディオやビデオなどのメディア入力デバイスへのアクセスができます。
この API によって、開発者はオーディオやビデオデバイスにアクセスし、ブラウザでライブビデオフィードをストリーミングして表示できます。このチュートリアルでは、ユーザーのデバイスからビデオフィードにアクセスし、getUserMedia
メソッドを使用してブラウザに表示します。
getUserMedia
API は、メディア入力デバイスを利用して MediaStream を生成します。MediaStream は、オーディオかビデオであるかに関わらず、要求されたメディアタイプを含みます。API から返されたストリームを利用して、ブラウザ上にビデオフィードを表示させることができるので、ブラウザ上でのリアルタイム通信に便利です。
MediaStream Recording API と一緒に使用する場合、ブラウザ上でキャプチャしたメディアデータを記録して保存することができます。この API は、新しく導入された他の API と同様にセキュアな発行元でのみ動作しますが、ローカルホスト
やファイル URL でも動作します。
前提条件
- JavaScript の基本知識。JavaScript が初めての方は、How To Code in JavaScript(JavaScriptを使ってコーディングする方法)シリーズをチェックしてみてください。
このチュートリアルでは、最初に概念を説明し、CodePenを使った例のデモをします。最後のステップでは、ブラウザ用に動作するビデオフィードを作成します。
ステップ1 — デバイスがサポートされているか確認する
まず、ユーザーのブラウザが mediaDevices
API をサポートしているか確認する方法を説明します。この API はnavigator インターフェイス内に存在し、ユーザーエージェントの現在の状態と ID を含んでいます。チェックは、CodePen に貼り付けることができる、次のコードを使用して行います。
if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {
console.log("Let's get this party started")
}
まず、mediaDevices
API がナビゲーター
内に存在するかどうかをチェックし、次に、getUserMedia
API が mediaDevices
内で使用可能かどうかを確認します。true
を返す場合は、開始できます。
ステップ2 — ユーザーの許可を要求する
ブラウザによる getUserMedia
のサポートを確認した後、ユーザーエージェントでメディア入力デバイスを使用するための許可を要求する必要があります。通常、ユーザーが許可した後、Promise
が返され、メディアストリームへの解決をします。この Promise
は、ユーザーによって許可が拒否された場合には返されず、これらのデバイスへのアクセスがブロックされます。
次の行を Codepen に貼り付けして、許可を要求します。
navigator.mediaDevices.getUserMedia({video: true})
getUserMedia
メソッドの引数として指定されたオブジェクトは、constraints(制約)
と呼ばれます。これにより、アクセス許可を要求しているメディア入力デバイスのうち、どのデバイスにアクセスするかを決定します。例えば、オブジェクトに audio:true
が含まれている場合、ユーザーはオーディオ入力デバイスへのアクセスを許可するように求められます。
ステップ3 — メディアの Constraints (制約)を理解する
このセクションでは、contraints(制約)
の一般的な概念について説明します。constraints
オブジェクトは、要求するメディアのタイプと各メディアタイプの要件を指定するMediaStreamConstraints
オブジェクトです。constraints
オブジェクトを使用して、(前面
、背面
)を使用するストリームの解像度など、要求されたストリームの要件を指定できます。
リクエストを行うときは、オーディオ
またはビデオ
のいずれかを指定する必要があります。要求されたメディアタイプがユーザーのブラウザで見つからない場合、NotFoundError
が返されます。
1280 x 720
の解像度のビデオストリームをリクエストする場合は、constraints
オブジェクトを次のように更新できます。
{
video: {
width: 1280,
height: 720,
}
}
このアップデートでは、ブラウザは、ストリームに指定された品質設定を一致させようとします。このビデオデバイスで、この解像度をサポートしていない場合、ブラウザは他の利用可能な解像度を返します。
ブラウザがサポートする解像度以上の解像度を返すようにするには、min
プロパティを使用する必要があります。constraints
オブジェクトを更新して、min
プロパティを含める方法は、次のとおりです。
{
video: {
width: {
min: 1280,
},
height: {
min: 720,
}
}
}
これにより、返されるストリーム解像度は、少なくとも1280 x720
になります。この最小要件を満たせない場合、promise はOverconstrainedError
で拒否されます。
場合によっては、データの保存に配慮して、ストリームが設定された解像度を超えないようにする必要があります。これは、ユーザーが限定プランを利用している場合に便利です。この機能を有効にするには、constraints オブジェクトを更新してmax
フィールドを含めます。
{
video: {
width: {
min: 1280,
max: 1920,
},
height: {
min: 720,
max: 1080
}
}
}
これらの設定により、ブラウザは、返されるストリームが 1280 x 720
を下回ったり、1920 x 1080
を超えたりしないようにします。
使用できる他の条件には、exact
と ideal
があります。ideal
の設定では、通常、min
や max
プロパティと共に使用され、指定された ideal 値に最も近い最良の設定を見つけます。
この constraints を更新して、ideal
キーワードを使用できます。
{
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
}
}
}
ブラウザに、(モバイルの場合)デバイスの前面または背面カメラを使用するように指示するには、facingMode
プロパティを、video
オブジェクトで指定します。
{
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
},
facingMode: 'user'
}
}
この設定では、すべてのデバイスで、常に前面カメラを使用するようになります。モバイルデバイスでバックカメラを利用するには、facingMode
プロパティを environment
に変更します。
{
video: {
...
facingMode: {
exact: 'environment'
}
}
}
ステップ4 – enumerateDevices
メソッドを使用する
enumerateDevices
メソッドが呼び出されると、ユーザーの PC で利用可能な入力メディアデバイスをすべて返します。
この方法では、入力メディアデバイスのユーザオプションを指定して、オーディオまたはビデオコンテンツのストリーミングに使用することができます。このメソッドは、各デバイスに関する情報を含む MediaDeviceInfo 配列に対して解決された Promise
を返します。
最後のステップでは、ブラウザ用に動作するビデオフィードを作成します。
async function getDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
}
各デバイスの応答例は次のようになります。
{
deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",
groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",
kind: "audiooutput",
label: "",
}
注意:利用可能なストリームが利用可能でない限り、またはユーザーがデバイスアクセス許可を付与していない限り、ラベルは返されません。
ステップ5 – ブラウザにビデオストリームを表示する
メディアデバイスへのアクセスを要求して取得するプロセスを経て、必要な解像度などの constraints (制約)を設定し、ビデオ録画に必要なカメラを選択をします。
これらのステップをすべて行った後、設定された構成に基づいてストリームが配信されているかどうかを確認することができます。これを確認するために、<video>
要素を使用してブラウザにビデオストリームを表示します。
前述したように、getUserMedia
メソッドは、ストリームへの解決ができる Promise
を返します。返されたストリームは、createObjectURL
メソッドを使用してオブジェクト URL に変換することができます。この URL は、ビデオソースとして設定されます。
enumerateDevices
メソッドを使用して、利用可能なビデオデバイスのリストからユーザーが選択できる短いデモを作成します。
これは navigator.mediaDevices
メソッドです。マイクやカメラなど、利用可能なメディアデバイスが一覧表示されます。使用可能なメディアデバイスの詳細を示すオブジェクトの配列に、解決可能な Promise
を返します。
index.html
ファイルを作成し、次のコードでコンテンツを更新します。
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<link rel="stylesheet" href="style.css">
<title>Document</title>
</head>
<body>
<div class="display-cover">
<video autoplay></video>
<canvas class="d-none"></canvas>
<div class="video-options">
<select name="" id="" class="custom-select">
<option value="">Select camera</option>
</select>
</div>
<img class="screenshot-image d-none" alt="">
<div class="controls">
<button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
<button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
<button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
</div>
</div>
<script src="https://unpkg.com/feather-icons"></script>
<script src="script.js"></script>
</body>
</html>
上記のスニペットでは、必要な要素とビデオのいくつかのコントロールを設定しました。現在のビデオフィードのスクリーンショットを撮るためのボタンも含まれています。
それでは、これらのコンポーネントを少しスタイルアップしましょう。
style.css
ファイルを作成し、次のスタイルをそのファイルにコピーします。Bootstrap(ブートストラップ) が、コンポーネントを動作させるために作成する必要のある CSS の量を減らすために含まれていました。
style.css
.screenshot-image {
width: 150px;
height: 90px;
border-radius: 4px;
border: 2px solid whitesmoke;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
position: absolute;
bottom: 5px;
left: 10px;
background: white;
}
.display-cover {
display: flex;
justify-content: center;
align-items: center;
width: 70%;
margin: 5% auto;
position: relative;
}
video {
width: 100%;
background: rgba(0, 0, 0, 0.2);
}
.video-options {
position: absolute;
left: 20px;
top: 30px;
}
.controls {
position: absolute;
right: 20px;
top: 20px;
display: flex;
}
.controls > button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls > button:hover svg {
color: white !important;
}
@media (min-width: 300px) and (max-width: 400px) {
.controls {
flex-direction: column;
}
.controls button {
margin: 5px 0 !important;
}
}
.controls > button > svg {
height: 20px;
width: 18px;
text-align: center;
margin: 0 auto;
padding: 0;
}
.controls button:nth-child(1) {
border: 2px solid #D2002E;
}
.controls button:nth-child(1) svg {
color: #D2002E;
}
.controls button:nth-child(2) {
border: 2px solid #008496;
}
.controls button:nth-child(2) svg {
color: #008496;
}
.controls button:nth-child(3) {
border: 2px solid #00B541;
}
.controls button:nth-child(3) svg {
color: #00B541;
}
.controls > button {
width: 45px;
height: 45px;
text-align: center;
border-radius: 100%;
margin: 0 6px;
background: transparent;
}
.controls > button:hover svg {
color: white;
}
次のステップでは、デモに機能を追加します。enumerateDevices
メソッドを使用して、使用可能なビデオデバイスを取得し、select 要素内のオプションとして設定します。script.js
というファイルを作成し、次のスニペットで更新します。
script.js
feather.replace();
const controls = document.querySelector('.controls');
const cameraOptions = document.querySelector('.video-options>select');
const video = document.querySelector('video');
const canvas = document.querySelector('canvas');
const screenshotImage = document.querySelector('img');
const buttons = [...controls.querySelectorAll('button')];
let streamStarted = false;
const [play, pause, screenshot] = buttons;
const constraints = {
video: {
width: {
min: 1280,
ideal: 1920,
max: 2560,
},
height: {
min: 720,
ideal: 1080,
max: 1440
},
}
};
const getCameraSelection = async () => {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(device => device.kind === 'videoinput');
const options = videoDevices.map(videoDevice => {
return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
});
cameraOptions.innerHTML = options.join('');
};
play.onclick = () => {
if (streamStarted) {
video.play();
play.classList.add('d-none');
pause.classList.remove('d-none');
return;
}
if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
const updatedConstraints = {
...constraints,
deviceId: {
exact: cameraOptions.value
}
};
startStream(updatedConstraints);
}
};
const startStream = async (constraints) => {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleStream(stream);
};
const handleStream = (stream) => {
video.srcObject = stream;
play.classList.add('d-none');
pause.classList.remove('d-none');
screenshot.classList.remove('d-none');
streamStarted = true;
};
getCameraSelection();
上記のスニペットでは、いくつかのことが起こっています。それらを分解しましょう。
feather.replace()
:このメソッド呼び出しは、Web 開発用のアイコンセットである feather をインスタンス化します。constraints
変数は、ストリームの初期構成を保持します。これにより、ユーザーが選択したメディアデバイスを含むように拡張されます。getCameraSelection
:この関数はenumerateDevices
メソッドを呼び出します。次に、解決されたPromise
から配列をフィルタリングし、ビデオ入力デバイスを選択します。フィルタされた結果から、<select>
要素の<option>
を作成します。getUserMedia
メソッドの呼び出しは、再生
ボタンのonclick
リスナー内で行われます。ここでは、ストリームを開始する前に、このメソッドがユーザーのブラウザでサポートされているかどうかを確認します。- 次に、
constraints
引数を取るstartStream
関数を呼び出します。提供されたconstraints
を使用してgetUserMedia
メソッドを呼び出します。handleStream
は、解決されたPromise
からのストリームを使用して呼び出されます。このメソッドは、返されたストリームをビデオ要素のsrcObject
に設定します。
次に、ページのボタンコントロールに、クリックリスナーを追加して、一時停止
停止
、スクリーンショット
を撮ります。また、リスナーを <select>
要素に追加して、選択したビデオデバイスのストリーム constraints を更新します。
次のコードで script.js
ファイルを更新します。
script.js
...
cameraOptions.onchange = () => {
const updatedConstraints = {
...constraints,
deviceId: {
exact: cameraOptions.value
}
};
startStream(updatedConstraints);
};
const pauseStream = () => {
video.pause();
play.classList.remove('d-none');
pause.classList.add('d-none');
};
const doScreenshot = () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
screenshotImage.src = canvas.toDataURL('image/webp');
screenshotImage.classList.remove('d-none');
};
pause.onclick = pauseStream;
screenshot.onclick = doScreenshot;
ここで、ブラウザで index.html
ファイルを開いて、再生ボタンをクリックするとストリームが開始します。
以下が完全なデモです。
まとめ
このチュートリアルでは、getUserMedia
API を紹介しました。これは、Web 上のメディアをキャプチャするプロセスを容易にする、HTML5への興味深い追加機能です。
API は、オーディオおよびビデオ入力デバイスへのアクセスを構成するために使用できるパラメーター(constraints
)を取ります。また、アプリケーションに必要なビデオ解像度を指定するためにも使用できます。
デモをさらに拡張して、MediaStream Recording API を使用して、撮影したスクリーンショットを保存したり、ビデオやオーディオデータを記録・保存したりするオプションをユーザーに提供できます。