/*
* @title 録画
* @description 画面をwebmで録画するやつ
* @include http://*
* @include https://*
* @license MIT License
* @javascript_url
*/
(function() {
'use strict';
if (!navigator.mediaDevices) {
alert(location.protocol === 'http:' ? 'httpsでないサイトでは使えません。' : '対応していないブラウザです');
return;
}
const tag = (name, props = {}, children = []) => {
const e = Object.assign(document.createElement(name), props);
if (typeof props.style === "object") Object.assign(e.style, props.style);
(children.forEach ? children : [children]).forEach(c => e.appendChild(c));
return e;
};
const modal = (() => {
let refs = {};
refs.container = tag('div', {
style: 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 2147483647'
}, [
refs.content = tag('div', {
style: `
position: absolute; width: 320px; height: 160px; left: 0; right: 0; top: 0; bottom: 0; margin: auto; padding: 1em;
background: white; border-radius: 5px;
display: flex; flex-direction: column; align-items: center; justify-content: space-evenly;`
}, [
tag('p', { style: 'font-size: 1.4em; font-weight: bold', textContent: '録画中...' }),
tag('p', { textContent: '下の「録画終了」ボタンをクリックするか、画面共有を停止すると、録画を終了します。' }),
refs.stopButton = tag('button', { textContent: '録画終了' })
])
]);
return {
show({onStop}) {
document.body.appendChild(refs.container);
refs.stopButton.onclick = onStop;
},
hide() {
refs.container.remove();
}
};
})();
function saveFile(filename, blob) {
const url = URL.createObjectURL(blob);
const link = tag('a', { style: 'display: none', href: url, download: filename });
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
link.remove();
}
function getDefaultFilename() {
const dateStr = new Date().toLocaleString('ja-JP', {
hour12: false,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
return dateStr.replace(/[\/:]/g, '').replace(/ /, '-');
}
navigator.mediaDevices.getDisplayMedia(/* w,h,audio */)
.then(stream => {
let chunks = [];
const recorder = new MediaRecorder(stream, {
mimeType: MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' : 'video/webm;codecs=vp8',
videoBitsPerSecond: 800 * 1000
});
recorder.ondataavailable = e => chunks.push(e.data);
const recordingPromise = new Promise((resolve, reject) => {
recorder.onstop = () => resolve(new Blob(chunks, { type: recorder.mimeType }));
recorder.onerror = e => reject(e.error);
})
.finally(() => {
// 録画を終了したら元ストリームも閉じる(共有を停止する)
stream.getTracks().forEach(track => track.stop());
modal.hide();
});
// 82canaryだとなくても共有終了で閉じてくれたけど、80stableでは必要っぽい?
stream.getTracks().forEach(x => x.addEventListener('ended', () => recorder.stop()));
modal.show({ onStop: () => recorder.stop() });
recorder.start();
return recordingPromise;
})
.then(blob => {
const name = prompt('録画が完了しました。ファイル名を入力してください。', getDefaultFilename());
if (!name) return;
saveFile(name + '.webm', blob);
})
.catch(e => {
const hint =
e.message.includes('user gesture') ? '一度ページのどこかをクリックしてから再実行してみてください。' :
e.name === 'NotAllowedError' ? 'キャプチャがブロックされているようです。\n・共有ダイアログで「拒否」を選択しませんでしたか?\n・ブラウザの設定でカメラの使用などをブロックしていませんか?' :
null;
const msg = e.message || e.name || e;
alert('録画に失敗しました。\n' + (hint ? hint + '\n\n' : '') + 'エラー詳細: ' + e);
});
})();