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 相关文章推荐
Javascript代码混淆综合解决方案-Javascript在线混淆器
Dec 18 Javascript
js 加载时自动调整图片大小
May 28 Javascript
IE8 浏览器Cookie的处理
Jan 31 Javascript
jQuery创建自己的插件(自定义插件)的方法
Jun 10 Javascript
jQuery实现信息提示框(带有圆角框与动画)效果
Aug 07 Javascript
jQuery的remove()方法使用详解
Aug 11 Javascript
全面介绍javascript实用技巧及单竖杠
Jul 18 Javascript
使用jquery/js获取iframe父子级、同级获取元素的方法
Aug 05 Javascript
最全面的百度地图JavaScript离线版开发
Sep 10 Javascript
javascript网页随机点名实现过程解析
Oct 15 Javascript
2分钟实现一个Vue实时直播系统的示例代码
Jun 05 Javascript
Vue+Element UI 树形控件整合下拉功能菜单(tree + dropdown +input)
Aug 28 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
Linux下ZendOptimizer的安装与配置方法
2007/04/12 PHP
jquery 图片轮换效果
2010/07/29 Javascript
jQuery Tools tooltip使用说明
2012/07/14 Javascript
浅析JavaScript中的delete运算符
2013/11/30 Javascript
js跨域访问示例(客户端/服务端)
2014/05/19 Javascript
js简单网速测试方法完整实例
2015/12/15 Javascript
BootStrap智能表单实战系列(六)表单编辑页面的数据绑定
2016/06/13 Javascript
JavaScript基于原型链的继承
2016/06/22 Javascript
JS图片等比例缩放方法完整示例
2016/08/03 Javascript
什么是JavaScript中的结果值?
2016/10/08 Javascript
利用JS判断鼠标移入元素的方向
2016/12/11 Javascript
jQuery插件zTree实现删除树子节点的方法示例
2017/03/08 Javascript
jQuery表格(Table)基本操作实例分析
2017/03/10 Javascript
Vue项目中使用Vux的安装过程
2018/05/01 Javascript
javascript对HTML字符转义与反转义
2018/12/13 Javascript
layui 选择列表,打勾,点击确定返回数据的例子
2019/09/02 Javascript
JS async 函数的含义和用法实例总结
2020/04/08 Javascript
javascript设计模式 ? 职责链模式原理与用法实例分析
2020/04/16 Javascript
Python进程间通信用法实例
2015/06/04 Python
Python 中的with关键字使用详解
2016/09/11 Python
浅谈django model的get和filter方法的区别(必看篇)
2017/05/23 Python
python实现堆和索引堆的代码示例
2018/03/19 Python
基于numpy.random.randn()与rand()的区别详解
2018/04/17 Python
sublime python3 输入换行不结束的方法
2018/04/19 Python
linux安装Python3.4.2的操作方法
2018/09/28 Python
Python OpenCV中的resize()函数的使用
2019/06/20 Python
Python logging日志库空间不足问题解决
2020/09/14 Python
css3中flex布局宽度不生效的解决
2020/12/09 HTML / CSS
荷兰网上药店:Drogisterij.net
2019/09/03 全球购物
薪酬专员岗位职责
2014/02/18 职场文书
销售员试用期自我评价
2014/09/15 职场文书
干部四风问题整改措施思想汇报
2014/10/13 职场文书
单位租房协议范本
2014/12/03 职场文书
丧事答谢词大全
2015/09/30 职场文书
Javascript 解构赋值详情
2021/11/17 Javascript
Java 轮询锁使用时遇到问题
2022/05/11 Java/Android