Author: ivyhaswell

For its introduction, I would like to start with a practical example that happened recently:

Recently, the group began to document the project. We need to find a screen recording tool to make videos and GIfs. So we started looking for something that didn’t capture the status bar, or was too big, or wasn’t available on Windows, and so on. It took a while to finally find one that worked. I was working on the electron project, and I thought: How long would it take to build a simple, cross-platform recording tool for electron?

The answer is 20 minutes, dozens of lines of code.

If you are interested, try the following steps:

(1) Create a screen recording tool

  1. First, create a directory to install electron.
yarn add -D electron
Copy the code
  1. createmain.jsHere is the code for the main process;
const { app, BrowserWindow, globalShortcut } = require("electron");
const path = require("path");

app.on("ready".() = > {
  const browserWindow = new BrowserWindow({
    webPreferences: { nodeIntegration: true.enableRemoteModule: true}}); browserWindow.loadFile(path.resolve(__dirname,"index.html"));

  globalShortcut.register('CommandOrControl+Shift+R'.() = > browserWindow.webContents.send("StartRecording"))
  globalShortcut.register('CommandOrControl+Shift+S'.() = > browserWindow.webContents.send("StopRecording"))}); app.on('will-quit'.() = > globalShortcut.unregisterAll())
Copy the code
  1. createindex.htmlTo introduce the renderer script
<h1>Current Status:<span id="status">free</span></h1>
<ol>
  <li>MacOS shortcuts: Command+Shift+R to start recording, Command+Shift+S to stop recording;</li>
  <li>Windows: Ctrl+Shift+R to start recording, Ctrl+Shift+S to stop recording;</li>
</ol>
<script src="./renderer.js"></script>
Copy the code
  1. createrenderer.jsHere is the main business code
const { desktopCapturer, remote, shell, ipcRenderer } = require("electron");
const path = require("path");
const fs = require("fs");

let mediaRecorder = null;
let chunks = []

async function start() {
    if (mediaRecorder) return;

    const sources = await desktopCapturer.getSources({ types: ["screen"]});const stream = await navigator.mediaDevices.getUserMedia({
      audio: false.video: {
        mandatory: {
          chromeMediaSource: "screen".chromeMediaSourceId: sources[0].id,
        },
      },
    });

    mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });
    mediaRecorder.ondataavailable = (event) = > {
      event.data.size > 0 && chunks.push(event.data);
    };

    mediaRecorder.start();
	  updateStatusText('Recording... ')}function stop(){
  if(! mediaRecorder)return

  mediaRecorder.onstop = async() = > {const blob = new Blob(chunks, { type: "video/webm" });
    const buffer = Buffer.from(await blob.arrayBuffer());
    const filePath = path.resolve(remote.app.getPath("downloads"), `The ${Date.now()}.webm`);

    fs.writeFile(filePath, buffer, () = > {
      shell.openPath(filePath);
      mediaRecorder = null;
      chunks = []
    });
  };
  mediaRecorder.stop();
  updateStatusText('free')}function updateStatusText(text){
  const $statusElement = document.querySelector('#status')
  $statusElement.textContent = text
}

ipcRenderer.on("StartRecording", start);
ipcRenderer.on("StopRecording", stop);
Copy the code

The application can then be launched from the command line

./node_modules/.bin/electron  main.js
Copy the code

It’s also very simple to use:

  1. On the Mac, use shortcut keysCommand+Shift+RStart recording,Command+Shift+SStop recording; The Windows shortcut keys areControl+Shift+RControl+Shift+S;
  2. After stopping recording, the recorded video will be automatically opened (the video is saved in the download directory by default).

When the app starts, it looks like this:

The following GIF is a screen recording and conversion using this demo:

(2) Tool code analysis

Twenty minutes and dozens of lines of code may be an exaggeration, as there are many features, such as recording parameter configuration, format conversion, multi-platform packaging, etc., that are yet to be implemented. But it doesn’t stop you from seeing how easy it is to start developing Electron.

Before we dive into the code, let’s look at one of the basic concepts of Electron: the main process and the renderer process.

  • Main process passesBrowserWindowCreate Windows, each window corresponding to a render process;
  • The rendering process manages the corresponding Web page,BrowserWindowAfter destruction, the corresponding rendering process also terminates;
  • The crash of one renderer does not affect other renderers;

To understand this concept, let’s try it out:

Start by adding a main-process.js to the root directory

const { app, BrowserWindow, dialog } = require("electron");
const path = require("path");

app.on("ready".() = > {
  const win1 = new BrowserWindow({ x: 20.y: 20 });
  win1.loadURL("https://github.com");

  const win2 = new BrowserWindow({ x: 500.y: 20 });
  win2.loadURL("https://stackoverflow.com");

  const win3 = new BrowserWindow({
    x: 20.y: 500.webPreferences: { nodeIntegration: true}}); win3.loadFile(path.resolve(__dirname,"crash-renderer.html"));

  win3.webContents.on('render-process-gone'.async() = > {await dialog.showMessageBoxSync(win3, {message: 'Process has crashed.'.buttons: ['off']})
    win3.close()
  })
});
Copy the code

Add another crash-renderer.html

<script>
  setTimeout(() = > {
    process.crash()
  }, 2000);
</script>
Copy the code

Start the application with./node_modules/. Bin /electron main-process.js

Here main-process.js creates three Windows, the first opening github, the second opening StackOverflow, and the third opening local HTML files. There are three Windows for each render process.

In crash-renderer.html we executed process.crash(), so we could see that the process in the third window crashed after 2 seconds, while github and StackOverflow remained normal for the other two Windows.

Going back to the screen recording application, its basic functional structure is designed as follows:

To reflect the implementation, let’s start with main.js

