Electron+vue从零开始打造一个本地播放器的方法示例


Posted in Javascript onOctober 27, 2020

为什么要做?

女朋友工作是音频后期,平常会收集一些音频音乐,需要看音频的频谱波形,每次用au这种大型软件播放音乐看波形,很不方便,看到她这么辛苦,身为程序猿的我痛心疾首,于是,就有了这么一个小软件,软件涉及到的技术主要为electron,vue,node,波形的展示主要通过wavesurfer生成。

从零开始-搭建项目

项目通过vue脚手架搭建的,所以需要安装cli工具,如果已经装了,可以跳过这一步.

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

装好后,通过脚手架搭建项目

vue create music

vue需要与electron集成,这里社区已经有比较成熟的vue插件了,Vue CLI Plugin Electron Builder。

vue add electron-builder

懒人可以直接去clone我的搭建好得架子直接开发, 戳这里。

从零开始-项目开发

首先先明确下这个播放器的功能需求,主要有这几个

  • 不添加文件目录,加载任意的本地文件系统内的音频文件,直接调用播放器播放
  • 前一首后一首功能
  • 声音音量控制
  • 自定义软件窗口

如何关联播放

如何实现关联播放?因为对electron不是很熟,查了很久electron的资料,终于找到了配置项,需要配置fileAssociations

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

配置好后,通过electron的open-file事件,获取打开的音频文件的本地路径。对于windows,需要通过process.argv,来获取文件路径。

const filePath = process.argv[1];

如何加载本地音频文件

上一步通过配置拿到文件的本地路径后,下一步就是通过路径读取音频文件的信息。由于音频的插件无法解析绝对路径,所以需要通过node的文件系统,通过fs.readFileSync读取到文件的buffer信息。

let buffer = fs.readFileSync(diskPath); //读取文件,并将缓存区进行转换

读取后需要将buffer转换成node可读流

const stream = this.bufferToStream(buffer);//将buffer数据转换成node 可读流

转换方法 bufferToStream

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

转换成流后需要将音频流转换成blob对象来加载,实现方法

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)
 })
}

转blob

let fileUrl; // blob对象
 streamToBlob(stream)
  .then(res => {
   fileUrl = res;
   // console.log(fileUrl);

   //将blob对象转成blob链接
   let filePath = window.URL.createObjectURL(fileUrl);
   // console.log(filePath);
   this.wavesurfer.load(filePath);

   // 自动播放
   this.wavesurfer.play();
   this.playing = true;
  })
  .catch(err => {
   console.log(err);
  });

这样就实现了加载本地文件播放了

上一首下一首功能

这里的上一首下一首的功能是基于上面获取到的文件的绝对路径,通过node的path模块,path.dirname获取到文件的父级目录。

const dirPath = path.dirname(diskPath);

然后通过fs.readdir读取目录下所有文件,会返回一个文件名数组,找到该目录下正在播放的文件的下标,通过数组下标判断前一首和后一首歌曲的名称,然后再组装成绝对路径,读取资源播放

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

声音音量控制

音量控制需要通过监听input的键入事件,获取到range的值,然后通过设置样式background-image,动态计算百分比,然后调用wavesurfer的setVolume方法调节音量

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

改变音量changeVol事件

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

自定义标题栏

个人觉得系统自带的菜单栏太丑了,就给设置了无边框再自己加上最小化,关闭的功能。最小化,关闭是通过ipc通信,渲染进程监听到有点击操作后,通知主进程进行相应的操作。

渲染进程

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

主进程

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

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

打开多个实例的问题

在实际测试的过程中发现会出现,打开一首新的音乐播放,就会出现重新开一个实例的现象,不能实现覆盖播放,后面查阅资料发现electron有一个second-instance事件,可以监听是否打开了第二个实例。当第二个实例被执行并且调用 app.requestSingleInstanceLock()") 时,这个事件将在应用程序的首个实例中触发,并且会返回第二个实例的相关信息,然后通过主进程通知渲染进程,告知渲染进程第二个实例的本地绝对路径,渲染进程接收到信息后,立马加载第二个实例的资源。app.requestSingleInstanceLock(),表示应用程序实例是否成功取得了锁。 如果它取得锁失败,可以假设另一个应用实例已经取得了锁并且仍旧在运行,所以可以直接关闭掉,这样就避免了打开多个实例的问题

主进程

