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 相关文章推荐
仿迅雷焦点广告效果(JQuery版)
Nov 19 Javascript
Javascript 文件夹选择框的两种解决方案
Jul 01 Javascript
在网页中使用document.write时遭遇的奇怪问题
Aug 24 Javascript
如何确保JavaScript的执行顺序 之jQuery.html并非万能钥匙
Mar 03 Javascript
JavaScript中的acos()方法使用详解
Jun 14 Javascript
JS实现点击按钮获取页面高度的方法
Nov 02 Javascript
Bootstrap选项卡动态切换效果
Nov 28 Javascript
jQuery简单实现遍历单选框的方法
Mar 06 Javascript
Vue 进阶教程之v-model详解
May 06 Javascript
微信小程序判断用户是否需要再次授权获取个人信息
Jul 18 Javascript
使用p5.js临摹动态图形
Oct 23 Javascript
springboot+vue实现文件上传下载
Nov 17 Vue.js
解决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
浅析PHP水印技术
2007/02/14 PHP
基于PHP编程注意事项的小结
2013/04/27 PHP
phpmailer发送gmail邮件实例详解
2013/06/24 PHP
Discuz7.2版的faq.php SQL注入漏洞分析
2014/08/06 PHP
PHP JWT初识及其简单示例
2018/10/10 PHP
jquery复选框CHECKBOX全选、反选
2008/08/30 Javascript
javascript不同页面传值的改进版
2008/09/30 Javascript
js 实现复制到粘贴板的功能代码
2010/05/13 Javascript
javascript获取url上某个参数的方法
2013/11/08 Javascript
jquery制作LED 时钟特效
2015/02/01 Javascript
Javascript中this关键字的一些小知识
2015/03/15 Javascript
js实现select跳转菜单新窗口效果代码分享(超简单)
2015/08/21 Javascript
详解webpack和webpack-simple中如何引入css文件
2017/06/28 Javascript
让你彻底掌握es6 Promise的八段代码
2017/07/26 Javascript
轻松玩转BootstrapTable(后端使用SpringMVC+Hibernate)
2017/09/06 Javascript
vue-cli项目中使用Mockjs详解
2018/05/14 Javascript
JS 实现分页打印功能
2018/05/16 Javascript
30分钟快速实现小程序语音识别功能
2018/11/27 Javascript
JavaScript实现网页tab栏效果制作
2020/11/20 Javascript
Python冒泡排序注意要点实例详解
2016/09/09 Python
完美解决python遍历删除字典里值为空的元素报错问题
2016/09/11 Python
python urllib urlopen()对象方法/代理的补充说明
2017/06/29 Python
python实现简单tftp(基于udp协议)
2018/07/30 Python
如何利用Python分析出微信朋友男女统计图
2019/01/25 Python
python 扩展print打印文件路径和当前时间信息的实例代码
2019/10/11 Python
下载官网python并安装的步骤详解
2019/10/12 Python
python循环嵌套的多种使用方法解析
2019/11/29 Python
Django Form and ModelForm的区别与使用
2019/12/06 Python
Django 允许局域网中的机器访问你的主机操作
2020/05/13 Python
Python环境管理virtualenv&amp;virtualenvwrapper的配置详解
2020/07/01 Python
分享8款纯CSS3实现的搜索框功能
2017/09/14 HTML / CSS
美国新蛋IT数码商城:Newegg.com
2016/07/21 全球购物
学生个人的自我评价分享
2013/11/05 职场文书
城管执法人员个人对照检查材料思想汇报
2014/09/29 职场文书
购房协议书范本
2014/10/02 职场文书
2015年会计人员工作总结
2015/05/22 职场文书