目录

标签: Electron

基于 Electron 的屏幕录制工具(带画中画)

本文主要实现了基于 Electron 技术的屏幕录制、麦克风声音录制、摄像头(画中画)录制。

整体效果如下所示:

代码中,比较重要的就是对多个媒体轨道进行合成、视频合成。

核心代码如下:

// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// No Node.js APIs are available in this process because
// `nodeIntegration` is turned off. Use `preload.js` to
// selectively enable features needed in the rendering
// process.

const jQuery = (window.jQuery = require('jquery'));
require('@popperjs/core');
require('bootstrap');
const Vue = require('vue/dist/vue');
const electron = require('electron');
const fs = require('fs');
const SCREEN_WIDTH = 3072;
const SCREEN_HEIGHT = 1920;
const PlayerCanvas = require('./PlayerCanvas');

new Vue({
  el: '#vueapp',
  data: {
    recording: false,
  },
  mounted() {
    this._playerCanvas = new PlayerCanvas(SCREEN_WIDTH, SCREEN_HEIGHT);
  },
  methods: {
    // 开始录制
    async btnStartRecordClicked(e) {
      this._stream = new MediaStream();
      await this.attachAudioStream();

      // 摄像头 stream
      this._cameraStream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: false,
      });
      this._playerCanvas.setCameraVideo(
        this.createVideoElementWithStream(this._cameraStream)
      );

      // 屏幕 stream
      this._screenStream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          mandatory: {
            chromeMediaSource: 'desktop',
            minWidth: SCREEN_WIDTH,
            maxWidth: SCREEN_WIDTH,
            minHeight: SCREEN_HEIGHT,
            maxHeight: SCREEN_HEIGHT,
          },
        },
      });
      this._playerCanvas.setScreenVideo(
        this.createVideoElementWithStream(this._screenStream)
      );
      //
      this._audioStream
        .getAudioTracks()
        .forEach((value) => this._stream.addTrack(value));
      let playerCanvasStream = this._playerCanvas.canvas.captureStream();
      playerCanvasStream.getTracks().forEach((t) => this._stream.addTrack(t));
      this.$refs.preview.srcObject = playerCanvasStream;
      this.startRecord();
    },
    // 附加音频流
    async attachAudioStream() {
      // 获取麦克风流
      this._audioStream = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
      });
      // 将麦克风的流,附加到主流上
      this._audioStream
        .getAudioTracks()
        .forEach((value) => this._stream.addTrack(value));
    },
    // 停止录制
    btnStopRecordClicked(e) {
      this.recording = false;
      this._recorder.stop();
    },
    // 创建一个 HTMLVideoElement
    createVideoElementWithStream(stream) {
      let video = document.createElement('video');
      video.autoplay = true;
      video.srcObject = stream;
      return video;
    },
    // 开始录制
    startRecord() {
      this._recorder = new MediaRecorder(this._stream, {
        mimeType: 'video/webm;codes=h264',
      });
      this._recorder.ondataavailable = async (e) => {
        let path = electron.remote.dialog.showSaveDialogSync(
          electron.remote.getCurrentWindow(),
          {
            title: '保存文件',
            defaultPath: 'ScreenData.webm',
          }
        );

        let dataArrayBuffer = await e.data.arrayBuffer();
        fs.writeFileSync(path, Buffer.from(dataArrayBuffer));
        // fs.writeFileSync(path, new Uint8Array(dataArrayBuffer));
      };
      this._recorder.start();
      this.recording = true;
    },
  },
});

完整源代码下载:

基于 Electron 的屏幕录制工具

主要基于 Chrome 内核提供的屏幕录制流(Stream)、麦克风流(Stream),进行画面与音频轨道合并。

最终会生成录制好的 webm 格式的文件,可以使用浏览器来打开它进行播放。或者使用 ffmpeg 类似的工具进行视频格式转换,使用其他工具进行播放。

实现效果如图所示:

核心代码如下:


const jQuery = (window.jQuery = require('jquery'));
require('@popperjs/core');
require('bootstrap');
const Vue = require('vue/dist/vue');
const electron = require('electron');
const fs = require('fs');

new Vue({
  el: '#vueapp',
  data: {
    recording: false,
  },
  methods: {
    async btnStartRecordClicked(e) {
      this._stream = await navigator.mediaDevices.getUserMedia({
        video: false,
        audio: true,
      });
      let screenStream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          mandatory: {
            chromeMediaSource: 'desktop',
            minWidth: 3072,
            maxWidth: 3072,
            minHeight: 1920,
            maxHeight: 1920,
          },
        },
      });
      screenStream
        .getVideoTracks()
        .forEach((value) => this._stream.addTrack(value));
      this.$refs.preview.srcObject = screenStream;
      this._recorder = new MediaRecorder(this._stream, {
        mimeType: 'video/webm;codes=h264',
      });
      this._recorder.ondataavailable = async (e) => {
        let path = electron.remote.dialog.showSaveDialogSync(
          electron.remote.getCurrentWindow(),
          {
            title: '保存文件',
            defaultPath: 'ScreenData.webm',
          }
        );

        let dataArrayBuffer = await e.data.arrayBuffer();
        fs.writeFileSync(path, Buffer.from(dataArrayBuffer));
        // fs.writeFileSync(path, new Uint8Array(dataArrayBuffer));
      };
      this._recorder.start();
      this.recording = true;
    },
    btnStopRecordClicked(e) {
      this.recording = false;
      this._recorder.stop();
    },
  },
});

源码下载:

electron-store 保存应用数据

比如有一些用户数据需要保存、应用的一些个性化配置需要保存到本地等,都可以使用这个组件来保存。

