この記事では、Webアプリで録画機能を開発したいという方に向けて、Javascriptを使った実装方法をご紹介します。
携帯やPCの付帯機能でも録画機能が備わっていますが、実はJavascriptでも画面録画をおこなうことができます。
Javascriptを使って録画機能を開発することで、画面の録画以外にもインカメラを使った録画や、画面とインカメラの同時録画も実現することが可能です。
この記事を読むことで、
が明確になりますので、ぜひ開発の際にお役立ていただければ幸いです。
PC画面の録画をすることができます。
画面の録画を活用することで、オンライン教育や教材の作成、アプリ開発のユーザーテストをした際のエラーの報告などを実現することができます。
PCのインカメラを使用して、撮影・録画をすることができます。
こちらを活用することで、ミーティングの動画や、個人の自己紹介の動画などを簡単に録画することができます。
(google meet 参考)
https://meet.google.com/?hs=197&authuser=0
また、インカメラでの撮影とスクリーンの撮影それぞれを同時におこなって、映像を結合させることで、ユーザー自身の顔とスクリーンの映像が一緒になった動画を録画することもできます。
AppStoreなどで画面の録画のできるアプリをダウンロードする場合は、PCの容量を気にする必要がありますが、ブラウザ上で画面の録画サービスを使用する場合はその必要がありません。
また、専用のアプリのダウンロードをしなくても済むことにより、気軽に録画機能を使用できるといったメリットもあります。
Javascriptで作成された録画サービスの場合、各々のユーザーがOSなどを気にせず、ブラウザ上で録画サービスを使用することができます。
そのため、幅広いユーザーにサービスを届けることができるといったメリットがあります。
Javascriptで録画機能を開発する場合、MediaRecorderというAPIを使用します。
MediaRecorderを使用することで、ユーザーの音声や画面の映像などを簡単に録音、録画することができます。
画面の映像のみの場合や、インカメラの映像のみの場合は、canvasの使用の必要はありませんが、それぞれの映像を結合させて録画させたい場合は、canvasを使用する必要があります。
canvasとは、図形の描画や画像のサイズ変更、円形などへのくり抜きをおこなうことのできるものです。
Javascriptでcanvasを使用することで、時間経過とともに画像を変更することができ、アニメーションを作成することも可能です。
今回紹介する方法のように、画面の映像とインカメラの映像を結合させたい場合は、インカメラの撮影している映像をスクリーンの撮影している映像の上にcanvasで描画するといった流れになります。
基本的な録画の方法としては、
といった流れです。
* MediaStreamとは、WebRTCやWebオーディオ/ビデオAPIで使用されるストリーミングメディアのフローを表すオブジェクトのこと
インカメラの映像を録画する場合を例に、録画方法を紹介していきます。
以下、インカメラを使用した録画を例に説明します。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport"content="width=device-width, initial-scale=1.0">
<title>録画機能デモ</title>
</head>
<body>
<!-- 録画中の映像を表示する要素 スタイルをブロック要素にしています。使用するときは cssで調整してください -->
<video id="recordVideo" autoplay muted width="800" height="600" style="display:block;"></video>
<!-- リプレイを表示する要素 -->
<video id="replayVideo" autoplay controls width="800" height="600" style="display:none;"></video>
<!-- 録画を動作するボタン -->
<button id="start">録画開始</button>
<button id="stop" disabled>録画停止</button>
<button id="save" disabled>動画保存</button>
<script src="app.js"></script>
</body>
</html>
const recordVideo = document.getElementById('recordVideo');
const replayVideo = document.getElementById('replayVideo');
const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop');
const saveButton = document.getElementById('save');
let mediaRecorder;
// 録画されたデータを格納する配列
const recordedBlobs = [];
// 録画を開始
async function startRecording() {
// streamを取得
const stream = await navigator.mediaDevices.getUserMedia(
{
video: true,
audio: true
}
).catch(err => console.error('カメラ取得エラー', err));
// video要素にstreamを入れ込む
recordVideo.srcObject = stream;
// mimeTypeを設定
const options = { mimeType: 'video/webm;codecs=vp9,opus' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
// 録画データが格納される
recordedBlobs.push(event.data);
}
};
mediaRecorder.start();
console.log('録画開始');
startButton.disabled = true;
stopButton.disabled = false;
}
// リプレイを再生
function replay() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
replayVideo.src = URL.createObjectURL(blob);
replayVideo.style.display = 'block';
replayVideo.controls = true;
recordVideo.style.display = 'none';
saveButton.disabled = false;
}
// 動画を保存
function saveRecording() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = '録画ファイル.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
// 録画を開始
function stopRecording() {
mediaRecorder.stop();
console.log('録画停止');
mediaRecorder.onstop = () => {
replay();
startButton.disabled = false;
stopButton.disabled = true;
recordVideo.srcObject.getTracks().forEach(track => track.stop());
};
}
startButton.addEventListener('click', () => {
startRecording();
startButton.disabled = true;
stopButton.disabled = false;
});
stopButton.addEventListener('click', stopRecording);
saveButton.addEventListener('click', saveRecording);
ストリームを取得するために、getUserMediaを実行します。
MediaDevices.getUserMedia()メソッドは、要求された種類のメディアを含むトラックを持つ MediaStreamを生成するメディア入力を使用する許可をユーザーに求めます。
以下URL参考
https://developer.mozilla.org/
録画中の動画にstreamを入れ込んだ後、MediaRecorderのインスタンスを生成します。
const options = { mimeType: 'video/webm;codecs=vp9,opus' };
これは、動画の形式を設定しています。
動画の形式をここで定義しておくことで、録画されるメディアのエンコード方式を指定することができます。
注意点としては、ブラウザによって対応しているmimeTypeが異なる点です。
2023年現時点では、Chrome・Edge・FirefoxではWebM、Safariではmp4 に対応しているため、それぞれのブラウザにあった形式を設定する必要があります。
その後、録画のデータはMediaRecorderのondataavailableイベントハンドラーを使用して、録画したデータをevent.dataとしてrecordedBlobの配列に格納しています。
以下のメソッドを実行することで録画を開始、停止をしています。
// 録画を開始
mediaRecorder.start();
// 録画を停止
mediaRecorder.stop();
最後に、以下の記述で、リプレイ用のvideo要素にrecorderBlobを入れ込んで録画後のビデオを表示しています。
function replay() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
replayVideo.src = URL.createObjectURL(blob);
replayVideo.style.display = 'block';
replayVideo.controls = true;
recordVideo.style.display = 'none';
saveButton.disabled = false;
}
今回はご紹介していませんが、もし取り直しなどの処理を行いたい場合は、recorderBlobを初期化して再度録画するといった流れとなります。
インカメラの処理をブラウザで確認すると、以下のようになります。
保存を押下後は、PCのダウンロードディレクトリに保存されます。
スクリーンの映像を取得する場合は、getDisplaymediaを使用します。
MediaDevices インターフェイスの getDisplayMedia() メソッドは、ディスプレイまたはその一部(ウィンドウなど)の内容を MediaStream としてキャプチャする許可を選択し、許可するようユーザーに促します。
以下URL参考
https://developer.mozilla.org/
app.js
async function startRecording() {
// スクリーンのメディアストリームを取得
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true }).catch(err => console.error('スクリーン取得エラー', err));
// 音声のメディアストリームを取得
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch(err => console.error('音声取得エラー', err));
// スクリーンのメディアストリームと音声のメディアストリームをマージする
const stream = new MediaStream([...screenStream.getTracks(), ...audioStream.getTracks()]);
// video要素にstreamを入れ込む
recordVideo.srcObject = stream;
// mimeTypeを設定
const options = { mimeType: 'video/webm;codecs=vp9,opus' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
// 録画データが格納される
recordedBlobs.push(event.data);
}
};
mediaRecorder.start();
console.log('録画開始');
startButton.disabled = true;
stopButton.disabled = false;
}
以上のような方法で、スクリーンの映像を取得して録画をすることが可能です。
インカメラのみの録画をする場合と、基本的な流れは同様です。
ただし、注意が必要な点として
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
のようにして、getDisplayMediaからaudioの情報も取得しようとすると、ユーザーの音声ではなく、PCの音声を取得してしまいます。
そのため以下のように、getDisplayMediaからはaudioを取得せず、getUserMediaからaudioを取得して、その後それぞれの音声ストリームを結合するといった処理が必要です。
// スクリーンのメディアストリームを取得
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true }).catch(err => console.error('スクリーン取得エラー', err));
// 音声のメディアストリームを取得
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch(err => console.error('音声取得エラー', err));
// スクリーンのメディアストリームと音声のメディアストリームを結合する
const stream = new MediaStream([...screenStream.getTracks(), ...audioStream.getTracks()]);
スクリーンの処理をブラウザで確認すると、以下のようになります。
スクリーンの映像とインカメラの映像を結合する際は、canvasを使って、スクリーンの映像を描画、その後インカメラの映像をスクリーンの映像の上に描画するといった処理が必要です。
以下は、あくまで簡易的な例です。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>録画機能デモ</title>
</head>
<body>
<!-- スクリーンのビデオ要素-->
<video id="screenVideo" width ="800" height="600" autoplay muted style="display:block;"></video>
<!-- カメラのビデオ要素-->
<video id="cameraVideo" width="160" height="120" autoplay playsinline style="display:block;"></video>
<!-- リプレイを表示する要素 -->
<video id="replayVideo" autoplay playsinline controls style="display:none;"></video>
<!-- 録画を動作するボタン -->
<button id="start">録画開始</button>
<button id="stop" disabled>録画停止</button>
<button id="save" disabled>動画保存</button>
<script src="app.js"></script>
</body>
</html>
スクリーン映像とインカメラ映像でビデオ要素を分けています。
app.js
const screenVideo = document.getElementById('screenVideo');
const cameraVideo = document.getElementById('cameraVideo');
const replayVideo = document.getElementById('replayVideo');
const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop');
const saveButton = document.getElementById('save');
let mediaRecorder;
const recordedBlobs = [];
async function startRecording() {
const screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
const cameraStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' }, audio: true });
// video要素にstreamを入れ込む
screenVideo.srcObject = screenStream;
cameraVideo.srcObject = cameraStream;
// キャンバス処理
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 640;
canvas.height = 480;
function drawVideoToCanvas() {
ctx.drawImage(screenVideo, 0, 0, canvas.width, canvas.height);
ctx.drawImage(cameraVideo, canvas.width - 160, canvas.height - 120, 160, 120);
requestAnimationFrame(drawVideoToCanvas);
}
drawVideoToCanvas();
// キャプチャしたcanvasを録画対象にする
const captureStream = canvas.captureStream(30);
// 音声用のストリームを作成
const audioStream = new MediaStream(cameraStream.getAudioTracks());
// キャプチャストリームと音声ストリームを結合する
const stream = new MediaStream([...captureStream.getVideoTracks(), ...audioStream.getAudioTracks()]);
//mimeTypeを設定
const options = { mimeType: 'video/webm;codecs=vp9,opus' };
mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
// 録画データが格納される
recordedBlobs.push(event.data);
}
};
mediaRecorder.start();
console.log('録画開始');
startButton.disabled = true;
stopButton.disabled = false;
}
function replay() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
replayVideo.src = URL.createObjectURL(blob);
replayVideo.style.display = 'block';
replayVideo.controls = true;
screenVideo.style.display = 'none';
cameraVideo.style.display = 'none';
saveButton.disabled = false;
}
function saveRecording() {
const blob = new Blob(recordedBlobs, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = '録画ファイル.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
function stopRecording() {
mediaRecorder.stop();
console.log('録画停止');
mediaRecorder.onstop = () => {
replay();
startButton.disabled = false;
stopButton.disabled = true;
recordVideo.srcObject.getTracks().forEach(track => track.stop());
cameraVideo.srcObject.getTracks().forEach(track => track.stop());
};
}
startButton.addEventListener('click', () => {
startRecording();
startButton.disabled = true;
stopButton.disabled = false;
});
stopButton.addEventListener('click', stopRecording);
saveButton.addEventListener('click', saveRecording);
canvasを使用した処理をブラウザで確認すると、以下のようになります。
ポイントとしては2点あり、
①MediaRecorderに渡す映像のストリームは、canvasで描画されたものを取得し、音声のストリームはインカメラで取得しているユーザーの音声を取得している点
// キャプチャしたcanvasを録画対象にする
const captureStream = canvas.captureStream(30);
// 音声用のストリームを作成
const audioStream = new MediaStream(cameraStream.getAudioTracks());
// キャプチャストリームと音声ストリームを結合する
const stream = new MediaStream([...captureStream.getVideoTracks(), ...audioStream.getAudioTracks()]);
//mimeTypeを設定
options = { mimeType: 'video/webm;codecs=vp9,opus' };
mediaRecorder = new MediaRecorder(stream, options);
②drawVideoToCanvasでそれぞれの映像を結合している点
drawVideoToCanvasメソッド内では、screenVideo要素をcanvas上に座標(左上隅(0,0))を指定して描画し、その後、cameraVideo要素をスクリーンの右下に映し出されるよう座標を指定して描画しています。
cameraVideo要素の高さと幅を基準に座標を決めています。
function drawVideoToCanvas() {
ctx.drawImage(screenVideo, 0, 0, canvas.width, canvas.height);
ctx.drawImage(cameraVideo, canvas.width - 160, canvas.height - 120, 160, 120);
requestAnimationFrame(drawVideoToCanvas);
}
以上の処理で録画の映像にはスクリーンとカメラの映像が結合したものを表示できました。
canvasを使う際、例えばカメラ要素を隅から離して表示したり(drawImage時の座標をずらす)、カメラの映像の角を少し丸くしたりといった調整もできるので、アレンジしてみてください。
また今回は、簡易的なコードを使ってご紹介したので、その他レイアウトなどもそれぞれのプロジェクトに合わせて改善してくださいね。
いかがでしたでしょうか?
Javascriptで録画をする方法について紹介させていただきました。
今回は録画という観点で機能を紹介しましたが、録音にもMediaRecorderの機能は応用できそうです。
目的に合わせて、機能を使い分けてみていただければと思います。
クランチタイマーでは、他にもフロントエンドの開発に役立つ記事を書いていますので、よろしければご覧ください。
SHARE:
お気軽にお問い合わせください。
TEL082-299-2286
代表の佐々⽊が⽉に1回お届けするメールマガジン。
国内外スタートアップの最新情報や最新技術のサマリー、クランチタイマーの開発事例紹介など、ITに関する役⽴つ情報を中⼼にお送りします!