preface

In the last article, we talked about what kind of bullet screen gadget we are going to make and what kind of technology we are going to use. So, starting with this article, we’ll put ideas into code step by step. In this article, we will use Electron to realize the receiving end and make our barrage fly.

rendering

As shown in the figure above, while showing PPT, users can scan the small program QR code to express their thoughts in real time, so as to achieve interactive effect.

Introduction to Electron

If you already know something about Electron, skip this part.

Electron is an open source library developed by Github for building cross-platform desktop applications using HTML, CSS, and JavaScript. Electron does this by merging Chromium and Node.js into the same runtime environment and packaging it as an application for Mac, Windows, and Linux.

In Electron, the main process and the render process are an important concept.

Main process and renderer process

A process is an instance of a computer program executing. Electron uses both main(the main process) and one or more rendere(the rendering process) to run multiple programs.

In Node.js and Electron, each running process contains a Process object. This object acts as a global representation of the current process. As a global variable, it can be retrieved at any time in the application without requiring ().

The main process

The main process, usually a file named main.js, is the entry file for each Electron application. It controls the entire life cycle of the App, from opening to closing. It also manages native elements of the system such as menus, menu bars, Dock bars, trays, etc. The main process is responsible for creating each render process of the APP. And the entire Node API is integrated into it.

The main process file for each app is defined in the main property of package.json. That’s why Electron can know which file to boot from.

In Chromium, this process is called a “browser process”. It was renamed at Electron to avoid confusion with the renderer process.

Rendering process

A renderer is a browser window inside your application. Unlike the main process, it can have multiple processes running in different places at the same time. And they can be hidden.

In normal browsers, web pages usually run in a sandbox and cannot use native resources. However, Electron users can perform some low-level interactions with the operating system on the page with the support of the Node.js API.

General architecture

As can be seen from the above renderings, the interface is mainly composed of bullet screen interface and small program TWO-DIMENSIONAL code.

Combined with the main process and rendering process mentioned above, we can put bullet screen and QR code in two rendering processes respectively. The main process communicates with the NodeJS server through Websocket, and distributes the received data to the corresponding rendering process.

Quick start

A quick start example is available to give you an idea of how the entire application works.

Clone sample project repository
$ git clone https://github.com/electron/electron-quick-start

# Enter this warehouse
$ cd electron-quick-start

Install dependencies and run
$ npm install && npm start
Copy the code

As you can see from package.json, the main property has a value of main.js, which is the main process entry file. In this entry file, a BrowserWindow is created through BrowserWindow and an HTML file is loaded. Yes, this is called the rendering process.

In this sense, what we need to do is very simple, basically the same as if we were writing static pages. Once you’ve written the code to create the browser window, all that’s left is to write the static page. Of course, there are also data communication schemes to consider.

To start developing

Creating a Browser window

In the main process, two rendering processes are created through API BrowserWindow to display the main interface of bullet screen and two-dimensional code respectively.

function createMainWindow() {
    mainWindow = new BrowserWindow({
        width: 1920.height: 1080.transparent: true.frame: false.resizable: false.alwaysOnTop: true.center: true.skipTaskbar: true.autoHideMenuBar: true.focusable: false
    });
    mainWindow.setAlwaysOnTop(true.'pop-up-menu'); // Make sure you do this otherwise you won't see it in full screen on your MAC

    mainWindow.maximize();// Maximize the window
    mainWindow.setIgnoreMouseEvents(true); // Click Through
    mainWindow.loadURL(`file://${__dirname}/app/index.html`);
}

function createQrcodeWindow() {
    qrcodeWindow = new BrowserWindow({
        width: 200.height: 200.transparent: true.frame: false.resizable: false.minimizable: false.maximizable: false.alwaysOnTop: true.center: true.skipTaskbar: true.autoHideMenuBar: true
    });
    qrcodeWindow.setAlwaysOnTop(true.'pop-up-menu'); // Make sure you do this otherwise you won't see it in full screen on your MAC

    qrcodeWindow.loadURL(`file://${__dirname}/app/qrcode.html`);
}

Copy the code

There are a lot of configuration items for creating Windows, so you can probably read through the official documentation to see what you can do.

Since our bullet screen is always on the top and cannot affect the operation of the lower layer, we set transparent, border-free, and can ignore the mouse event (clicking can penetrate), so that the lower window can be seen and related operations can be carried out at the same time. In addition, we also set no zoom in or out, and maximize the barrage window.

The system tray

Because we do not want the window to appear in the taskbar, after all, the bullet screen is always running and takes up space and is an eyesore in the taskbar, we prefer it to appear in the system tray, as shown below:

Above is the Mac effect, with Windows appearing in the lower right corner.

We can set up the system Tray through the API Tray.

function initTrayMenu() {
    let iconPath = path.join(__dirname, 'ico/favicon.ico');
    if (os.type() === "Darwin") {
        iconPath = path.join(__dirname, 'ico/favicon.png');
    }
    const nimage = nativeImage.createFromPath(iconPath);
    tray = new Tray(nimage);
    tray.setToolTip('barrage');
    const contextMenu = Menu.buildFromTemplate([
        {
            label: 'Display barrage'.type: 'radio'.click: showMainWindow
        },
        {
            label: 'Close the barrage'.type: 'radio'.click: hideMainWindow
        },
        {
            type: 'separator'
        },
        {
            label: 'Display qr code'.type: 'radio'.click: showQrcodeWindow
        },
        {
            label: 'Hide qr code'.type: 'radio'.click: hideQrcodeWindow
        },
        {
            type: 'separator'
        },
        {
            label: 'exit'.type: 'normal'.click: function () { app.quit(); }}]); tray.setContextMenu(contextMenu);// Set the menu
    tray.on('click', handleToggleShowMainWindow);
}
Copy the code

