Electron新玩法:让你的桌面随着音乐动起来!

lxf2023-03-16 17:54:01

创作灵感

开局先上效果与源码(两个仓库都是本人提交的,内容相同):

music-wallpaper: 基于Electron开发的桌面插件,在壁纸层显示音频可视化效果 (gitee.com)

DLand-Team/music-wallpaper: 基于Electron开发的桌面插件,在壁纸层显示音频可视化效果 (github.com备份仓库)

Electron新玩法:让你的桌面随着音乐动起来!

最近闲来无事,想整点活,做点东西,给自己的开源攒点人气,于是决定研究下Electron。在有一次逛github的时候,发现了一个好玩的插件:

meslzy/electron-as-wallpaper: set your electron window as wallpaper behind desktop icons (github.com)

简单说下,就是将Electron窗口插入到桌面图标层和壁纸层之间,以达到这个仓库所说的“Electron As Wallpaper”的目的。于是,我也就打算用Electron做个桌面壁纸功能的小玩具。

在去年我写了个H5音乐播放器,研究了下音频可视化的效果,我决定通过Electron将这个效果在桌面上显示出来。

H5音乐播放器源码: 基于Angular+ionic的音乐播放器,支持实时歌词显示,音频可视化 (gitee.com)

代码搭建

如图,只有一个单一的音频可视化功能,直接用最基础的Electron即可完成,最多整个ts进去。像是网上广为流传,说是做Electron桌面端就应该整合vite + ts + vue + pinia,我是觉得对于这种小玩具就没必要哈,用不着高射炮打蚊子的做法。

创建项目方法就不赘述了,直接上package.json文件:

{
  "name": "music-wallpaper",
  "version": "1.0.0",
  "description": "A minimal Electron application",
  "main": "application-framework.js",
  "scripts": {
    "start": "npm run web-build:dev && electron ./application-framework.js",
    "package-build:win": "npm run web-build:prod && electron-packager . application --platform=win32 --arch=x64 --icon=favicon.ico --out=./out --asar --overwrite",
    "web-build:dev": "webpack --mode=development",
    "web-build:prod": "webpack --mode=production",
    "gyp-configure": "node-gyp configure",
    "gyp-rebuild": "node-gyp rebuild"
  },
  "dependencies": {
    "babylonjs": "^5.46.0",
    "babylonjs-materials": "^5.46.0",
    "bindings": "^1.5.0",
    "node-addon-api": "^6.0.0"
  },
  "devDependencies": {
    "@types/node": "^18.13.0",
    "electron": "^23.0.0",
    "electron-packager": "^17.1.1",
    "ts-loader": "^9.4.2",
    "typescript": "^4.9.5",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1"
  }
}

可以看到主要依赖只用了node编译工具与ts,其他的就是这两个所需要的一些边边角角的东西了。也用了babylonjs,因为我后面考虑整个3D可视化效果进去(目前你可以把它无视掉)。scripts里面的执行代码分别为:

标题作用
start运行程序
package-build:win打包为win平台可执行文件
web-build:dev使用webpack development模式编译页面代码
web-build:prod使用webpack production模式编译页面代码
gyp-configure配置node-gyp
gyp-rebuild使用node-gyp编译C++文件

非常基础的开发方法,我连webpack热更新都没做(作者太懒了)。不过这样降低配置量,降低学习成本,够用即可,也是一种好事。因为我不喜欢上来就搞一大堆配置啥的。

核心原理

既然说到了node-gyp与C++编译,那么这里就讲讲为啥需要这个东西。

上面提到的Electron As Wallpaper插件,就是通过C++调用Windows API,将窗口嵌入到桌面图标层与壁纸层之间。这个插件提供了npm包,可以在上述github仓库地址中详细查看引入方法。但是在此我没有通过npm去引入这个项目,我选择将核心C++代码放到本项目中,手动编译然后调用。这样也能够更好地去理解与学习Electron是如何通过C++与系统交互的。

原作者C++代码片段: Electron新玩法:让你的桌面随着音乐动起来!

原作者C++与Electron交互实现代码片段: Electron新玩法:让你的桌面随着音乐动起来!

具体调用方法,在本项目代码中可以看到,通过node-bindings实现调用,很简单,就两三个方法几行代码的量。也可以去插件作者的仓库深入研究。

