目录

标签: 屏幕录制

基于 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();
    },
  },
});

源码下载: