Electron+vue從零開(kāi)始打造一個(gè)本地播放器的方法示例
為什么要做?
女朋友工作是音頻后期,平常會(huì)收集一些音頻音樂(lè),需要看音頻的頻譜波形,每次用au這種大型軟件播放音樂(lè)看波形,很不方便,看到她這么辛苦,身為程序猿的我痛心疾首,于是,就有了這么一個(gè)小軟件,軟件涉及到的技術(shù)主要為electron,vue,node,波形的展示主要通過(guò)wavesurfer生成。
從零開(kāi)始-搭建項(xiàng)目
項(xiàng)目通過(guò)vue腳手架搭建的,所以需要安裝cli工具,如果已經(jīng)裝了,可以跳過(guò)這一步.
npm install -g @vue/cli # OR yarn global add @vue/cli
裝好后,通過(guò)腳手架搭建項(xiàng)目
vue create music
vue需要與electron集成,這里社區(qū)已經(jīng)有比較成熟的vue插件了,Vue CLI Plugin Electron Builder。
vue add electron-builder
懶人可以直接去clone我的搭建好得架子直接開(kāi)發(fā), 戳這里。
從零開(kāi)始-項(xiàng)目開(kāi)發(fā)
首先先明確下這個(gè)播放器的功能需求,主要有這幾個(gè)
- 不添加文件目錄,加載任意的本地文件系統(tǒng)內(nèi)的音頻文件,直接調(diào)用播放器播放
- 前一首后一首功能
- 聲音音量控制
- 自定義軟件窗口
如何關(guān)聯(lián)播放
如何實(shí)現(xiàn)關(guān)聯(lián)播放?因?yàn)閷?duì)electron不是很熟,查了很久electron的資料,終于找到了配置項(xiàng),需要配置fileAssociations
fileAssociations: [
{
ext: ["mp3", "wav", "flac", "ogg", "m4a"],
name: "music",
role: "Editor"
}
],
配置好后,通過(guò)electron的open-file事件,獲取打開(kāi)的音頻文件的本地路徑。對(duì)于windows,需要通過(guò)process.argv,來(lái)獲取文件路徑。
const filePath = process.argv[1];
如何加載本地音頻文件
上一步通過(guò)配置拿到文件的本地路徑后,下一步就是通過(guò)路徑讀取音頻文件的信息。由于音頻的插件無(wú)法解析絕對(duì)路徑,所以需要通過(guò)node的文件系統(tǒng),通過(guò)fs.readFileSync讀取到文件的buffer信息。
let buffer = fs.readFileSync(diskPath); //讀取文件,并將緩存區(qū)進(jìn)行轉(zhuǎn)換
讀取后需要將buffer轉(zhuǎn)換成node可讀流
const stream = this.bufferToStream(buffer);//將buffer數(shù)據(jù)轉(zhuǎn)換成node 可讀流
轉(zhuǎn)換方法 bufferToStream
bufferToStream(binary) {
const readableInstanceStream = new Readable({
read() {
this.push(binary);
this.push(null);
}
});
return readableInstanceStream;
}
轉(zhuǎn)換成流后需要將音頻流轉(zhuǎn)換成blob對(duì)象來(lái)加載,實(shí)現(xiàn)方法
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)
})
}
轉(zhuǎn)blob
let fileUrl; // blob對(duì)象
streamToBlob(stream)
.then(res => {
fileUrl = res;
// console.log(fileUrl);
//將blob對(duì)象轉(zhuǎn)成blob鏈接
let filePath = window.URL.createObjectURL(fileUrl);
// console.log(filePath);
this.wavesurfer.load(filePath);
// 自動(dòng)播放
this.wavesurfer.play();
this.playing = true;
})
.catch(err => {
console.log(err);
});
這樣就實(shí)現(xiàn)了加載本地文件播放了
上一首下一首功能
這里的上一首下一首的功能是基于上面獲取到的文件的絕對(duì)路徑,通過(guò)node的path模塊,path.dirname獲取到文件的父級(jí)目錄。
const dirPath = path.dirname(diskPath);
然后通過(guò)fs.readdir讀取目錄下所有文件,會(huì)返回一個(gè)文件名數(shù)組,找到該目錄下正在播放的文件的下標(biāo),通過(guò)數(shù)組下標(biāo)判斷前一首和后一首歌曲的名稱,然后再組裝成絕對(duì)路徑,讀取資源播放
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);
}
});
},
聲音音量控制
音量控制需要通過(guò)監(jiān)聽(tīng)input的鍵入事件,獲取到range的值,然后通過(guò)設(shè)置樣式background-image,動(dòng)態(tài)計(jì)算百分比,然后調(diào)用wavesurfer的setVolume方法調(diào)節(jié)音量
: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));
},
自定義標(biāo)題欄
個(gè)人覺(jué)得系統(tǒng)自帶的菜單欄太丑了,就給設(shè)置了無(wú)邊框再自己加上最小化,關(guān)閉的功能。最小化,關(guān)閉是通過(guò)ipc通信,渲染進(jìn)程監(jiān)聽(tīng)到有點(diǎn)擊操作后,通知主進(jìn)程進(jìn)行相應(yīng)的操作。
渲染進(jìn)程
close() {
ipcRenderer.send("close");
},
minimize() {
ipcRenderer.send("minimize");
}
主進(jìn)程
ipcMain.on("close", () => {
win.close();
app.quit();
});
ipcMain.on("minimize", () => {
win.minimize();
});
打開(kāi)多個(gè)實(shí)例的問(wèn)題
在實(shí)際測(cè)試的過(guò)程中發(fā)現(xiàn)會(huì)出現(xiàn),打開(kāi)一首新的音樂(lè)播放,就會(huì)出現(xiàn)重新開(kāi)一個(gè)實(shí)例的現(xiàn)象,不能實(shí)現(xiàn)覆蓋播放,后面查閱資料發(fā)現(xiàn)electron有一個(gè)second-instance事件,可以監(jiān)聽(tīng)是否打開(kāi)了第二個(gè)實(shí)例。當(dāng)?shù)诙€(gè)實(shí)例被執(zhí)行并且調(diào)用 時(shí),這個(gè)事件將在應(yīng)用程序的首個(gè)實(shí)例中觸發(fā),并且會(huì)返回第二個(gè)實(shí)例的相關(guān)信息,然后通過(guò)主進(jìn)程通知渲染進(jìn)程,告知渲染進(jìn)程第二個(gè)實(shí)例的本地絕對(duì)路徑,渲染進(jìn)程接收到信息后,立馬加載第二個(gè)實(shí)例的資源。app.requestSingleInstanceLock(),表示應(yīng)用程序?qū)嵗欠癯晒θ〉昧随i。 如果它取得鎖失敗,可以假設(shè)另一個(gè)應(yīng)用實(shí)例已經(jīng)取得了鎖并且仍舊在運(yùn)行,所以可以直接關(guān)閉掉,這樣就避免了打開(kāi)多個(gè)實(shí)例的問(wèn)題
主進(jìn)程
const gotTheLock = app.requestSingleInstanceLock();
if (gotTheLock) {
app.on("second-instance", (event, commandLine, workingDirectory) => {
// 監(jiān)聽(tīng)是否有第二個(gè)實(shí)例,向渲染進(jìn)程發(fā)送第二個(gè)實(shí)例的本地路徑
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();
}
渲染進(jìn)程
ipcRenderer.on("path", (event, arg) => {
const newOriginPath = arg;
// console.log(newOriginPath);
this.loadMusic(newOriginPath);
});
自動(dòng)更新
需求的起因是,在很興奮的給女朋友成品的時(shí)候,尷尬的被女朋友試出很多bug(捂臉ing),然后頻繁的修改打包,然后通過(guò)私發(fā)傳給她。特別麻煩,所以這個(gè)需求很急迫。最后查了資料,通過(guò)electron-updater實(shí)現(xiàn)了這個(gè)需求.
安裝electron-updater
yarn add electron-updater
發(fā)布設(shè)置
electronBuilder: {
builderOptions: {
publish: ['github']
}
}
主進(jìn)程監(jiān)聽(tīng)
autoUpdater.on("checking-for-update", () => {});
autoUpdater.on("update-available", info => {
dialog.showMessageBox({
title: "新版本發(fā)布",
message: "有新內(nèi)容更新,稍后將重新為您安裝",
buttons: ["確定"],
type: "info",
noLink: true
});
});
autoUpdater.on("update-downloaded", info => {
autoUpdater.quitAndInstall();
});
生成Github Access Token
因?yàn)槭怯胓ithub作為更新站,所以本地需要相應(yīng)的操作權(quán)限,去這里生成token,戳這,生成后,在powershell中設(shè)置
[Environment]::SetEnvironmentVariable("GH_TOKEN","<YOUR_TOKEN_HERE>","User")
# 例如 [Environment]::SetEnvironmentVariable("GH_TOKEN","sdfdsfgsdg14463232","User")
打包上傳Github
yarn electron:build -p always
完成上面步驟后軟件會(huì)自動(dòng)上傳打包后的文件到release,然后編輯下release就可以直接發(fā)布了,軟件是基于版本號(hào)更新的,所以記得一定要改版本號(hào)
從零開(kāi)始-結(jié)束
作為程序猿最開(kāi)心的事莫過(guò)于得到女朋友的夸獎(jiǎng),雖然這是一個(gè)小程序,實(shí)現(xiàn)難度也不高,但是最后做出最小可用的版本呈現(xiàn)在女朋友面前的時(shí)候,看到女盆友感動(dòng)的眼神,我想,這應(yīng)該是我作為程序猿唯一感到欣慰的時(shí)候。軟件還有很多可以改進(jìn)的地方,源碼在此,戳這里Github
到此這篇關(guān)于Electron+vue從零開(kāi)始打造一個(gè)本地播放器的方法示例的文章就介紹到這了,更多相關(guān)Electron+vue本地播放器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3.x中useRouter()執(zhí)行后返回值是undefined問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于vue3.x中useRouter()執(zhí)行后返回值是undefined問(wèn)題的解決方法,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用vue3.x具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
詳解Vue2+Echarts實(shí)現(xiàn)多種圖表數(shù)據(jù)可視化Dashboard(附源碼)
本篇文章主要介紹了詳解Vue2+Echarts實(shí)現(xiàn)多種圖表數(shù)據(jù)可視化Dashboard(附源碼),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
詳解基于vue-cli配置移動(dòng)端自適應(yīng)
本篇文章主要介紹了詳解基于vue-cli配置移動(dòng)端自適應(yīng),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Vue實(shí)現(xiàn)Tab選項(xiàng)卡切換
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)Tab選項(xiàng)卡切換,點(diǎn)擊不同標(biāo)題顯示對(duì)應(yīng)圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Vue.js頁(yè)面驗(yàn)證跳轉(zhuǎn)功能實(shí)現(xiàn)
這篇文章主要介紹了Vue.js頁(yè)面驗(yàn)證跳轉(zhuǎn)功能實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-04-04
vue3實(shí)現(xiàn)數(shù)字滾動(dòng)特效實(shí)例詳解
這篇文章主要為大家介紹了vue3實(shí)現(xiàn)數(shù)字滾動(dòng)特效實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

