当前位置: 首页 > 后端技术 > Node.js

Electron+vue从头搭建本地播放器

时间:2023-04-04 01:02:06 Node.js

为什么要这么做?我的女朋友在后期音频阶段工作。她平时收藏一些音频音乐,需要看音频的频谱波形。每次都是用au之类的大软件播放音乐看波形,很不方便。看到她这么努力,作为程序员的我心痛不已,所以才有了这么小的软件。软件涉及的技术主要有electron、vue、node,波形显示主要由wavesurfer生成。从零开始——搭建项目项目是通过vue脚手架搭建的,所以需要安装cli工具。如果你已经安装了它,你可以跳过这一步。npminstall-g@vue/cli#ORyarnglobaladd@vue/cli安装后,项目vuecreatemusicvue需要通过脚手架与electron集成。这里的社区已经有了比较成熟的vue插件VueCLIPluginElectronBuilder。vueaddelectron-builder懒人可以直接去clone我建好的架子直接开发,点这里。从零开始——项目开发首先定义了这个播放器的功能需求,主要包括这几个不添加文件目录,加载本地文件系统中的任何音频文件,直接调用播放器播放上一曲和下一曲的功能下一曲音量控制如何自定义软件窗口联动播放如何实现联动播放?因为对electron不是很熟悉,查了半天electron的资料,终于找到配置项,需要配置fileAssociationsfileAssociations:[{ext:["mp3","wav","flac","ogg","m4a"],name:"music",role:"Editor"}],配置后通过electron的open-file事件获取打开音频文件的本地路径。对于windows,需要通过process.argv获取文件路径。constfilePath=process.argv[1];如何加载本地音频文件上一步通过配置获取到文件的本地路径后,接下来就是通过路径读取音频文件的信息了。由于音频插件无法解析绝对路径,需要通过fs.readFileSync通过node的文件系统读取文件的buffer信息。letbuffer=fs.readFileSync(diskPath);//读取文件并转换为缓冲区读取后需要将缓冲区转换为节点可读流conststream=this.bufferToStream(buffer);//将缓冲区数据转换为节点可读流转换方法bufferToStreambufferToStream(binary){constreadableInstanceStream=newReadable({read(){this.push(binary);this.push(null);}});返回可读实例流;}转为流后音频流需要转为blob对象进行加载,实现方法module.exports=streamToBlobfunctionstreamToBlob(stream,mimeType){if(mimeType!=null&&typeofmimeType!=='string'){thrownewError('无效的mimetype,预期的字符串。')}returnnewPromise((resolve,reject)=>{constchunks=[]stream.on('data',chunk=>chunks.push(chunk)).once('end',()=>{constblob=mimeType!=null?newBlob(chunks,{type:mimeType}):newBlob(chunks)resolve(blob)}).once('error',reject)})}turnblobletfileUrl;//blob对象流mToBlob(stream).then(res=>{fileUrl=res;//console.log(fileUrl);//将blob对象转换为blob链接letfilePath=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父目录constdirPath=path.dirname(diskPath);然后通过fs.readdir读取该目录下的所有文件,并返回一个文件名数组,找到该目录下正在播放的文件的下标,判断上一首和上一首歌曲的名称,然后拼装成绝对路径,读取资源播放playFileList(diskPath,pos){letisInFiles;让文件索引;让preIndex;让下一个索引;让全路径;让dirPath=path.dirname(diskPath);让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);}});},soundvolumecontrol音量控制需要监听inputtyping事件,获取范围value,然后通过设置stylebackground-image动态计算百分比,然后调用wavesurfer的setVolume方法调整音量:style="`background-image:linear-gradient(toright,${fillColor},${fillColor}${percent},${emptyColor}${percent})`"改变体积changeVoleventchangeVol(e){letval=e.target.value;让min=e.target.min;让max=e.target.max;让速率=(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("最小化");}主进程ipcMain.on("close",()=>{win.close();app.quit();});ipcMain.on("minimize",()=>{win.minimize();});实际测试中会出现打开多个实例的问题,打开一首新音乐时,会重新打开一个实例,无法实现叠加播放。查看资料后发现electron有一个二实例事件,可以监听二实例是否开启。当第二个实例执行并调用app.requestSingleInstanceLock()")时,该事件会在应用的第一个实例中被触发,并会返回第二个实例的相关信息,然后通过主进程通知渲染进程,通知渲染进程第二个实例的本地绝对路径,渲染进程收到信息后,立即加载第二个实例的资源,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)=>{constnewOriginPath=arg;//console.log(newOriginPath);this.loadMusic(newOriginPath);});要求自动更新的原因是,当我很兴奋的把成品给女朋友的时候,被女朋友试了很多bug(捂脸),然后频繁的修改包,和然后通过私人邮件发送给她。很麻烦,所以这个需求很迫切。最后查了资料,通过electron-updater实现了这个需求。安装electron-updateryarn添加electron-updaterpublishsettingselectronBuilder:{builderOptions:{publish:['github']}}主进程监听autoUpdater.on("checking-for-update",()=>{});autoUpdater.on("update-available",info=>{dialog.showMessageBox({title:"新版本发布",message:"有新内容更新,稍后更新为您重新安装",buttons:["OK"],type:"info",noLink:true});});autoUpdater.on("update-downloaded",info=>{autoUpdater.quitAndInstall();});GenerateGithubAccessToken因为使用了github作为更新站,所以本地需要相应的操作权限,到这里生成token,点击这里,生成后设置[Environment]::SetEnvironmentVariable("GH_TOKEN","","User")#为例如,[Environment]::SetEnvironmentVariable("GH_TOKEN","sdfdsfgsdg14463232","User")打包上传Githubyarnelectron:build-palways完成以上步骤后,软件会自动将打包后的文件上传到release,然后编辑发布直接发布。软件是根据版本号更新的,所以记得把版本号从零改到末尾。程序员最开心的事情就是被女朋友表扬,虽然这是一个小程序,实现起来也不难,但是当我终于做出最小的可用版本呈现在女朋友面前的时候,我看到了女朋友动人的眼神。我想这应该是我作为程序员唯一感到欣慰的事情了。什么时候。软件还有很多可以改进的地方,源码在这里,点这里Github