Closed yumetodo closed 1 month ago
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Screen Recorder</title>
</head>
<body>
<h1>画面録画アプリ</h1>
<button id="start">録画開始</button>
<script>
const { ipcRenderer } = require('electron');
document.getElementById('start').addEventListener('click', () => {
ipcRenderer.send('start-recording');
});
ipcRenderer.on('recording-finished', (event, outputPath) => {
alert(`録画が完了しました: ${outputPath}`);
});
</script>
</body>
</html>
const { app, BrowserWindow, ipcMain, desktopCapturer } = require('electron');
const { createWriteStream } = require('fs');
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(createWindow);
ipcMain.on('start-recording', async (event) => {
const sources = await desktopCapturer.getSources({ types: ['screen'] });
const screenSource = sources[0];
const outputPath = path.join(app.getPath('videos'), 'recording.mp4');
const outputStream = createWriteStream(outputPath);
ffmpeg(screenSource.thumbnail.toPNG())
.format('mp4')
.pipe(outputStream);
outputStream.on('finish', () => {
event.reply('recording-finished', outputPath);
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
https://stackoverflow.com/questions/36753288/saving-desktopcapturer-to-video-file-in-electron
https://github.com/electron/electron/issues/27139
どうも navigator.mediaDevices.getDisplayMedia
にsource.id
を渡す方法が見つからないと思ったら何事かあるらしい。
ParseOldStyleNames
関数ではパースしている
} else if (constraint.name_ == kMediaStreamSource) {
// TODO(hta): This has only a few legal values. Should be
// represented as an enum, and cause type errors.
// https://crbug.com/576582
result.media_stream_source.SetExact(constraint.value_);
} else if (constraint.name_ == kDisableLocalEcho &&
RuntimeEnabledFeatures::
DesktopCaptureDisableLocalEchoControlEnabled()) {
result.disable_local_echo.SetExact(ToBoolean(constraint.value_));
} else if (constraint.name_ == kMediaStreamSourceId ||
constraint.name_ == kMediaStreamSourceInfoId) {
result.device_id.SetExact(constraint.value_);
呼び出し元
static MediaConstraints CreateFromNamedConstraints(
ExecutionContext* context,
Vector<NameValueStringConstraint>& mandatory,
const Vector<NameValueStringConstraint>& optional) {
MediaTrackConstraintSetPlatform basic;
MediaTrackConstraintSetPlatform advanced;
MediaConstraints constraints;
ParseOldStyleNames(context, mandatory, basic);
// We ignore unknown names and syntax errors in optional constraints.
Vector<MediaTrackConstraintSetPlatform> advanced_vector;
for (const auto& optional_constraint : optional) {
MediaTrackConstraintSetPlatform advanced_element;
Vector<NameValueStringConstraint> element_as_list(1, optional_constraint);
ParseOldStyleNames(context, element_as_list, advanced_element);
if (!advanced_element.IsUnconstrained())
advanced_vector.push_back(advanced_element);
}
constraints.Initialize(basic, advanced_vector);
return constraints;
}
これより前がよくわkらん。
mainから呼べない時点でキャプチャは却下。
結局録画はOBSなどの外部ツールに頼ることとし、実装仕掛けた録画開始/終了ボタンは #4 のキーボード監視の開始終了のみの機能となった。つまりユーザーは外部ツールの録画開始と本ツールの録画開始を十分に近い時間に押下するものとする。
https://www.electronjs.org/ja/docs/latest/api/desktop-capturer