摘要:相關(guān)操作音頻的打斷音頻的打斷包括兩種情況組件重新上傳新的語(yǔ)音第一種情況,解綁相關(guān)事件,釋放內(nèi)存。當(dāng)瀏覽器預(yù)計(jì)能夠在不停下來(lái)進(jìn)行緩沖的情況下持續(xù)播放指定的音頻視頻時(shí),會(huì)發(fā)生事件。
我的blog原文鏈接
最近公司迭代的項(xiàng)目中,新增了音頻播放功能,填了不少音頻的坑,總結(jié)一下:
需求:交互需求:
上傳:點(diǎn)擊按鈕上傳語(yǔ)音,返回文件id(上傳文件的范疇,本文不會(huì)闡述)
播放:點(diǎn)擊播放按鈕,異步獲取語(yǔ)音播放src,音頻載入之后播放
操作:語(yǔ)音支持播放、暫停、停止、進(jìn)度拖動(dòng)等操作
當(dāng)然還有一些隱性需求:
一個(gè)界面可能存在多個(gè)播放文件
隨時(shí)播放一個(gè)語(yǔ)音,其它語(yǔ)音應(yīng)當(dāng)暫停
播放過(guò)程中,用戶重新上傳新的語(yǔ)音,此時(shí)播放應(yīng)停止
實(shí)現(xiàn)效果如下圖所示:(當(dāng)然,這只是項(xiàng)目用到的一部分,項(xiàng)目中還有其他頁(yè)面也用到了這個(gè)組件,那么就更考驗(yàn)組件的健壯性和可拓展性了。)
按需實(shí)現(xiàn) 一個(gè)界面可能存在多個(gè)播放文件我們對(duì)音頻的操作,通常是先獲取這個(gè)音頻 DOM Element,通過(guò)對(duì)它的操作,實(shí)現(xiàn)想要達(dá)到的效果,如果你只是設(shè)定一個(gè)audio這樣單薄的ref名稱,恐怕會(huì)有些問(wèn)題,因此我給每個(gè)音頻設(shè)定了一個(gè)唯一的ref名稱。
暫停其他語(yǔ)音
注意到上面的代碼,我在給 audio 添加屬性的時(shí)候,多添加了一個(gè) data-key的屬性,那就是為了暫停其他語(yǔ)音而使用的,作為我要操作頁(yè)面其他音頻而設(shè)置的標(biāo)識(shí):
// 暫停其他語(yǔ)音的方法 pauseOthers (except) { var audios = document.querySelectorAll("audio") ;[].forEach.call(audios, audio => { if (audio.dataset["key"] !== except.uniqueId) { audio.pause() } }) } // 調(diào)用的時(shí)候 this.pauseOthers (this)異步獲取語(yǔ)音src,音頻載入之后播放
我想這就是項(xiàng)目坑點(diǎn)之一,因?yàn)橐纛lsrc并不是上傳語(yǔ)音就返回的,上傳語(yǔ)音只返回了語(yǔ)音id,我們需要通過(guò)id再去異步請(qǐng)求一次,才能獲取到src。
基于這樣的前提,播放操作做了兩點(diǎn)考慮(單例模式思維):
為什么點(diǎn)擊播放再獲取語(yǔ)音src?雖然也可以進(jìn)入界面就請(qǐng)求src,但是如果用戶不點(diǎn)擊播放,就白白浪費(fèi)了不需要的請(qǐng)求,基于性能的考慮,決定點(diǎn)擊播放后再進(jìn)行請(qǐng)求。
并不需要每次點(diǎn)擊都重新請(qǐng)求一次,只有未獲取過(guò)src的音頻需要重新請(qǐng)求。
具體實(shí)現(xiàn):
播放按鈕綁定togglePlay()事件
判斷audioSrc是否有值
如果有值,直接進(jìn)行播放,綁定相關(guān)事件,暫停其他語(yǔ)音
如果沒(méi)有值,設(shè)置loading并進(jìn)行異步請(qǐng)求,將返回結(jié)果賦值給audioSrc,監(jiān)聽(tīng)音頻 canplay
監(jiān)聽(tīng)音頻 canplay (這邊有一個(gè)坑點(diǎn),后面會(huì)提到)
在canplay的回調(diào)中,loading結(jié)束,綁定相關(guān)事件,暫停其他語(yǔ)音
為什么相關(guān)事件的綁定放在 canplay 中? 不然你可能會(huì)出現(xiàn)下面的報(bào)錯(cuò):
Uncaught (in promise) DOMException: The element has no supported sources.
所以,答應(yīng)我,基于audio播放的 事件 或是 屬性 ,都放在 canplay 的回調(diào)之后。
相關(guān)事件包括(本組件中):
監(jiān)聽(tīng)事件 timeupdate : 控制進(jìn)度條展示
監(jiān)聽(tīng)事件 pause : 監(jiān)聽(tīng)按鈕 播放/暫停 樣式
設(shè)置屬性 currentTime : 控制進(jìn)度拖動(dòng)或者停止語(yǔ)音
監(jiān)聽(tīng)事件 error : 監(jiān)聽(tīng)播放錯(cuò)誤
音頻的操作 播放與暫停按鈕的樣式通過(guò)設(shè)置一個(gè)變量作為狀態(tài)值,paly() 和 pause() 的時(shí)候分別改變狀態(tài)值。
其它具體邏輯上文描述比較清楚,不再贅述。
停止、進(jìn)度拖動(dòng)停止:暫停音頻,并將currentTime設(shè)置為0
進(jìn)度拖動(dòng):根據(jù)拖動(dòng)位置計(jì)算currentTime值,并設(shè)置currentTime
兩個(gè)操作都涉及到了currentTime的設(shè)置,我們?cè)谶@里遇到了兩個(gè)坑:
設(shè)置currentTime無(wú)效
查詢資料后發(fā)現(xiàn)是后端的鍋,具體解決辦法鏈接在這里:HTML5 audio ,在chrome中設(shè)置currentTime無(wú)效
設(shè)置currentTime繼續(xù)播放
一開(kāi)始仍然以為是后端的鍋,因?yàn)楫?dāng)我靜態(tài)設(shè)置一個(gè) audioSrc 的時(shí)候,是沒(méi)有問(wèn)題的,但是當(dāng)我動(dòng)態(tài)設(shè)置,就會(huì)出現(xiàn)這樣的問(wèn)題:無(wú)論我是播放狀態(tài)還是暫停狀態(tài),設(shè)置到相對(duì)應(yīng)的currentTime都會(huì)繼續(xù)播放。
通過(guò)排查,發(fā)現(xiàn)當(dāng)我設(shè)置currentTime會(huì)再次觸發(fā)一次 canplay事件, canplay 的回調(diào)是綁定播放的相關(guān)操作,因此會(huì)繼續(xù)播放。
解決辦法,溫習(xí)了一遍addEventListener的語(yǔ)法,綁定canplay事件最多只調(diào)用一次。
this.audioElement.addEventListener("canplay", () => { // ...相關(guān)操作 }, { once: true })音頻的打斷
音頻的打斷包括兩種情況:
組件 destroyed
重新上傳新的語(yǔ)音
第一種情況,解綁相關(guān)事件,釋放內(nèi)存。
第二種情況,具體描述一下:
當(dāng)用戶重新上傳新的語(yǔ)音,不論此時(shí)語(yǔ)音暫停還是播放狀態(tài),都應(yīng)該停止。
我們通過(guò) watch 監(jiān)聽(tīng) id (上傳返回來(lái)的音頻id),當(dāng)id變化的時(shí)候,將 audioSrc 清空,以免播放舊的音頻內(nèi)容。
然而,僅僅這樣是不夠的,如果監(jiān)聽(tīng) error 事件,就會(huì)發(fā)現(xiàn)報(bào)錯(cuò),解決的辦法還是解綁相關(guān)事件,即,我們?cè)?canplay 回調(diào)中的綁定的相關(guān)事件,讓audio恢復(fù)初始狀態(tài),等到下一次播放的時(shí)候,需要重新請(qǐng)求新的src,回到上面播放的部分。
拓展在解決問(wèn)題的過(guò)程中,也查詢到了一些實(shí)用的知識(shí)點(diǎn),雖然在應(yīng)用中沒(méi)有運(yùn)用到,但是這些知識(shí)點(diǎn)看起來(lái)似乎挺有用的,為了下次遇到其他坑能快速找到解決辦法,先把這些知識(shí)點(diǎn)記錄下來(lái)。
canplay 與 canplaythrough 辨析當(dāng)瀏覽器能夠開(kāi)始播放指定的音頻/視頻時(shí),會(huì)發(fā)生 canplay 事件。
當(dāng)瀏覽器預(yù)計(jì)能夠在不停下來(lái)進(jìn)行緩沖的情況下持續(xù)播放指定的音頻/視頻時(shí),會(huì)發(fā)生 canplaythrough 事件。
了解其他媒體相關(guān)事件
HTMLMediaElement.play() 返回 Promise或 的 play()返回一個(gè) Promise,如果播放成功,Promise狀態(tài)變成fulfilled,如果播放失敗,狀態(tài)變?yōu)?b>rejected并提供錯(cuò)誤信息。
var playPromise = document.querySelector("video").play(); // In browsers that don’t yet support this functionality, // playPromise won’t be defined. if (playPromise !== undefined) { playPromise.then(function() { // Automatic playback started! }).catch(function(error) { // Automatic playback failed. // Show a UI element to let the user manually start playback. }); }video 412錯(cuò)誤
412 一般是因?yàn)榉?wù)器的 If-Unmodified-Since 或 If-None-Match 未實(shí)現(xiàn)
// 解決辦法 media.addEventListener("error", function (e) { var date = new Date(); var milliSecs = date.getMilliseconds(); var curr_src = $(media[0]).attr("src"); var curr_src_arr = curr_src.split("?"); var new_src = curr_src_arr[0]+"?t="+milliSecs; $(media[0]).attr("src",new_src); $(media[0]).find("source").attr("src",new_src); media[0].load(); }, false);
暫時(shí)完。
后續(xù)如果測(cè)試妹妹發(fā)現(xiàn)了什么bug,我會(huì)繼續(xù)填坑記錄滴。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97798.html
摘要:格式文件中不包含頭部信息,播放器無(wú)法知道采樣率,聲道數(shù),采樣位數(shù),音頻數(shù)據(jù)大小等信息,導(dǎo)致無(wú)法播放。 本文記錄一點(diǎn)工作經(jīng)歷,探討音頻文件的格式更多訪問(wèn)我的博客 前言 最近在整理音視頻編程的知識(shí),回憶起半年多,有一次需求是在后臺(tái)播放某來(lái)源的 pcm 文件,當(dāng)時(shí)處理方法用了點(diǎn)技巧,記錄下來(lái) 背景:業(yè)務(wù)需求,在web后臺(tái)里播放 pcm 文件,文件不大(約300KB,已知 pcm 的參數(shù)采樣...
閱讀 1058·2021-10-11 10:59
閱讀 3610·2021-09-26 09:55
閱讀 904·2019-08-30 15:55
閱讀 2658·2019-08-30 15:44
閱讀 442·2019-08-30 14:06
閱讀 689·2019-08-30 11:26
閱讀 3349·2019-08-30 10:49
閱讀 2499·2019-08-29 12:53