抓取系统音频

Electron有一个名为desktopCapturer的模块,通过它来捕捉系统音频。Electron文档地址: desktopCapturer | Electron (electronjs.org)

直接在Electron的index.html页面中,通过调用navigator.mediaDevices.getUserMedia,使用如下配置参数,即可在回调中获取到系统音频。然后使用AudioContext即可实现对音频流的实时可视化效果。

navigator.mediaDevices.getUserMedia({
   // 配置参数
   audio: {
      // @ts-ignore
      mandatory: { chromeMediaSource: 'desktop' }
   },
   video: {
      // @ts-ignore
      mandatory: { chromeMediaSource: 'desktop' }
   }
}).then(stream => {
   // 在回调中获取到的系统视频与音频流,我们选择音频流即可
   let context = new AudioContext();
   this.source = context.createMediaStreamSource(stream);
   this.analyser = context.createAnalyser();
   this.analyser.fftSize = 2048;
   this.source.connect(this.analyser);
});

关于AudioContext如何实现音频可视化,这是前端一个很成熟的技术,网上有很多教程和案例,在此就不做赘述了。

主要代码解析

文件名作用
application-framework.jsElectron程序入口文件
application-preload.jsElectron页面预加载文件
application-main.tsElectron页面ts入口,也就是webpack编译ts的入口

application-framework.js中,初始化程序窗口,添加系统托盘图标等相关功能。以下为生成窗口的相关参数设置:

mainWindow = new BrowserWindow({
   show: false, // 开始时不显示
   frame: false, // 设置为false时可以创建一个无边框窗口
   focusable: false, // 窗口不能作为焦点被选中
   transparent: true, // 使窗口透明
   webPreferences: {
      preload: path.join(__dirname, 'application-preload.js'),
   }
});
mainWindow.loadFile('index.html').then(() => {
   try {
       // 调用C++代码,嵌入窗口到图标层与壁纸层之间
       bindings.attach(mainWindow.getNativeWindowHandle(), { transparent: true });
       mainWindow.maximize(); // 窗口最大化
       mainWindow.show(); // 等一切操作完成后,显示窗口
   } catch (e) {
       console.log(e);
   }
});

对于application-preload.js,它是Electron在页面运行其他脚本之前预先加载指定的脚本,在本项目中的作用就是创建Electron于页面沟通的桥梁,实现通过托盘栏菜单对页面的控制。

application-main.ts就是页面入口文件,webpack编译ts的入口,本项目使用的是最原始的方法,没有框架,仅有将ts编译为bundle.js供页面使用这个单一的功能。

文件名作用
src/core/engine.ts引擎
src/effect/*各个可视化效果的具体实现
src/wt/*js dom api的封装

src/core/engine.ts的作用是:通过实现一个“引擎”用来管理渲染循环以及各个效果的显示与关闭,这种方法在游戏开发中很常用。

// 将渲染控制在60帧
private loop(e: number) {
   if (e - this.frameTime >= 1000 / 60) {
      this.frameTime = e;
      this.render();
   }
   requestAnimationFrame((e1: number) => {
      if (this.allState) {
         this.loop(e1);
      }
   });
}

// 在每一帧中更新各个特效
private render(): void {
   this.analyser.getByteFrequencyData(this.voiceHigh);
   this.e1_2d?.update(this.voiceHigh);
   this.e2_2d?.update(this.voiceHigh);
   this.e3_2d?.update(this.voiceHigh);
}

src/effect/* 由于时间和篇幅的限制,各个效果的具体实现过程就不做详细介绍了,有兴趣可以直接看源码。每个效果都是作者自己手写的。

src/wt/* 封装了一些js dom api,用来生成页面上的dom元素。因为本项目也没用任何前端框架,作者是java gui开发出身的,很喜欢这种直接怼代码的gui写法。

结尾

初次写文章,也不知道咋结尾,也不知道该讲的讲清楚没,有问题欢迎留言讨论。另外本项目提供了打包好的文件,解压即可运行。release发布在gitee仓库中。欢迎大家给点star,star就是我整活的动力

music-wallpaper: 基于Electron开发的桌面插件,在壁纸层显示音频可视化效果 (gitee.com)