Communicates with the NodeJS server

In our design, the communication between the receiver and the server uses websocket protocol. Therefore, in addition to creating the renderer process, the main process also has an important task, which is to establish a Websocket connection with the server.

We chose to use the WS module to quickly establish connections:

const ws = require('ws');
const Socket = new ws('wss://danmu.xxx.com');// The parameter is the socket service address
Socket.on('open'.function open() {
    // During initialization, send the initialization message with the ID of the receiving client to obtain the small program QR code
    const initData = {
        type: 'INIT'.clientId: 'xxxx' // Unique ID of the client
    }
    Socket.send(JSON.stringify(initData));
});

Socket.on('message'.function incoming(data) {
    // Distribute the received data
    try {
        const received_msg = JSON.parse(data);
        if (received_msg.type === 'qrcode') {
            qrcodeWindow.webContents.send('qrcodeBase64', received_msg.data);
        }else{
            mainWindow.webContents.send('new-message', received_msg); }}catch (error) {
        console.log(error); }});Copy the code

After establishing a connection with the server, we sent the initialization message. We agreed that after receiving the initialization message, the server would generate a small program TWO-DIMENSIONAL code with parameters according to clientId and return the Base64 data of the two-dimensional code. So, when we receive a new message, we determine the message type and forward it to the corresponding renderer.

As mentioned above, you need to provide a unique client ID to the NodeJS server to generate a unique applets QR code. We can generate it using the following method:

function generateUUID() {
    return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
        returnv.toString(16); }); } // Why do you use this method to generate UUID?Copy the code

In addition, we hope that the ID will remain the same after the first use of the danmu app. After all, the qr code changes all the time, which is very unfriendly to users. So we need to write this ID in a local file. If you are lazy, you can also use the module superelectron-store written by others. The principle is to write the information that needs to be stored locally in a JSON file. Various special paths can be obtained through app.getPath(name).

For example, app.getPath(“appData”) can get the application data folder of the current user. The default is:

  • Windows: % APPDATA %
  • Linux: $XDG_CONFIG_HOME or ~/.config
  • MacOS: ~ / Library/Application Support

See the official documentation for more paths.

Communication between the main process and the renderer

Here’s ipcMain, ipcRenderer, and webContents, which are all instances of the EventEmitter class.

IpcMain: When used in the main process, it handles asynchronous and synchronous information sent from the renderer process. Messages sent from the renderer will be sent to this module.

IpcRenderer: You can use some of the methods it provides to send synchronous or asynchronous messages from the renderer to the main process. It can also receive messages returned by the main process.

WebContents: A property of the BrowserWindow object whose send method can send an asynchronous message to the renderer process, and can send any parameters.

Check out the qr code for chestnuts:

// In the main process
ipcMain.on("qrcodeFinished".function () {
    // doSomething
});
qrcodeWindow.webContents.send('qrcodeBase64', received_msg.data);

// Render process
const ipcRenderer = require('electron').ipcRenderer;
ipcRenderer.on('qrcodeBase64'.function (event, data) {
    const qrcode = document.getElementById('qrcode');
    qrcode.src = data;
    qrcode.style.display = 'block';
    ipcRenderer.send("qrcodeFinished");
});
Copy the code

Let the barrage fly

As mentioned above, we created the browser window in the main process, and then used the browser window instance to load the HTML file to display our page for processing. So, making a barrage fly is no different from writing a static page, where the data comes from receiving messages sent by the main process.

How to make the barrage move, this should be familiar to everyone, is to constantly update the location of the barrage can be.

Here are going to have to talk about window. RequestAnimationFrame () this artifact. This method tells the browser that you want the animation executed and asks the browser to call the specified function to update the animation before the next redraw. This method takes as an argument a callback function that is called before the browser redraws.

The specific implementation will not be described here.

Packaging application

The above Outlines the key points of application development. Once the development is complete, it’s the packaging phase.

We use the electron Builder to pack applications very easily.

Installation module:

npm install electron-builder --save-dev
Copy the code

Add the following to package.json:

"scripts": {
    "start": "electron ."."pack:win": "electron-builder --win --ia32"."pack:mac": "electron-builder --mac"
},
"build": {
    "appId": "com.Barrage.app"."productName": Barrage "666"."copyright": "Copyright © 2018 ${an}"."electronVersion": "3.0.4"."mac": {
      "icon": "ico/favicon.icns"."artifactName": "${productName}_Setup_${version}.${ext}"
    },
    "win": {
      "target": "nsis"."icon": "ico/favicon.ico"."artifactName": "${productName}_Setup_${version}.${ext}"}}Copy the code

Package command:

npm run pack:win
//or
npm run pack:mac
Copy the code

See the official website documentation for details.

other

  • Automatic updates: This should also be an integral part of the process, and we can use the electron-updater to configure automatic updates for our applications. I won’t go into that here.
  • Error log collection: A complete application should have error logs.
  • more…

conclusion

This paper Outlines the key points of developing the Electron receiver. As a matter of fact, it may take a lot of space to explain each point in detail. The best plan, still should oneself start work to go practice, encounter a problem to solve a problem, certainly harvest somewhat.

Thank you for your patience. Above, if there are mistakes, welcome to correct!

The latter

When I was typing code, a few words suddenly appeared on the screen. It was really scary. Later, I checked it. It should be because the screenshot in this article has my client QR code. Some friends scanned the code and experienced a wave, so I received it.

So, quickly add the first experience version. The functionality is not perfect, there should be a lot of bugs.

Download experience

  • Click to download the Mac version
  • Click to download the Windows version

@Author: TDGarden