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 this 深入理解
Jul 30 Javascript
js/jQuery简单实现选项卡功能
Jan 02 Javascript
对table和ul实现js分页示例分享
Feb 24 Javascript
Jquery图片延迟加载插件jquery.lazyload.js的使用方法
May 21 Javascript
常用的JavaScript WEB操作方法分享
Feb 28 Javascript
jQuery幻灯片带缩略图轮播效果代码分享
Aug 17 Javascript
javascript检查某个元素在数组中的索引值
Mar 30 Javascript
js print打印网页指定区域内容的简单实例
Nov 01 Javascript
微信小程序使用radio显示单选项功能【附源码下载】
Dec 11 Javascript
详解创建自定义的Angular Schematics
Jun 06 Javascript
实现一个 Vue 吸顶锚点组件方法
Jul 10 Javascript
vue-video-player 解决微信自动全屏播放问题(横竖屏导致样式错乱问题)
Feb 25 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
4月1日重磅发布!《星际争霸II》6.0.0版本更新
2020/04/09 星际争霸
ThinkPHP CURD方法之page方法详解
2014/06/18 PHP
PHP使用redis实现统计缓存mysql压力的方法
2015/11/14 PHP
用php定义一个数组最简单的方法
2019/10/04 PHP
js 实现在离开页面时提醒未保存的信息(减少用户重复操作)
2013/01/16 Javascript
javascript中对Attr(dom中属性)的操作示例讲解
2013/12/02 Javascript
深入理解javascript作用域和闭包
2014/09/23 Javascript
JS图片轮播与索引变色功能实例详解
2017/07/06 Javascript
详解vue axios用post提交的数据格式
2018/08/07 Javascript
简单了解vue.js数组的常用操作
2019/06/17 Javascript
Vue页面切换和a链接的本质区别详解
2019/11/12 Javascript
vue.js使用v-model实现父子组件间的双向通信示例
2020/02/05 Javascript
JavaScript实现指定数量的并发限制的示例代码
2020/03/10 Javascript
解决vue单页面 回退页面 keeplive 缓存问题
2020/07/22 Javascript
vue实现移动端H5数字键盘组件使用详解
2020/08/25 Javascript
解决Element中el-date-picker组件不回填的情况
2020/11/07 Javascript
ES5和ES6中类的区别总结
2020/12/21 Javascript
Python图算法实例分析
2016/08/13 Python
python使用两种发邮件的方式smtp和outlook示例
2017/06/02 Python
python 列表递归求和、计数、求最大元素的实例
2018/11/28 Python
Django 请求Request的具体使用方法
2019/11/11 Python
基于python修改srt字幕的时间轴
2020/02/03 Python
Selenium之模拟登录铁路12306的示例代码
2020/07/31 Python
python闭包与引用以及需要注意的陷阱
2020/09/18 Python
值得收藏的HTML5资源(学习html5的朋友可以收藏下)
2010/07/20 HTML / CSS
Edwaybuy西班牙:小米在线商店
2019/12/04 全球购物
请写出 float x 与"零值"比较的 if 语句
2016/01/04 面试题
应届生高等护理求职信
2013/10/12 职场文书
2014广电局实施党的群众路线教育实践活动方案思想汇报
2014/09/22 职场文书
查摆问题整改措施范文
2014/10/11 职场文书
汉字听写大会观后感
2015/06/12 职场文书
会议开幕致辞怎么写
2016/03/03 职场文书
python实现简单聊天功能
2021/07/07 Python
html+css实现环绕倒影加载特效
2021/07/07 HTML / CSS
mysql备份策略的实现(全量备份+增量备份)
2021/07/07 MySQL
Python卷积神经网络图片分类框架详解分析
2021/11/07 Python