【簡単】JavaScriptで動画を録画する機能を実装する方法
本記事では、ブラウザからJavaScriptでPCなどのデバイスカメラにアクセスして、動画の録画をする方法について解説します。
この記事を書いている僕は、エンジニア歴4年のフロントエンドエンジニアです。
この記事を読むことによって、ブラウザからPCやスマホのカメラやマイクを使って簡単に動画撮影する機能を実装することができるようになります。
JavaScriptで動画を録画する機能を実装する方法
GitHub
GitHubにコードを上げています。
▼GitHubコード
https://github.com/it-web-life/javascript_record_video_how_to
▼サンプルのdemoページ
https://it-web-life.github.io/javascript_record_video_how_to/index.html
※マイクとカメラを許可する必要があります
今回のコード
今回のコードは以下になります。メインはJavaScriptですが、ベースとなるHTMLも載せています。
▼HTML index.html
<!DOCTYPE html>
<html lang="ja">
<head></head>
<body>
<div class="container">
<div class="screen">
<h2>カメラの映像</h2>
<video
class="js-video-record"
data-video-player=".js-video-player"
data-record-start=".js-record-start"
data-record-stop=".js-record-stop"
data-play-start=".js-play-start"
data-download=".js-download"
></video>
<div>
<button class="js-record-start">録画開始</button>
<button class="js-record-stop">録画停止</button>
<button class="js-play-start">動画の再生</button>
<button class="js-download">動画のダウンロード</button>
</div>
</div>
<div class="screen">
<h2>録画した動画の再生</h2>
<video class="js-video-player"></video>
</div>
</div>
</body>
</html>
▼JavaScript videoRecord.js
/**
* 動画の録画機能を付与する
*/
class VideoRecord {
/**
* @constractor
* @param {Object} params
* @param {HTMLElement} params.$target カメラ映像をマウントする要素
* @param {HTMLElement} params.$videoPlayer 録画した動画をマウントする要素
* @param {HTMLElement} params.$recordStart 録画開始ボタン
* @param {HTMLElement} params.$recordStop 録画停止ボタン
* @param {HTMLElement} params.$playStart 再生ボタン
* @param {HTMLElement} params.$download ダウンロードボタン
*/
constructor({ $target, $videoPlayer, $recordStart, $recordStop, $playStart, $download }) {
// 各要素
this.$target = $target
this.$videoPlayer = $videoPlayer
this.$recordStart = $recordStart
this.$recordStop = $recordStop
this.$playStart = $playStart
this.$download = $download
this.initialize = this.initialize.bind(this)
this.startRecording = this.startRecording.bind(this)
this.startRecording = this.startRecording.bind(this)
this.stopRecording = this.stopRecording.bind(this)
this.startPlaying = this.startPlaying.bind(this)
this.download = this.download.bind(this)
// 設定の初期化処理
this.initialize()
// イベント設定
this.$recordStart.addEventListener('click', this.startRecording)
this.$recordStop.addEventListener('click', this.stopRecording)
this.$playStart.addEventListener('click', this.startPlaying)
this.$download.addEventListener('click', this.download)
}
/**
* 録画関連の初期化処理
*/
async initialize() {
this.mediaStream = null
this.mediaRecorder = null
// Blob
this.recordedChunks = []
this.superBuffer = null
this.$videoPlayer.src = null
this.$videoPlayer.srcObject = null
// ボタンの表示初期化
this.$recordStart.disabled = false
this.$recordStop.disabled = true
this.$playStart.disabled = true
this.$download.disabled = true
// カメラ・音声の取得
try {
const mediaDevicesConstraints = {
audio: true,
video: { width: 1280, height: 720 }
}
// デバイスの動画・音声トラックを取得
this.mediaStream = await navigator.mediaDevices.getUserMedia(mediaDevicesConstraints)
// MediaStreamを設定して表示する
this.$target.srcObject = this.mediaStream
this.$target.play()
} catch (err) {
throw new Error(err)
}
}
/**
* 録画を開始する
*/
startRecording() {
// 録画機能の生成
this.mediaRecorder = new MediaRecorder(this.mediaStream, { mimeType: 'video/webm; codecs=vp8' });
// availableイベントでメディア記録を保持
this.mediaRecorder.ondataavailable = event => this.recordedChunks.push(event.data)
// 録画開始
this.mediaRecorder.start()
console.log('this.superBuffer', this.superBuffer)
if (this.superBuffer) {
// メモリ解放
URL.revokeObjectURL(this.superBuffer)
}
// ボタンの表示更新 (動画停止を許可)
this.$recordStop.disabled = false
console.log('MediaRecorder start')
}
/**
* 録画を停止する
*/
stopRecording() {
// 録画停止
this.mediaRecorder.stop()
// ボタンの表示更新 (動画再生・ダウンロードを許可)
this.$playStart.disabled = false
this.$download.disabled = false
console.log('MediaRecorder stop')
}
/**
* 動画を再生する
*/
startPlaying() {
// webm形式でBlobで取得
this.superBuffer = new Blob(this.recordedChunks, { type: "video/webm" });
// BlobをURLに変換して設定
this.$videoPlayer.src = URL.createObjectURL(this.superBuffer)
this.$videoPlayer.controls = true;
// 動画の再生
this.$videoPlayer.play()
console.log('Video playing')
}
/**
* ダウンロードする
*/
download() {
const blob = new Blob(this.recordedChunks, { type: "video/webm" });
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
document.body.appendChild(a)
a.style = 'display: none'
a.href = url
a.download = 'video.webm'
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
console.log('Video download')
}
}
/**
* 動画の録画処理を生成する
*/
export const createVideoRecord = () => {
// .js-video-recordの要素をすべて取得
const targets = [...document.getElementsByClassName('js-video-record')]
// 各要素の録画機能設定をする
for (const target of targets) {
/** トリガーとなるセレクター名を取得する */
// 動画の再生プレイヤー
const videoPlayer = target.getAttribute('data-video-player')
if (!videoPlayer) {
console.error('data-video-player is required.')
continue;
}
// 録画の開始ボタン
const recordStart = target.getAttribute('data-record-start')
if (!recordStart) {
console.error('data-record-start is required.')
continue;
}
// 録画の停止ボタン
const recordStop = target.getAttribute('data-record-stop')
if (!recordStop) {
console.error('data-record-stop is required.')
continue;
}
// 動画の再生ボタン
const playStart = target.getAttribute('data-play-start')
if (!playStart) {
console.error('data-play-start is required.')
continue;
}
// 動画をダウンロードするボタン
const download = target.getAttribute('data-download')
if (!download) {
console.error('data-download is required.')
continue;
}
/** トリガーとなる各セレクターを取得する */
// 動画の再生プレイヤー
const $videoPlayer = document.querySelector(videoPlayer);
if (!$videoPlayer) {
console.error('videoPlayer Selector does not exist.')
continue;
}
// 録画の開始ボタン
const $recordStart = document.querySelector(recordStart);
if (!$recordStart) {
console.error('recordStart Selector does not exist.')
continue;
}
// 録画の停止ボタン
const $recordStop = document.querySelector(recordStop);
if (!$recordStop) {
console.error('recordStop Selector does not exist.')
continue;
}
// 動画の再生ボタン
const $playStart = document.querySelector(playStart);
if (!$playStart) {
console.error('playStart Selector does not exist.')
continue;
}
// 動画をダウンロードするボタン
const $download = document.querySelector(download);
if (!$download) {
console.error('download Selector does not exist.')
continue;
}
// インスタンスの作成
new VideoRecord({
$target: target,
$videoPlayer,
$recordStart,
$recordStop,
$playStart,
$download
})
}
}
JavaScriptで動画を録画するコードの詳細な解説
JavaScriptの処理を順番に解説していきます。
構成
「.js-video-record
」というclassをvideoタグにつけておき、data属性に動画再生や録画開始・停止ボタンなどを指定するような構成にしています。
createVideoRecord()
という関数を実行することによって、すべての動作が始まるようになっています。
createVideoRecord()
を実行すると、「.js-video-record
」のデータを読み込み、VideoRecord
クラスのインスタンスを生成して、動画の録画機能が付与されるようになっています。
export const createVideoRecord = () => {
// .js-video-recordの要素をすべて取得
const targets = [...document.getElementsByClassName('js-video-record')]
// 各要素の録画機能設定をする
for (const target of targets) {
// 【中略】
// インスタンスの作成
new VideoRecord({
$target: target,
$videoPlayer,
$recordStart,
$recordStop,
$playStart,
$download
})
}
VideoRecord Class
Class定義をしており、「new VideoRecord()」で必要な要素を引数として渡すことによって、動画の録画・再生機能が付与されるようになっています。
class VideoRecord {
constructor({ $target, $videoPlayer, $recordStart, $recordStop, $playStart, $download }) {
// 【中略】
}
async initialize() {
// 【中略】
}
startRecording() {
// 【中略】
}
// 【中略】
}
constructorでイベント設定
// イベント設定
this.$recordStart.addEventListener('click', this.startRecording)
this.$recordStop.addEventListener('click', this.stopRecording)
this.$playStart.addEventListener('click', this.startPlaying)
this.$download.addEventListener('click', this.download)
これらによって、各ボタンがクリックされた時に、それぞれの処理を呼ぶようにしています。
constructorでthisをbind
this.initialize = this.initialize.bind(this)
this.startRecording = this.startRecording.bind(this)
this.startRecording = this.startRecording.bind(this)
this.stopRecording = this.stopRecording.bind(this)
this.startPlaying = this.startPlaying.bind(this)
this.download = this.download.bind(this)
this
をそれぞれbind
しています。
VideoRecord Classのconstractor内の初期化処理
async initialize() {
this.mediaStream = null
this.mediaRecorder = null
// Blob
this.recordedChunks = []
this.superBuffer = null
this.$videoPlayer.src = null
this.$videoPlayer.srcObject = null
// ボタンの表示初期化
this.$recordStart.disabled = false
this.$recordStop.disabled = true
this.$playStart.disabled = true
this.$download.disabled = true
// カメラ・音声の取得
try {
const mediaDevicesConstraints = {
audio: true,
video: { width: 1280, height: 720 }
}
// デバイスの動画・音声トラックを取得
this.mediaStream = await navigator.mediaDevices.getUserMedia(mediaDevicesConstraints)
// MediaStreamを設定して表示する
this.$target.srcObject = this.mediaStream
this.$target.play()
} catch (err) {
throw new Error(err)
}
}
いろいろな変数の初期化をおこなっていますが、注目すべきは、「await navigator.mediaDevices.getUserMedia()
」の部分です。
this.mediaStream = await navigator.mediaDevices.getUserMedia(mediaDevicesConstraints)
これによって、ブラウザからデバイス(PCやスマホ)のカメラ・マイクにアクセスしてデバイスからの映像などをMediaStream
として取得しています。
また取得したMediaStream
をvideoタグ
のDOMにマウントしています。こうすることで、カメラから取得した映像を表示しています。
this.$target.srcObject = this.mediaStream
this.$target.play()
録画開始ボタンがクリックされた時の処理
startRecording() {
// 録画機能の生成
this.mediaRecorder = new MediaRecorder(this.mediaStream, { mimeType: 'video/webm; codecs=vp8' });
// availableイベントでメディア記録を保持
this.mediaRecorder.ondataavailable = event => this.recordedChunks.push(event.data)
// 録画開始
this.mediaRecorder.start()
console.log('this.superBuffer', this.superBuffer)
if (this.superBuffer) {
// メモリ解放
URL.revokeObjectURL(this.superBuffer)
}
// ボタンの表示更新 (動画停止を許可)
this.$recordStop.disabled = false
console.log('MediaRecorder start')
}
「MediaRecorder
」インスタンスを生成して、this.mediaRecorder
に格納しています。
MediaRecorder
の仕様を見るとわかりますが、dataavailableイベント
が発生したら、データをthis.recordedChunks
配列に格納しています。
「this.mediaRecorder.start()」
で録画を開始しています。
「URL.revokeObjectURL(this.superBuffer)
」は、後の動画再生処理「startPlaying()
」で生成した「URL.createObjectURL(this.superBuffer)
」が残っていたら「URL.revokeObjectURL()
」でメモリを開放する処理です。(2回目の録画の時など)
「this.$recordStop.disabled = false
」で録画停止ボタンを押せるようにしています。
録画停止ボタンがクリックされた時の処理
stopRecording() {
// 録画停止
this.mediaRecorder.stop()
// ボタンの表示更新 (動画再生・ダウンロードを許可)
this.$playStart.disabled = false
this.$download.disabled = false
console.log('MediaRecorder stop')
}
「this.mediaRecorder.stop()
」で録画を停止しています。また録画停止ボタンを押したことで録画動画が取得できますので、動画の再生やダウンロードボタンを押せるようにしています。
動画の再生ボタンがクリックされた時の処理
startPlaying() {
// webm形式でBlobで取得
this.superBuffer = new Blob(this.recordedChunks, { type: "video/webm" });
// BlobをURLに変換して設定
this.$videoPlayer.src = URL.createObjectURL(this.superBuffer)
this.$videoPlayer.controls = true;
// 動画の再生
this.$videoPlayer.play()
console.log('Video playing')
}
「new Blob(this.recordedChunks, { type: "video/webm" });
」によって、録画したデータをwebm
でBlob
形式で取得しています。
URL.createObjectURL
により、ブラウザのメモリに保存されたBlob
にアクセス可能なURL
を生成しています。ブラウザを閉じるまで有効になるのと同時に、「URL.createObjectURL()
」で生成したデータは常駐するため、「URL.revokeObjectURL()
」でメモリ解放する必要があります。
「this.$videoPlayer.controls = true;
」で「再生、音量、シーク、ポーズ」の各機能を制御するコントロールを表示しています。
「this.$videoPlayer.play()
」によって動画の再生が開始されます。
動画のダウンロード機能
download() {
const blob = new Blob(this.recordedChunks, { type: "video/webm" });
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
document.body.appendChild(a)
a.style = 'display: none'
a.href = url
a.download = 'video.webm'
a.click()
URL.revokeObjectURL(url)
document.body.removeChild(a)
console.log('Video download')
}
こちらはGoogleで公開されているサンプルを参考にして作成しています。
先ほどと同じくBlob
で生成したデータを「URL.createObjectURL
」でアクセスできるようにして、そのURLリンクをJavaScriptでクリックしてダウンロード、そして「URL.revokeObjectURL
」でメモリを開放しているという内容です。
ダウンロードボタンがクリックされたらこの処理が実行されてwebm形式
で録画した動画が「video.webm
」という名前でダウンロードされます。
注意点など
今回のサンプルコードをローカルで動作させる時は、「yarn
」でパッケージをインストールしてから、「yarn dev
」を実行すると、「localhost:8080
」でアクセスできるようになります。
localhostでスマホからは見れないかも
ちなみに、スマホからアクセスする時はサイトに上げるなどの工夫が必要です。ローカルIPアドレスからカメラにアクセスはできないと思います。localhost
か httpsのオリジンのサイト
からしかnavigator.mediaDevices.getUserMedia()
はアクセスできないようです。
まとめ
今回はブラウザからカメラやマイクにアクセスして、簡単に録画し、その動画を再生・ダウンロードできる機能について解説しました。
GitHubのコード
https://github.com/it-web-life/javascript_record_video_how_to
GitHub Pages サンプルページ
https://it-web-life.github.io/javascript_record_video_how_to/index.html
ご参考になれば幸いです。
※当サイトでは一部のリンクについてアフィリエイトプログラムを利用して商品を紹介しています