Frontend Development 10 min read

Building a Custom Screen Recording Application with Electron, Vite, and FFmpeg

This tutorial walks through creating a desktop screen‑recording tool using Electron with a React‑Vite front‑end, configuring resolution, frame rate, and output formats (webm, mp4, gif), implementing IPC communication, system‑tray controls, draggable windows, and FFmpeg‑based recording logic.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Building a Custom Screen Recording Application with Electron, Vite, and FFmpeg

Screen‑recording software is familiar to most developers, and this guide shows how to build your own using Electron as the core platform, which is well‑suited for desktop capture scenarios.

The application provides three main features: resolution adjustment, frame‑rate adjustment, and support for saving recordings as webm , mp4 , or gif files.

Environment setup : the author prefers a React+Vite stack, and uses the electron‑vite starter to scaffold the project. The basic framework is created with the command:

npm create @quick-start/electron

A simple configuration UI is built with antd , containing dropdowns for resolution, frame rate, and file extension. The UI code resides in renderer/src/App.jsx and persists settings to localStorage using useEffect hooks.

Configuration options are defined as arrays:

const DEFINITION_LIST = [
  { label: '超清', value: '3840x2160' },
  { label: '高清', value: '1280x720' },
  { label: '标清', value: '720x480' }
];

const FRAME_RATE_LIST = [
  { label: '高', value: '60' },
  { label: '中', value: '30' },
  { label: '低', value: '15' }
];

const EXT_LIST = [
  { label: 'webm', value: 'webm' },
  { label: 'mp4', value: 'mp4' },
  { label: 'gif', value: 'gif' }
];

IPC communication between the renderer and main processes is demonstrated with two examples. The renderer sends configuration data:

useEffect(() => {
  const options = { definition, frameRate, ext };
  window.electron.ipcRenderer.send(RECORD_EVNET.SET_CONFIG, options);
}, [definition, frameRate, ext]);

The main process receives it:

import { ipcMain } from 'electron';
let config = {};
ipcMain.on(RECORD_EVNET.SET_CONFIG, (e, data) => {
  config = data;
});

Conversely, the main process can push events to the renderer:

mainWindow.webContents.send('event-name', { name: 'jayliang' });
window.electron.ipcRenderer.on('event-name', callback);

System tray integration adds a tray icon with a context menu offering "Start", "Stop", and "Quit" actions:

import { Menu, Tray, nativeImage } from 'electron';
let trayIcon = nativeImage.createFromPath(icon).resize({ width: 16, height: 16 });
const tray = new Tray(trayIcon);
const contextMenu = Menu.buildFromTemplate([
  { label: '开始', type: 'normal', click: startRecording },
  { label: '停止', type: 'normal', click: stopRecording },
  { type: 'separator' },
  { label: '退出', type: 'normal', role: 'quit' }
]);
tray.setToolTip('你的应用名称');
tray.setContextMenu(contextMenu);

A draggable window feature is added by injecting JavaScript into the renderer after the page loads, listening for mouse events and calling window.moveTo to reposition the window.

mainWindow.webContents.on('did-finish-load', () => {
  mainWindow.webContents.executeJavaScript(`
    document.addEventListener('mousedown', e => {
      if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
        window.isDragging = true;
        offset = { x: e.screenX - window.screenX, y: e.screenY - window.screenY };
      }
    });
    document.addEventListener('mousemove', e => {
      if (window.isDragging) {
        const { screenX, screenY } = e;
        window.moveTo(screenX - offset.x, screenY - offset.y);
      }
    });
    document.addEventListener('mouseup', () => { window.isDragging = false; });
  `);
});

Recording implementation uses ffmpeg-static to obtain a platform‑specific FFmpeg binary. The export variable FFMPEG_BINARIES_URL can be set to a mirror for faster downloads. Build scripts for Windows, macOS, and Linux are provided.

"build:win": "rm -rf ./node_modules && npm run build && electron-builder --win --config",
"build:mac": "rm -rf ./node_modules && npm run build && electron-builder --mac --config",
"build:linux": "rm -rf ./node_modules && npm run build && electron-builder --linux --config"

The startRecording function builds an FFmpeg command based on the selected ext , frameRate , and definition , then spawns a child process to run the command. stopRecording sends a SIGINT to terminate the process.

import ffmpegPath from 'ffmpeg-static';
const startRecording = async () => {
  const { ext, frameRate, definition } = config;
  const fileName = `${app.getPath('downloads')}/${+new Date()}.${ext}`;
  let ffmpegCommand = '';
  if (ext === 'webm') {
    ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -c:v libvpx-vp9 -c:a libopus ${fileName}`;
  } else if (ext === 'mp4') {
    ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -vsync vfr -c:v libx264 -preset ultrafast -qp 0 -c:a aac ${fileName}`;
  } else if (ext === 'gif') {
    ffmpegCommand = `${ffmpegPath} -f avfoundation -framerate ${frameRate} -video_size ${definition} -i "1" -vf "fps=15" -c:v gif ${fileName}`;
  }
  ffmpegProcess = spawn(ffmpegCommand, { shell: true });
  ffmpegProcess.stderr.on('data', data => console.error(`FFmpeg Error: ${data}`));
  ffmpegProcess.on('exit', (code, signal) => console.log(`Recording process exited with code ${code} and signal ${signal}`));
};

const stopRecording = () => {
  if (ffmpegProcess) {
    ffmpegProcess.kill('SIGINT');
  }
};

The guide notes that the current implementation relies on the macOS avfoundation input, which is not compatible with Windows, and suggests adding Windows support in the future.

In summary, the article provides a complete step‑by‑step walkthrough for building a functional screen‑recording desktop application with Electron, covering UI creation, inter‑process communication, system‑tray integration, draggable windows, and FFmpeg‑based media capture.

ReactElectronIPCFFmpegviteScreen RecordingTray
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.