△ 是新朋友嗎?記得先點web前端學習圈關注我哦~
爲何要作?
女友工做是音頻後期,日常會收集一些音頻音樂,須要看音頻的頻譜波形,每次用au這種大型軟件播放音樂看波形,很不方便,看到她這麼辛苦,身爲程序猿的我痛心疾首,因而,就有了這麼一個小軟件,軟件涉及到的技術主要爲electron,vue,node,波形的展現主要經過wavesurfer生成。前端
從零開始-搭建項目
項目經過vue腳手架搭建的,因此須要安裝cli工具,若是已經裝了,能夠跳過這一步.vue
npm install -g @vue/cli
# OR
yarn global add @vue/cli
裝好後,經過腳手架搭建項目node
vue create music
vue須要與electron集成,這裏社區已經有比較成熟的vue插件了,Vue CLI Plugin Electron Builder。git
vue add electron-builder
懶人能夠直接去clone個人搭建好得架子直接開發, 戳這裏 :https://github.com/Kerinlin/simple-electron-vue-template 。github
從零開始-項目開發
首先先明確下這個播放器的功能需求,主要有這幾個web
-
不添加文件目錄,加載任意的本地文件系統內的音頻文件,直接調用播放器播放 -
前一首後一首功能 -
聲音音量控制 -
自定義軟件窗口
如何關聯播放
如何實現關聯播放?由於對electron
不是很熟,查了好久 electron
的資料,終於找到了配置項,須要配置 fileAssociations
shell
fileAssociations: [
{
ext: ["mp3", "wav", "flac", "ogg", "m4a"],
name: "music",
role: "Editor"
}
],
配置好後,經過 electron
的open-file事件,獲取打開的音頻文件的本地路徑。對於 windows
, 須要經過 process.argv
,來獲取文件路徑。npm
const filePath = process.argv[1];
如何加載本地音頻文件
上一步經過配置拿到文件的本地路徑後,下一步就是經過路徑讀取音頻文件的信息。因爲音頻的插件沒法解析絕對路徑,因此須要經過node的文件系統,經過fs.readFileSync讀取到文件的buffer信息。小程序
let buffer = fs.readFileSync(diskPath); //讀取文件,並將緩存區進行轉換
讀取後須要將buffer轉換成node可讀流segmentfault
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 :https://github.com/Kerinlin/localMusicPlayer
源自:https://segmentfault.com/a/1190000037613988
聲明:文章著做權歸做者全部,若有侵權,請聯繫小編刪除。
本文分享自微信公衆號 - web前端學習圈(web-xxq)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。