Electron + Vue builds a local player from scratch

Time:2020-10-31

Electron + Vue builds a local player from scratch

Why?

My girlfriend works in the late stage of audio. She usually collects some audio music and needs to see the frequency spectrum waveform of audio. It’s very inconvenient to play music and watch the waveform with Au software every time. Seeing that she works so hard, as a program ape, I am distressed. Therefore, there is such a small software. The technology involved in the software is mainly electronic, Vue, node, and the waveform display is mainly throughwavesurferGeneration.

Start from scratch – build projects

The project is built by Vue scaffold, so you need to install cli tools. If you have installed it, you can skip this step

npm install -g @vue/cli
# OR
yarn global add @vue/cli

After installation, the project is set up through scaffolding

vue create music

Vue needs to be integrated with electronic. There are mature Vue plug-ins in the community,Vue CLI Plugin Electron Builder

vue add electron-builder

Lazy people can go to clone directly. My built shelf can be developed directly,Poke it here

Start from scratch – project development

First of all, clear the functional requirements of this player, mainly these several

  • Do not add a file directory, load any local file system audio files, directly call the player to play
  • Previous song after Song function
  • Voice volume control
  • Custom software window

How to associate playback

How to realize associated playback? Because I’m not familiar with electron, I checked the data of electron for a long time, and finally found the configuration item. I need to configure fileassociations

        fileAssociations: [
          {
            ext: ["mp3", "wav", "flac", "ogg", "m4a"],
            name: "music",
            role: "Editor"
          }
        ],

After the configuration is completed, through the electronic’sOpen file eventTo get the local path of the open audio file. For windows, the process.argv To get the file path.

const filePath = process.argv[1];

How to load local audio files

After getting the local path of the file through the configuration, the next step is to read the audio file information through the path. Because the audio plug-in cannot resolve the absolute path, it needs to pass through the file system of nodefs.readFileSyncRead the buffer information of the file.

let buffer =  fs.readFileSync (diskpath); // read the file and convert the cache

After reading, the buffer needs to be converted into a node readable stream

const stream =  this.bufferToStream (buffer); // convert the buffer data into a node readable stream

Method conversionbufferToStream

    bufferToStream(binary) {
      const readableInstanceStream = new Readable({
        read() {
          this.push(binary);
          this.push(null);
        }
      });
      return readableInstanceStream;
    }

After converting to stream, audio stream needs to be converted into blob object to loadmethod

module.exports = streamToBlob

function streamToBlob (stream, mimeType) {
  if (mimeType != null && typeof mimeType !== 'string') {
    throw new Error('Invalid mimetype, expected string.')
  }
  return new Promise((resolve, reject) => {
    const chunks = []
    stream
      .on('data', chunk => chunks.push(chunk))
      .once('end', () => {
        const blob = mimeType != null
          ? new Blob(chunks, { type: mimeType })
          : new Blob(chunks)
        resolve(blob)
      })
      .once('error', reject)
  })
}

Transfer to blob

Let fileurl; // blob object
  streamToBlob(stream)
    .then(res => {
      fileUrl = res;
      // console.log(fileUrl);

      //Turn blob object into blob link
      let filePath = window.URL.createObjectURL(fileUrl);
      // console.log(filePath);
      this.wavesurfer.load(filePath);

      //Auto play
      this.wavesurfer.play();
      this.playing = true;
    })
    .catch(err => {
      console.log(err);
    });

In this way, the local file can be loaded and played

Previous song next song function

The function of the previous song and the next song here is based on the absolute path of the file obtained above, through the path module of node,path.dirnameGets the parent directory of the file.

const dirPath = path.dirname(diskPath);

And then through thefs.readdirWhen reading all the files in the directory, an array of file names will be returned to find the index of the file being played in the directory. The names of the previous song and the next song can be judged by the index of the array, and then assembled into an absolute path to read and play the resources

    playFileList(diskPath, pos) {
      let isInFiles;
      let fileIndex;
      let preIndex;
      let nextIndex;
      let fullPath;
      let dirPath = path.dirname(diskPath);
      let basename = path.basename(diskPath);
      fs.readdir(dirPath, (err, files) => {
        isInFiles = files.includes(basename);

        if (isInFiles && pos === "pre") {
          fileIndex = files.indexOf(basename);
          preIndex = fileIndex - 1;
          fullPath = path.resolve(dirPath, files[preIndex]);

          this.loadMusic(fullPath);
        }
        if (isInFiles && pos === "next") {
          fileIndex = files.indexOf(basename);
          nextIndex = fileIndex + 1;
          fullPath = path.resolve(dirPath, files[nextIndex]);
          this.loadMusic(fullPath);
        }
      });
    },

Voice volume control