const gotTheLock = app.requestSingleInstanceLock();
if (gotTheLock) {
 app.on("second-instance", (event, commandLine, workingDirectory) => {
  // 监听是否有第二个实例,向渲染进程发送第二个实例的本地路径
  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();
}

渲染进程

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

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

自动更新

需求的起因是,在很兴奋的给女朋友成品的时候,尴尬的被女朋友试出很多bug(捂脸ing),然后频繁的修改打包,然后通过私发传给她。特别麻烦,所以这个需求很急迫。最后查了资料,通过electron-updater实现了这个需求.

安装electron-updater

yarn add electron-updater

发布设置

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

主进程监听

autoUpdater.on("checking-for-update", () => {});
autoUpdater.on("update-available", info => {
 dialog.showMessageBox({
  title: "新版本发布",
  message: "有新内容更新,稍后将重新为您安装",
  buttons: ["确定"],
  type: "info",
  noLink: true
 });
});

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

生成Github Access Token

因为是用github作为更新站,所以本地需要相应的操作权限,去这里生成token,戳这,生成后,在powershell中设置

[Environment]::SetEnvironmentVariable("GH_TOKEN","<YOUR_TOKEN_HERE>","User")
# 例如 [Environment]::SetEnvironmentVariable("GH_TOKEN","sdfdsfgsdg14463232","User")

打包上传Github

yarn electron:build -p always

完成上面步骤后软件会自动上传打包后的文件到release,然后编辑下release就可以直接发布了,软件是基于版本号更新的,所以记得一定要改版本号

从零开始-结束

作为程序猿最开心的事莫过于得到女朋友的夸奖,虽然这是一个小程序,实现难度也不高,但是最后做出最小可用的版本呈现在女朋友面前的时候,看到女盆友感动的眼神,我想,这应该是我作为程序猿唯一感到欣慰的时候。软件还有很多可以改进的地方,源码在此,戳这里Github

到此这篇关于Electron+vue从零开始打造一个本地播放器的方法示例的文章就介绍到这了,更多相关Electron+vue本地播放器内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
IE6/IE7中JavaScript json提示缺少标识符、字符串或数字问题处理
Dec 16 Javascript
分享9点个人认为比较重要的javascript 编程技巧
Apr 27 Javascript
下雪了 javascript实现雪花飞舞
Aug 02 Javascript
基于bootstrap插件实现autocomplete自动完成表单
May 07 Javascript
jQuery实现文字自动横移
Jan 08 Javascript
jQuery滚动插件scrollable.js用法分析
May 25 jQuery
开发Vue树形组件的示例代码
Dec 21 Javascript
js生成word中图片处理方法
Jan 06 Javascript
JS设计模式之访问者模式定义与用法分析
Feb 05 Javascript
JS 实现分页打印功能
May 16 Javascript
关于Vue Router中路由守卫的应用及在全局导航守卫中检查元字段的方法
Dec 09 Javascript
vue2.x数组劫持原理的实现
Apr 19 Javascript
解决vue数据不实时更新的问题(数据更改了,但数据不实时更新)
Oct 27 #Javascript
js实现自定义滚动条的示例
Oct 27 #Javascript
vue移动端下拉刷新和上滑加载
Oct 27 #Javascript
Element-UI 使用el-row 分栏布局的教程
Oct 26 #Javascript
解决vue项目运行npm run serve报错的问题
Oct 26 #Javascript
js实现简易拖拽的示例
Oct 26 #Javascript
js实现限定范围拖拽的示例
Oct 26 #Javascript
You might like
xml在joomla表单中的应用详解分享
2012/07/19 PHP
PHP数据类型之布尔型的介绍
2013/04/28 PHP
php smarty truncate UTF8乱码问题解决办法
2014/06/13 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
2017/08/01 PHP
JavaScript 继承详解(三)
2009/07/13 Javascript
Javascript alert消息换行的方法
2013/08/07 Javascript
jquery实现从数组移除指定的值
2015/06/24 Javascript
jQuery使用$.each遍历json数组的简单实现方法
2016/04/18 Javascript
js实现页面跳转的几种方法小结
2016/05/16 Javascript
jquery输入数字随机抽奖特效的简单实现代码
2016/06/10 Javascript
VC调用javascript的几种方法(推荐)
2016/08/09 Javascript
angularjs实现下拉列表的选中事件示例
2017/03/03 Javascript
详解element-ui中form验证杂记
2019/03/04 Javascript
微信小程序解析富文本过程详解
2019/07/13 Javascript
Layui 数据表格批量删除和多条件搜索的实例
2019/09/04 Javascript
微信小程序本地存储实现每日签到、连续签到功能
2019/10/09 Javascript
es6中new.target的作用和使用场景简单示例分析
2020/03/14 Javascript
vue开发移动端底部导航条功能
2020/04/08 Javascript
利用PHP实现递归删除链表元素的方法示例
2020/10/23 Javascript
[01:12]快闪回顾DOTA2亚洲邀请赛(DAC) 静候2018新征程开启
2018/03/11 DOTA
[51:06]DOTA2-DPC中国联赛 正赛 Elephant vs Aster BO3 第二场 1月26日
2021/03/11 DOTA
python使用PyV8执行javascript代码示例分享
2013/12/04 Python
在Django的URLconf中使用命名组的方法
2015/07/18 Python
Python随机生成均匀分布在单位圆内的点代码示例
2017/11/13 Python
Python实现查询某个目录下修改时间最新的文件示例
2018/08/29 Python
解决python super()调用多重继承函数的问题
2019/06/26 Python
python的常见矩阵运算(小结)
2019/08/07 Python
Django自关联实现多级联动查询实例
2020/05/19 Python
移动端html5 meta标签的神奇功效
2016/01/06 HTML / CSS
信号量和自旋锁的区别?如何选择使用?
2015/09/08 面试题
七一党日活动总结
2014/07/08 职场文书
上课不认真检讨书
2014/09/17 职场文书
学生夜不归宿检讨书
2014/09/23 职场文书
机关作风建设工作总结
2014/10/23 职场文书
2016继续教育研修日志
2015/11/13 职场文书
如何用PHP websocket实现网页实时聊天
2021/05/26 PHP