First you register the methods after the application is ready and before you exit

app.on('ready'.() = >{... }); app.on('will-quit'.() = >{... });Copy the code

Two things are done in the Ready event, the first is to create the window:

const browserWindow = new BrowserWindow({
  webPreferences: { nodeIntegration: true.enableRemoteModule: true}}); browserWindow.loadFile(path.resolve(__dirname,"index.html"));
Copy the code

NodeIntegration and enableRemoteModule are set to true to use the Node and remote modules in the rendering process. Sometimes we need to disable these properties for application security.

Then load index.html with the loadFile method;

Next is the register global shortcut:

globalShortcut.register('CommandOrControl+Shift+R'.() = > browserWindow.webContents.send("StartRecording"))
globalShortcut.register('CommandOrControl+Shift+S'.() = > browserWindow.webContents.send("StopRecording"))
Copy the code

When a shortcut key is pressed, a message is sent to the renderer process via IPC.

In renderer.js, the corresponding IPC message is received and the corresponding method is executed:

ipcRenderer.on("StartRecording", start);
ipcRenderer.on("StopRecording", stop);
Copy the code

Where the start method performs recording:

  1. Through desktopCapturer. GetSources screen source, here, take one of the first, often give priority to the screen;
  2. Through the navigator. MediaDevices. GetUserMedia to obtain video stream;
  3. Create mediaRecorder, record video data through mediaRecorder;
async function start() {
    // Already recorded
    if (mediaRecorder) return;

    // Take the first screen source, usually the main screen
    const sources = await desktopCapturer.getSources({ types: ["screen"]});const stream = await navigator.mediaDevices.getUserMedia({
      audio: false.video: {
        mandatory: {
          chromeMediaSource: "screen".chromeMediaSourceId: sources[0].id,
        },
      },
    });

    mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });
    mediaRecorder.ondataavailable = (event) = > {
      event.data.size > 0 && chunks.push(event.data);
    };

    mediaRecorder.start();
	  updateStatusText('Recording... ')}Copy the code

The end method ends recording and saves the file:

  1. Add end method to mediaRecorder, call mediaRecorder. Stop end recording;
  2. At the end, take the recorded data chunks and create Blob objects.
  3. Convert the Blob object to an arrayBuffer, and then to a buffer;
  4. Write buffer to local new video file;
  5. Open the video file;
  6. Reset mediaRecorder and Chunks
function stop(){
  if(! mediaRecorder)return

  mediaRecorder.onstop = async() = > {const blob = new Blob(chunks, { type: "video/webm" });
    const buffer = Buffer.from(await blob.arrayBuffer());
    const filePath = path.resolve(remote.app.getPath("downloads"), `The ${Date.now()}.webm`);

    fs.writeFile(filePath, buffer, () = > {
      shell.openPath(filePath);
      mediaRecorder = null;
      chunks = []
    });
  };
  mediaRecorder.stop();
  updateStatusText('free')}Copy the code

And you’re done.

(3) probe the Electron

Let’s revisit Electron’s official introduction: Building cross-platform desktop applications with JavaScript, HTML, and CSS.

As you can see from the examples above, nothing is written beyond the troika (not even CSS). For those of you who have developed NodeJS on the front end, it’s easy to understand the code without having to look at the parsing.

Does it feel like, without the main process and communication module, the development experience is like developing a web page with an integrated Node environment?

Electron itself provides a way to quickly open a link or HTML file without even writing the main process code, such as:

electron https://github.com
electron index.html
electron /Users/username/Projects/electron-demo/index.html
...
Copy the code

Each of these methods causes Electron to launch the app and open a window that loads the corresponding web link or file.

But if it’s just a web shell, shouldn’t we just use PWA?

Because Electron has so much more to offer.

Let’s look at the main body of electron, which consists of three parts:

  1. Chromium: For web content display;
  2. Node.js: used for file reading and writing, operating system and other low-level API interaction;
  3. Custom API: used to provide common system operation needs method, such as setting menu and tray, control window, etc.;

The official documentation itself pokes a joke: Developing applications using Electron is like building Node.js applications using a Web interface, or Web pages using seamlessly integrated Node.js. Many students are familiar with Web development and Node.js, so they need to be familiar with the development mode and API provided by ELECTRON to start electron development. In addition, if you want to build a larger project for use in the company, you also need some engineering experience in electron development.

Official documents are a good source of learning, but not the only reference. From the official document to the surrounding library document, the document can not explain their own to try, try not to open VScode source reference examination; Encounter a bug to github to find an issue, sometimes need to trace all the way back to the electron source and chromium source……

With the knowledge we learned and the pitfalls we stepped on, we came up with an open source project:

Github.com/tal-tech/el…

In the project, we summarized our own experience in learning and stepping pits, and made this project by referring to some excellent schemes of the open source community as a quick learning and stepping pits application.

At present, the most important functions are: first, the embedded code in the document can run directly, or you can modify the code to run directly on the interface, which is convenient to adjust parameters to see the effect; The second is the drill field, which is used to write some small functional modules to run directly. Currently, there are only basic templates. Our goal is to add many common functional modules in the future, such as screenshots, such as message notification, such as file upload and download…… Wait, wait, wait.

If you have any suggestions about the project, what functions you want, or what bugs you find, you are welcome to make issue on Github. Our reply speed is super fast.

In order to learn electron better, we have created a series at present, if you are interested, you can have a look

  • [Electron playground series] Menu
  • 【Electron Playground series 】Dialog with file selection
  • [Electron playground series] Agreement
  • 【Electron Playground series 】 The tray

For more complete documentation, please refer to the documentation below

Electron-Playground official document

Github address portal: github.com/tal-tech/el…