The volume control needs to listen to the input input input to get the value of range, and then set the stylebackground-imageDynamically calculate the percentage, then call the setVolume method of wavesurfer to adjust the volume.

:style="`background-image:linear-gradient( to right, ${fillColor}, ${fillColor} ${percent}, ${emptyColor} ${percent})`"

Change volume changevol event

    changeVol(e) {
      let val = e.target.value;
      let min = e.target.min;
      let max = e.target.max;
      let rate = (val - min) / (max - min);
      this.percent = 100 * rate + "%";
      console.log(this.percent, rate);
      this.wavesurfer.setVolume(Number(rate));
    },

Custom title bar

I think the system’s own menu bar is too ugly, so I set no border and add the function of minimizing and closing. Minimize, close is through IPC communication, rendering process listen to the click operation, inform the main process to carry out the corresponding operation.

Rendering Progress

    close() {
      ipcRenderer.send("close");
    },
    minimize() {
      ipcRenderer.send("minimize");
    }

Main process

ipcMain.on("close", () => {
  win.close();
  app.quit();
});

ipcMain.on("minimize", () => {
  win.minimize();
});

Problems with opening multiple instances

In the actual test process, it is found that if a new music is opened, an instance will be opened again, and the overlay playback cannot be realized. After consulting the data, it is found that electron has onesecond-instanceEvent to monitor whether the second instance is opened. When the second instance is executed and calledapp.requestSingleInstanceLock()“), this event will be triggered in the first instance of the application, and relevant information of the second instance will be returned. Then, the main process will inform the rendering process of the local absolute path of the second instance. After receiving the information, the rendering process will immediately load the resources of the second instance. app.requestSingleInstanceLock (), indicating whether the application instance successfully acquired the lock. If it fails to obtain the lock, it can be assumed that another application instance has already obtained the lock and is still running, so it can be closed directly, thus avoiding the problem of opening multiple instances

Main process

const gotTheLock = app.requestSingleInstanceLock();
if (gotTheLock) {
  app.on("second-instance", (event, commandLine, workingDirectory) => {
    //Listen for a second instance and send the local path of the second instance to the rendering process
    win.webContents.send("path", `${commandLine[commandLine.length - 1]}`);
    if (win) {
      if (win.isMinimized()) win.restore();
      win.focus();
    }
  });

  app.on("ready", async () => {
    createWindow();
  });
} else {
  app.quit();
}

Rendering Progress

  ipcRenderer.on("path", (event, arg) => {
    const newOriginPath = arg;

    // console.log(newOriginPath);
    this.loadMusic(newOriginPath);
  });

Automatic update

The reason for the demand is that when I was very excited about the finished product for my girlfriend, I was embarrassed to be tried out many bugs by my girlfriend (covering my face), and then frequently modified the package, and then sent it to her through private email. Especially troublesome, so the demand is urgent. Finally, the data is checked and the requirement is realized by electronic Updater

Install electronic Updater

yarn add electron-updater

Publishing settings

    electronBuilder: {
      builderOptions: {
        publish: ['github']
      }
    }

Main process listening

autoUpdater.on("checking-for-update", () => {});
autoUpdater.on("update-available", info => {
  dialog.showMessageBox({
    Title: "new version released",
    Message: "there are new content updates, which will be re installed for you later.",
    Buttons: [OK],
    type: "info",
    noLink: true
  });
});

autoUpdater.on("update-downloaded", info => {
  autoUpdater.quitAndInstall();
});

Generating GitHub access token
Because GitHub is used as the update station, the corresponding operation permissions are required locally. Go here to generate token,Poke thisAfter generating, set the

[Environment]::SetEnvironmentVariable("GH_TOKEN","<YOUR_TOKEN_HERE>","User")
#For example [environment]:: setenvironmentvariable ("GH_ TOKEN","sdfdsfgsdg14463232","User")

Package and upload GitHub

yarn electron:build -p always

After completing the above steps, the software will automatically upload the packaged files to the release, and then edit the release to release directly. The software is updated based on the version number, so remember to change the version number

Start from scratch – end

As a program ape, the happiest thing is to get praise from my girlfriend. Although this is a small program, it is not difficult to realize. But when I finally made the smallest version available and presented it to my girlfriend, I saw the girl’s moving eyes. I think this should be the only time I feel relieved as a procedural ape. There are many improvements to the software. The source code is here,Here, GitHub

Recommended Today

Docker minimalist Guide

What is docker? Beginners can use the concept of “virtual machine” to understand docker. When we want to build a virtual machine, we need to download two things: virtual machine software (such as VMware) and. ISO file (such as Ubuntu), and then we can use the Ubuntu system in VMware. Similarly, when we want to […]