本质上,这个组件是操作了当前应用的配置文件 “config.json” 。
所保存的路径,可参考官方文档,其中 getPath("userData") 得到的路径。

electron-store:GitHub 文档

示例

npm install electron-store --save-dev
const Store = window.require('electron-store')

// 存储
store.set('unicorn', '这是需要存储的内容');

// 获取
store.get('unicorn');

就是这么简单易用。

另外,多个层级的JSON数据,可以使用 store.get('user.depatment.name') 这样的方式来获取。

关于 electron-store ,推荐一篇网友的文章:进入推荐文章

Electron-Builder 减小压缩包大小

electron-builder 默认会将 node_modules 中的所有文件打入安装包内,实际上我们并不需要那么多的安装包,因为我的 Web 程序已经编译,如果没有用到一些 node.js 的包的话,直接忽略 node_modules 即可,如果有用到的话,手动配置下导入相关的包即可。

比如我的 electron-builder 配置如下:

  "build": {
    "files": [
      "build" ,
      "!node_modules",
      "node_modules/electron-is-dev"
    ]
  }

build 目录是我 react 生成出来的 web 文件。

!node_modules 表示打包完全忽略这个文件夹

node_modules/electron-is-dev 表示我需要包含打包进去的文件夹。

创建 Electron + React 项目流程

1. 全局安装 Create-React-App 脚手架

npm isntall -g create-react-app

输入:create-react-app -V (注意 V 是大写)可查看当前脚手架版本号

2. 创建 React 项目

create-react-app project-name

project-name 表示你的项目名称,可以自己取一个。

创建完成后,就可以 cd 到项目目录中,npm start 运行,即可弹出项目的初始默认站点,说明 OK 了。

3. 引入 Electron 包

 npm install electron --save-dev

过程中会下载一个包,如果很慢或无法下载的话,可以用以下命令代替:

ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ npm install electron --save-dev

4. 添加 main.js 文件与 package.json 配置

main.js 直接从官网拷贝:进入官网文档 , 并进行修改

    webPreferences: {
      nodeIntegration: true,
      enableRemoteModule: true,   // 这行代码必须加进去,不然渲染进程无法访问 nodejs,electron 的资源
    }

然后在 package.json 文件中加入配置

  1. "main":"./src/electron/main.js" 其中的路径为你的 main.js 路径配置。
  2. "homepage": "./" 表示请求资源用相对路径,否则打包之后请求不到js,css资源

继续:引入 electron-is-dev 包

npm install electron-is-dev --save

这个包用于判断当前运行环境是开发环境还是生产环境,在 main.js 中,我们这里加一个判断:

继续:引入 concurrently

npm install concurrently --save

这个包用于同时启动多个服务用

继续:引入 wait-on

npm install wait-on --save

用于启动等待命令

const isDev = require('electron-is-dev')
const path = require('path')


if (isDev) {
  win.loadURL('http://localhost:3000')
} else {
  win.loadFile(path.join(__dirname, '../build/index.html'))
}

路劲为 main.js 相对 build 文件夹(React打出来的包文件夹)的路径。

可选配置(没有也没关系)

修改package.json,添加一个electron-dev脚本命令:
{
  ...
  "scripts": {
    ...
    "dev": "concurrently \"BROWSER=none react-scripts start\" \"wait-on http://localhost:3000 && electron .\"",
    ...
  }
  ...
}

这样就可以直接 npm run dev 开启调试模式,不用单独再运行 React 和 Electron。

以上,即可:npm run dev 运行程序

Electron-Vue 从创建到发布

本文为 Electron-Vue 创建项目到发布的关键流程,其中一些命令如果报错,应该是缺少一些环境导致的错误,针对错误进行相应的环境安装即可。

一、必备基础环境安装

检查基础环境:

# 下面这行的命令会打印出Node.js的版本信息
node -v

# 下面这行的命令会打印出npm的版本信息
npm -v

基础环境官方文档

二、创建一个 Electron-Vue 项目

执行以下命令创建第一个 Electron-Vue 项目,vue-cli 如果已经安装过了的话,就不必再次安装了。(建议到自己创建的目录后执行,它会为你创建项目文件夹)

# 全局安装vue-cli
npm install -g vue-cli
# 使用vue-cli创建electron-vue手脚架项目
vue init simulatedgreg/electron-vue my-project

耐心的等待一段时间(可能需要梯子),就可以直接运行应用了。

# 安装依赖
$ cd my-project
$ npm install
# 运行程序
npm run dev

运行成功,如果中途有出现报错,对相应的错误进行搜索引擎检索解决。一般都是缺失环境导致。

附:Electron-Vue 文档

解决 Electron-Vue 报错:Webpack ReferenceError: process is not defined

网上找了几种方式,让修改 webpack.renderer.config.jswebpack..config.js 都不太好使,应该是由于不同的版本造成的。这里给出一种我目前(2020-10-19)配置的最新环境,可以解决的方案:

解决方案地址:https://github.com/SimulatedGREG/electron-vue/issues/871#issuecomment-564302194

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>vest</title>
    <% if (htmlWebpackPlugin.options.nodeModules) { %>
      <!-- Add `node_modules/` to global paths so `require` works properly in development -->
      <script>
        require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
      </script>
    <% } %>
  </head>
  <body>
    <div id="app"></div>
    <!-- Set `__static` path to static files in production -->
    <%  if (!require('process').browser) { %>
      <script>
        if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
      </script>
    <% } %>

    <!-- webpack builds are automatically injected -->
  </body>
</html>

以上代码,覆盖到 index.ejs 文件即可。

运行效果