关闭数据连接,远离网络的嘈杂,感受生活带来的生机,带上一副耳机,倾听你我心声。PCA ( point change audio ) 本地音乐播放器。
PCA是基于 electron、vue3 的桌面应用 —— 本地音乐播放器,无需联网,无网络请求。随着HTML5时代的发展,audio元素支持的音频类型越来越多,常见的 MP3、OGG、FLAC等类型的音频文件都能播放;但有些类型例如APE、WMA等音频文件经过测试不能播放,甚至识别不出文件类型。为了兼容不支持的音频类型,做了个简易的音频转换器,它集成在本地音乐播放器里,稍微等待几秒既能转换完成;同时拥有多个主题,多种色彩;心情不好,换个主题,换个心情,快来体验吧。
list:[
{
title: "test",
artist: "t",
album: "test group",
container: "FLAC",
path: "D: \\音乐\\xxx.flac",
songSize: 46932581,
duration: 372.2710204081633,
bitrate: 1008375.2627008631,
sampleRate: 44100,
lossless: true,
picture: false,
isActive: true,
}
]
属性 | 属性介绍 |
---|---|
title | 歌名 |
artist | 歌手 |
album | 专辑 |
container | 音频编码格式 |
path | 路径 |
songSize | 文件大小(字节) |
duration | 持续时间(秒) |
bitrate | 每秒编码音频文件的比特数 |
sampleRate | 采样率,单位为每秒采样数(S/s) |
lossless | 是否无损 |
picture | 是否有封面/插图 |
isActive | 是否激活 |
依赖 | 依赖介绍 |
---|---|
electron | 集成Chromium 和 Node.js桌面应用框架,就是把网页嵌套进桌面应用 |
vue3 | 操纵数据,数据驱动视图 |
element plus | 快速构建html |
pinia | 持久化数据和状态管理 |
ffmpeg | 处理和操纵音频 |
music-metadata | 读取音频元信息 |
animate.css | 控制动画 |
-
修复并移除"上一首"、"下一首" 在可视化的操作。
-
修复歌词读取错误
-
新增单一歌曲的歌词增加或者修改
-
新增跟随封面色。
-
优化lrc歌词双时间同一段文字的匹配。
-
增加全局按键说明。
-
去除开始播放音量逐渐上升时的断崖式音量下降,造成不好体验。
-
新增全局按键控制播放、下一首、上一首。
-
新增在正在播放页面的全屏显示,实现歌词滚动的功能。
-
组件细分化,复用组件,减少单个页面的代码臃肿。
-
优化部分动画,使其更优雅。
-
0.0.0-0.0.9版本出现重大问题,修复事件重复执行、导致内存占用过大的问题。
-
修改时长计算算法,增加总时长计算。
-
正在播放页面的背景可切为封面色或主题色。
-
新增主题切换动画。
-
修复读取音频文件夹的报错导致停止读取。
-
修复 随机播放时,回退上一首音乐还是随机性。本应该是重复上一首音乐或者上上一首。。。
-
补充字体调整文字提示。
-
修复主题部分切换时还是显示上一个主题色。
-
新增音频文件无封面时,可自定义图片功能。
-
修复黑夜模式下的音乐控制按钮看不见的问题。
-
新增字体调整,可调整字体类型、大小、粗细、间隔。
-
优化定时器函数。
-
新增 "歌词" 功能。
-
修复扫描音频文件夹的子文件夹。优化读取音频信息。
-
新增 "音频调速" 功能。
-
优化音频后缀匹配规则
-
新增 "精确读取音频信息",只解决部分duration为"00:00"。
-
修复音频封面规则匹配,列表标签错乱规则。
-
增添 "正在播放" 页面切换动画。
- 新增 "正在播放" 页面。
-
修复播放顺序持续保存
-
优化事件造成的内存开销," 一键重置 "更彻底。
-
新增调整窗口大小的功能,实现响应式界面
-
重要修复音频封面,让其正常显示。
-
版本0.0.0和版本0.0.1禁止使用,会卡顿。
- 修改部分bug,增添可视化音频入口动画
- 实现音频播放,音频转换,主题切换 3 个主要功能。
-
列表为空,点击右下角 "+" 按钮添加文件夹;双击列表项播放音频,右键移除列表项;单击 ">" 打开列表项详情。
-
音频转换,选择需要转换的音频格式,点击 绿色 "+" 按钮添加文件夹, 勾选需转换音频,开始转换,转换完一般在需转换音频的文件夹下创建新音频文件,后缀大写以便区分。小白可以参考 "格式风格指南"
-
设置:一键初始化本应用、主题切换、一键初始化主题。
-
点击左上角 图标 logo 启动音频可视化。
- pointchangeaudio目录下
npm start
-
下载缓慢或者进度不动:
npm edit ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
官网里也有介绍 -
本项目是先装好vue3,其次在安装electron。由于electron 大部分使用 CommonJS规范,可vue3是ES Modules规范,二者有些冲突。最好解决方案删除package.json文件里的type: module,单纯使用CommonJS。
-
本项目几乎是ES Modules规范,但例如:node:path 使用ES Modules引入,
ReferenceError: __dirname is not defined
,不支持__dirname。解决 3 种方案:- 删除文件 package.json 中的配置项:"type": "module"
import { fileURLToPath } from 'url' import { dirname } from 'path' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename)
- 使用CommonJS导入。
-
preload.js 文件里使用ES Modules规范引入electron 文件无法挂载到window全局对象上,很迷。具体报错信息:
Uncaught ReferenceError: electron is not defined
,后来发现改后缀mjs+webPreferences的nodeIntegration: true
,问题解决。
-
Electron Forge 一体化打包,省事;但本项目音频转换依赖ffmpeg,打包后屡次报错,path正确,但就是报错,一般ffmpeg 等.exe文件打包在app.asar.unpack中才合适。
"config": { "forge": { "packagerConfig": { "asar": true,//electron 内置压缩 "name": "pointchangeaudio",// 应用程序的名称 "executableName": "pointchangeaudio(PCA)",//执行文件的名称 "icon": "./images/chrome-256x256"//应用程序的图标路径 (无需后缀) }, "makers": [ { "name": "@electron-forge/maker-zip",//压缩包 "platforms": [ "win32"//window平台 ] }, { "name": "@electron-forge/maker-squirrel",//Windows的.exe安装包 "config": { "setupIcon": "./images/chrome-256x256.ico" } } ], //插件 "plugins": [ { //自动解包本机模块插件 //此插件会自动将node_modules文件夹中的所有本机Node模块添加到asar.unpack文件夹中的packagerConfig配置选项中 "name": "@electron-forge/plugin-auto-unpack-natives", "config": {} } ] } }
-
electron-builder 很成熟的打包工具,无论用 cmd 还是 esM 都支持。在 package.json 配置好build
"build": {
//应用id 唯一id
"appId": "com.pointchange.audio",
//安装完之后的名称
"productName": "point change audio",
//版权
"copyright": "pointchange©20240719",
"directories": {
//输出文件夹
"output": "release"
},
//需要打包的文件 :例如本项目,
//本项目vue3+electron, vue3单独放一个文件夹好点,打包后依赖的node_modules会小很多。
//electron-builder自动打包node_modules,
//一些二进制文件 .exe 会在打包后app.asar.unpacked文件夹下
"files": [
"dist",
"vue_child/dist",
"preload",
"main.js",
"util",
"images"
],
//electron 内置加密
"asar": true,
//win配置
"win": {
"icon": "images/chrome-256x256.ico",
"target": [
{
//打包成一个独立的 exe 安装程序
"target": "nsis",
"arch": [
// 打出来32 位 + 64 位的包,就是window作兼容
"ia32"
]
}
]
},
//安装程序相关的配置
"nsis": {
"oneClick": false,
//语言 2052是中文
"language": "2052",
//是否开启安装时权限限制
"perMachine": true,
//允许修改安装目录
"allowToChangeInstallationDirectory": true,
//卸载时删除用户数据
"deleteAppDataOnUninstall": true,
// 安装图标
"installerIcon": "images/chrome-256x256.ico",
//卸载图标
"uninstallerIcon": "images/chrome-256x256.ico",
//安装时头部图标
"installerHeaderIcon": "images/chrome-256x256.ico",
// 创建桌面图标
"createDesktopShortcut": true,
//开始菜单图标
"createStartMenuShortcut": true
}
}
-
由于electron大量使用promise代理,对promise、async、 await有了深刻理解,尤其在错误管理方面,那一步出错几乎能准确定位并给用户提示
-
在创建audio对象时,总结出有 3 种方法:
-
audio标签:最好的方式,但考虑到各个页面可能用到或着控制audio事件,所以单页面使用方便,多页面或多个组件使用起来就麻烦。
-
new audio 不创建audio标签,但使用几乎与audio标签相同的功能。
-
new AudioContext() 几乎兼容上面两种方案。有一种情况: AudioContext.createBufferSource()作为音频源,要想调节播放进度条,AudioContext继承的currentTime是只读属性,无法更改。
-
-
调节播放进度条卡住或者卡住后重新播放的 bug: 通过网络请求,自定义协议拦截,从node读取出来的流(stream)文件,把流响应给页面时,响应头作相应配置。感谢这位老哥的文章。
-
map 数组里每项为 引用类型 会改变原数组。
-
有的时候以为 forEach 这么简单函数应该像for循环一样使用,只是for的函数版;其实forEach和promise、async、await结合就发现不是预期的结果。不报错,无提示,就是结果不对。
forEach() 期望的是一个同步函数,它不会等待 Promise 兑现。
解决方法 promise.all() 或者 简简单单用 for 代替就完事了。 -
音频可视化,一个非常消耗内存资源的功能。虽然好看好完,但必须对canvas熟悉,canvas可以说是另一个特殊的css,了解完canvas基本功能就够呛的了。在某个配合完美的图形上,要是积分学得好,就容易实现。
音频格式转换
-
本质上只是音频数据格式转换,一般是从无损转换有损。音频元数据转换过程中只是删除某些数据,压缩到文件小了。元数据记载信息一般是不变。所以前端只是把数据的路径传给后端,后端执行转换操作。后端转换完后,有两种响应操作:
-
只响应音频流,前端把转换前的音频文件信息复制下来,等待后端响应流数据后,直接音频文件信息与流数据一一匹配,特点:快速,高效。
-
响应音频流和再次对转换后音频流解析的音频文件信息,后端都处理完,前端只是展示到页面。
前者适合元数据不变的情况下,后者更适合元数据发生意外的各种情况。
-
-
ffprobe读取音频元数据很慢,但几乎能读取比较多可用的数据。music-metadata几乎是一瞬间读取,很快但读取数据会出现意外情况。将二者结合会更适合应对大多数意外情况。
-
前端与后端交互,electron给出了很多种形式。总结目前知道的只有两种;主进程与渲染进程通信和自定义协议通信(拦截网络请求):
-
起初设计上只知道用主进程与渲染进程通信,但在调整播放进度条功能下,这重通信就显得有点麻烦,况且前端定位在进度条某个位置,还得传送位置给主进程。要是一开始传整个文件给前端倒是不麻烦,但万一音频文件一百多m,那就又有问题;后端得分块读取慢慢传。处理过程繁琐。
-
了解自定义协议( 感谢这位老哥 ), 解决响应问题后,就可以像网络请求一样处理。
-
二者结合其实更好,但也可以类似单纯使用网络请求。但是使用自定义协议后,从代码写法上,protocol 不能在任何除
app.whenReady().then()
函数里面,造成现在 main.js 文件十分糟糕与尴尬得局面
-
-
可视化页面是另一个新的 BrowserWindow 的窗口。其中数据传输和又一次类似事件显得代码臃肿。
-
本项目没有使用 TS 语法,考虑到electron 会出现更多的问题,所以在第一个electron 应用上暂时不用。不用带来的坏处十分多。例如:主进程与渲染进程通信,更直白的可以这么理解:
graph LR A{main.js} -->|send|B{preload.js} B -->|callback| C{页面} C -->|fn| B B -->|callback| A F[主进程与渲染进程通信]
- 在经过一层传输数据后,往往要返回传送的对象查看类型。更加增加查看操作。
-
在选取本地存储持久化数据方式有 3 种:
-
保存在数据库中,但要增加对数据库的操作
-
保存在类似 .txt 文件,就是 node 日常对文件增删改查
-
保存在 localStorage 中,可每个域只能有 5MB 大小。
本项目选取localStorage,主要是用于测试。目前 localStorage 不能存储buffer 数据,否则会造成页面卡顿。
-
-
element plus 对于vue支持,节省不少代码,简洁而优雅。但在主题颜色选择中有 el-color-picker 颜色选择器带来新的问题:
Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive.
就是滚动性能提升的建议。-
要么事件+
passive: true
-
要么是用插件
default-passive-events
,但随之而来,调节音频的Slider进度条报错:Unable to preventDefault inside passive event listener invocation
需要passive: false
,这就本末倒置了。
还得是原生 input type="color" 可以轻松解决问题。
-
本次项目作为简易本地播放器,初次体验 electron 知道了还可以以嵌套网页的形式作为桌面端应用,考虑到 main.js 的书写糟糕、可视化音频的臃肿、选取通信方式和存储格式的犹豫,可能后期维护与优化本项目造成一定的难度。