基于electron的音視頻播放器
前言
我是一個(gè)前端工程師,前一段時(shí)間想著搞一個(gè)屬于自己的作品,所以就突發(fā)奇想搞了一個(gè)基于electron的音視頻播放器桌面應(yīng)用程序。經(jīng)過幾個(gè)月的開發(fā),終于實(shí)現(xiàn)了大部分的功能,所以我想在這里總結(jié)一下前面一段時(shí)間的工作,以及在開發(fā)的時(shí)候遇見的各種坑坑洼洼,希望可以對(duì)想要從事electron桌面軟件開發(fā)的朋友有點(diǎn)幫助吧。
選擇做一個(gè)音視頻播放器桌面應(yīng)用程序原因
一開始我是打算做個(gè)網(wǎng)站或者webAPP那種的,但是作為一名前端工程師來說,網(wǎng)站或者webAPP并沒有什么可以吸引我的地方,因?yàn)槟欠N東西都是司空見慣的,千篇一律,所以我就想著使用electron做一個(gè)音視頻播放器,搞點(diǎn)與眾不同的東西。雖然網(wǎng)上有很多視頻播放器,但是那些播放器基本都是使用C寫出來的,并沒有使用electron做出來的播放器。
技術(shù)的選型
我主要使用的技術(shù)是electron、node、vue、express、HTML5相關(guān)技術(shù)、DPlayer。Electron主要是用來構(gòu)建音視頻播放器所需要的環(huán)境,提供訪問系統(tǒng)資源的api(調(diào)用資源管理器,瀏覽器等等)以及打包成桌面應(yīng)用程序。其實(shí)說白了electron就是相當(dāng)于一個(gè)瀏覽器和服務(wù)器后臺(tái)的結(jié)合,但是electron打破了傳統(tǒng)瀏覽器的界限,提供了調(diào)用系統(tǒng)底層資源的api,使得開發(fā)者可以使用系統(tǒng)的資源,比如攝像頭,麥克風(fēng)等等。同時(shí)開發(fā)者還可以在electron中調(diào)用node的模塊,搭建一個(gè)后臺(tái)等等。electron有2種進(jìn)程,一種是渲染進(jìn)程,另一種是主進(jìn)程。主進(jìn)程只能有一個(gè),負(fù)責(zé)調(diào)用系統(tǒng)底層資源,管理窗口那些。渲染進(jìn)程可以有多條,負(fù)責(zé)渲染頁面的。Node主要使用了fs和path這2個(gè)模塊,因?yàn)檫@個(gè)音視頻播放器涉及到了的文件讀取操作,所以這2個(gè)模塊是必不可少的。Vue是負(fù)責(zé)構(gòu)建界面的。Express是用來在應(yīng)用程序中構(gòu)建一個(gè)微型后臺(tái),負(fù)責(zé)把視頻讀取出來變成流的形式,然后返回給前端界面。html5主要使用了拖拽api、全屏api、Notification消息通知等技術(shù)。DPlayer是整個(gè)音視頻播放器的核心組件,負(fù)責(zé)播放音視頻的。
已經(jīng)實(shí)現(xiàn)了的功能
- 視頻播放:目前已經(jīng)支持大多數(shù)視頻格式,比如 MP4、WebM、mkv、avi、WMV、FLV、rmvb 等,后續(xù)會(huì)添加更多的視頻格式
- 音頻播放:目前已經(jīng)支持大多是音頻格式,比如 MP3 等,后續(xù)會(huì)添加更多的音頻格式
- 換膚功能:該功能類似其他軟件的換膚功能,用戶可以根據(jù)自己的喜好選擇不同的主題皮膚
- 歷史記錄:音視頻播放器會(huì)自動(dòng)記錄用戶播放已經(jīng)過的的視頻或音頻,比如音頻或視頻播放到那個(gè)時(shí)間
- 記憶功能:音視頻播放器會(huì)自動(dòng)保存用戶的操作和修改的配置,比如用戶更換了主題皮膚,用戶關(guān)閉了應(yīng)用后再次打開,音視頻播放器會(huì)應(yīng)用用戶已經(jīng)修改的主題皮膚。用戶對(duì)視頻或音頻進(jìn)行加速等操作都會(huì)被記憶下來,用戶再次點(diǎn)擊該視頻或音頻就會(huì)恢復(fù)用戶的操作
- 播放模式:播放模式主要有5種,分別是 單個(gè)播放、單個(gè)循環(huán)、循環(huán)播放列表、順序播放、隨機(jī)播放
- 排序模式:排序模式主要有5種,分別是 默認(rèn)排序、大小排序、時(shí)間排序、隨機(jī)排序、名稱排序
- 置頂功能:保持應(yīng)用界面始終在最頂端
- 加減速功能:音視頻加速或者減速播放
- 拖拽文件或文件夾:用戶可以把文件或者文件夾拖拽進(jìn)音視頻播放器中,應(yīng)用會(huì)過濾掉不能播放的文件
- 全屏功能:實(shí)現(xiàn)了應(yīng)用的全屏功能,這里是使用了electron提供的全屏api,沒有使用html5的全屏api
- 右鍵菜單功能:目前已經(jīng)實(shí)現(xiàn)了大多數(shù)右鍵菜單的功能,沒實(shí)現(xiàn)的后續(xù)實(shí)現(xiàn)
音視頻播放實(shí)現(xiàn)
音視頻播放實(shí)現(xiàn)。一開始我是想著直接使用HTML5提供的標(biāo)簽,但是這個(gè)標(biāo)簽局限性很大,它只支持三種視頻格式:MP4、WebM、Ogg,但是目前主流視頻格式還有avi、mkv、wmv等視頻格式。然后我就想著對(duì)那些不是MP4、WebM、Ogg的視頻格式進(jìn)行轉(zhuǎn)碼,但是需要使用ffmepg來進(jìn)行轉(zhuǎn)碼,electron進(jìn)行打包的時(shí)候是不會(huì)把ffmepg這個(gè)工具打包進(jìn)去的,所以這就要求每一個(gè)使用這個(gè)音視頻播放器的用戶需要自己去手動(dòng)安裝ffmepg和配置環(huán)境,這種做法顯然是不行的。同時(shí)轉(zhuǎn)碼的過程是需要時(shí)間,一旦遇見那些幾個(gè)G是視頻,起碼要花費(fèi)幾分鐘進(jìn)行轉(zhuǎn)碼,然后才能響應(yīng)用戶的操作,這對(duì)于用戶來說是極其差的用戶體驗(yàn)。最后我選擇了使用express在electron中搭建一個(gè)微型服務(wù)器,當(dāng)express接收到前端界面的請(qǐng)求時(shí),就把所需要的視頻讀取出來,以流的形式返回給前端,因?yàn)閷?shí)在electron環(huán)境下,所以使用的是localhost,這樣就可以快速的響應(yīng)用戶的操作,逼近原生播放器的體驗(yàn)。
代碼:
let pathSrc = req.query.video; let stat = fs.statSync(pathSrc); let fileSize = stat.size; let range = req.headers.range; if (range) { //有range頭才使用206狀態(tài)碼 let parts = range.replace(/bytes=/, "").split("-"); let start = parseInt(parts[0], 10); let end = parts[1] ? parseInt(parts[1], 10) : start + 999999; // end 在最后取值為 fileSize - 1 end = end > fileSize - 1 ? fileSize - 1 : end; let chunksize = (end - start) + 1; let file = fs.createReadStream(pathSrc, { start, end }); let head = { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4', }; res.writeHead(206, head); file.pipe(res); } else { let head = { 'Content-Length': fileSize, 'Content-Type': 'video/mp4', }; res.writeHead(200, head); fs.createReadStream(pathSrc).pipe(res); }
右鍵菜單實(shí)現(xiàn)
右鍵菜單我一開始的做法是監(jiān)聽右鍵事件,通過動(dòng)態(tài)生成DOM,然后插入到頁面中。但是這種做法并不可行。因?yàn)樯傻挠益I菜單需要出現(xiàn)在用戶鼠標(biāo)點(diǎn)擊的位置附近,用戶鼠標(biāo)出現(xiàn)的位置可能是應(yīng)用程序中間,可能是左上角,右上角等等。因?yàn)槭鞘褂肈OM生成,渲染出來的右鍵菜單不能超出文檔的范圍,否則就會(huì)出現(xiàn)滾動(dòng)條。所以當(dāng)用戶的鼠標(biāo)位置在界面邊界的時(shí)候,需要計(jì)算出右鍵菜單應(yīng)該出現(xiàn)在鼠標(biāo)所在位置的上面、下面或者左上角等等,這需要經(jīng)過一系列大量的計(jì)算才能得出結(jié)果,這在electron的渲染進(jìn)程顯然是不可行,因?yàn)檫@么復(fù)雜的計(jì)算可能會(huì)造成頁面卡頓。所以后面我是用electron的Menu模塊,在主進(jìn)程中生成右鍵菜單,減輕渲染進(jìn)程的負(fù)擔(dān),同是還減少了大量的DOM操作,但是是用electron的Menu模塊生成的右鍵菜單就是白底黑字,樣式可能沒有符合預(yù)期的效果。但是通過2種生成右鍵菜單的利益權(quán)衡后,采用electron的Menu模塊生成右鍵菜單才是最佳的選擇。
代碼
let contextMenuTemplate = [ { label: "播放順序", submenu: [ { label: this.playMode == 1 ? "√ 單個(gè)播放" : " 單個(gè)播放", click: () => { this.setPlayMode(1); } }, { label: this.playMode == 2 ? "√ 單個(gè)循環(huán)" : " 單個(gè)循環(huán)", click: () => { this.setPlayMode(2); } }, { label: this.playMode == 3 ? "√ 循環(huán)列表" : " 循環(huán)列表", click: () => { this.setPlayMode(3); } }, { label: this.playMode == 4 ? "√ 順序播放" : " 順序播放", click: () => { this.setPlayMode(4); } }, { label: this.playMode == 5 ? "√ 隨機(jī)播放" : " 隨機(jī)播放", click: () => { this.setPlayMode(5); } } ] }, { type: "separator" }, { label: "聲音", submenu: [ { label: this.volumePercent == 0.1?"√ 10%":" 10%", click:()=>{ let inWidth = 0.1*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.2?"√ 20%":" 20%", click:()=>{ let inWidth = 0.2*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.3?"√ 30%":" 30%", click:()=>{ let inWidth = 0.3*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.4?"√ 40%":" 40%", click:()=>{ let inWidth = 0.4*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.5?"√ 50%":" 50%", click:()=>{ let inWidth = 0.5*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.6?"√ 60%":" 60%", click:()=>{ let inWidth = 0.6*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.7?"√ 70%":" 70%", click:()=>{ let inWidth = 0.7*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.8?"√ 80%":" 80%", click:()=>{ let inWidth = 0.8*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 0.9?"√ 90%":" 90%", click:()=>{ let inWidth = 0.9*62 this.setInWidth(inWidth) } }, { label: this.volumePercent == 1?"√ 100%":" 100%", click:()=>{ let inWidth = 1*62 this.setInWidth(inWidth) } }, { label: (this.volumePercent != 0.1&&this.volumePercent != 0.2&&this.volumePercent != 0.3&&this.volumePercent != 0.4&&this.volumePercent != 0.5&&this.volumePercent != 0.6&&this.volumePercent != 0.7&&this.volumePercent != 0.8&&this.volumePercent != 0.9&&this.volumePercent != 1&&this.volumePercent != 0)?`√ 其他(${Math.round(this.volumePercent*100)}%)`:" 其他", }, { label: this.volumePercent == 0?"√ 靜音":" 靜音", click:()=>{ let inWidth = 0 this.setInWidth(inWidth) } } ] }, { type: "separator" }, { label: "設(shè)置" } ]; if (this.currentVideo) { let addMenu = [ { label: this.isPlaying ? "暫停" : "播放", click: () => { this.setPlaying(!this.isPlaying); } }, { type: "separator" } ]; contextMenuTemplate.unshift(...addMenu); contextMenuTemplate.splice(4, 0, { label: this.isFullScreen ? "退出全屏" : "全屏", click: () => { this.setFullScreen(!this.isFullScreen); } }); contextMenuTemplate.push({ label:'文件信息', click:()=>{ this.videoInfo = this.currentVideo this.isShowInfo = true } }) } let m = Menu.buildFromTemplate(contextMenuTemplate); Menu.setApplicationMenu(m); m.popup({ window: remote.getCurrentWindow() });
總結(jié)
為什么直說音視頻播放和右鍵菜單實(shí)現(xiàn)?因?yàn)檫@2個(gè)功能是我重寫的次數(shù)最多的功能,特別是音視頻播放這個(gè)功能,我還寫了很多demo去測試不同的播放方法,測試不同播放方法的性能問題,最終才選擇了搭建一個(gè)微型服務(wù)器這個(gè)方法。其他的功能沒什么需要特別講解的地方,其他功能都是細(xì)節(jié)問題,同住還要注意封裝公共代碼,降低耦合度,分模塊,分功能去編寫代碼。因?yàn)橐婚_始我并沒有注意到這些地方,寫到后面代碼越來越多,出現(xiàn)問題的時(shí)候都無從下手,不知道改哪里,這使得我花費(fèi)了大量的時(shí)間對(duì)代碼進(jìn)行重構(gòu),整理。
效果圖
效果圖1
效果圖2
效果圖3
效果圖4
效果圖5
效果圖6
效果圖7
最后如果大家覺得我這個(gè)音視頻播放器還可以的話,歡迎去我的github:
https://github.com/c10342/player 給個(gè)star
到此這篇關(guān)于基于electron的音視頻播放器的文章就介紹到這了,更多相關(guān)electron音視頻播放器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
isArray()函數(shù)(JavaScript中對(duì)象類型判斷的幾種方法)
我們知道,JavaScript中檢測對(duì)象類型的運(yùn)算符有:typeof、instanceof,還有對(duì)象的constructor屬性2009-11-11javascript將非數(shù)值轉(zhuǎn)換為數(shù)值
parseInt()不能轉(zhuǎn)換浮點(diǎn)型數(shù)值,我們用parseFloat()來解決。這篇文章主要介紹了javascript將非數(shù)值轉(zhuǎn)換為數(shù)值,需要的朋友可以參考下2018-09-09laravel實(shí)現(xiàn)中文和英語互相切換的例子
今天小編就為大家分享一篇laravel實(shí)現(xiàn)中文和英語互相切換的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09JS實(shí)現(xiàn)點(diǎn)擊按鈕控制Div變寬、增高及調(diào)整背景色的方法
這篇文章主要介紹了JS實(shí)現(xiàn)點(diǎn)擊按鈕控制Div變寬、增高及調(diào)整背景色的方法,涉及javascript動(dòng)態(tài)操作頁面元素屬性的相關(guān)技巧,適用于動(dòng)態(tài)更換頁面皮膚的功能,需要的朋友可以參考下2015-08-08