好久沒(méi)寫(xiě)blog了脸候,有三點(diǎn)原因,一是懶绑蔫,二是懶运沦,三是懶。
因?yàn)樽罱?xiàng)目里面有個(gè)需求配深,要在移動(dòng)端用web的Audio
實(shí)現(xiàn)音頻播放携添。本想說(shuō)臣妾做不到啊~然而,還是開(kāi)始挖坑了凉馆。在這里記錄下各種坑死人的問(wèn)題薪寓。
準(zhǔn)備
先看兼容性(下圖),可以看到在移動(dòng)端上用是完全可行的(理論上):
我們?cè)俜謩e看看audio提供的屬性澜共,方法和事件
:
屬性
方法
事件
具體的可以戳這里向叉。
實(shí)踐
其實(shí)按照上面的方法,隨便怎么寫(xiě)怎么玩都可以嗦董,但主要有以下幾個(gè)問(wèn)題要解決的:
1.預(yù)加載的問(wèn)題母谎;
2.加載進(jìn)度條問(wèn)題;
3.多個(gè)音頻文件切換問(wèn)題京革;
4.其他的兼容性問(wèn)題奇唤。
1.預(yù)加載的問(wèn)題
我們先來(lái)看預(yù)加載的流程(如下),先用load
去加載音頻匹摇,當(dāng)音頻可以播放就會(huì)觸發(fā)canplay
事件咬扇,表示加載已經(jīng)完成,可以播放廊勃,完美懈贺。
但是,理想和現(xiàn)實(shí)總是有區(qū)別的坡垫,在表現(xiàn)不一的手機(jī)上就有問(wèn)題了梭灿。
問(wèn)題一:load
方法調(diào)用了沒(méi)效果,根本沒(méi)有加載音頻冰悠,要調(diào)用play
方法才開(kāi)始加載堡妒。
問(wèn)題二:在三星note3 和錘子T1手機(jī)上,有50%的幾率預(yù)加載失敗溉卓。如果預(yù)加載失敗皮迟,要切換好幾次播放/暫停
狀態(tài)才開(kāi)始加載播放搬泥,或者一直沒(méi)反應(yīng)。
問(wèn)題三:一般觸發(fā)load
加載音頻文件后万栅,音頻文件緩沖好會(huì)觸發(fā)canplay
事件的佑钾。
在安卓下西疤,觸發(fā)canplay
事件烦粒,會(huì)有下面問(wèn)題:
-
360瀏覽器
的audio.seekable
為false
; -
uc瀏覽器,魅族自帶瀏覽器代赁,微信
的audio.buffered.length
居然為0扰她;
在iOS下,有以下問(wèn)題:
-
canplay
事件觸發(fā)后芭碍,微信的audio.seekable
為false
徒役; -
safari
在load
了之后,canplay
事件不觸發(fā)窖壕,點(diǎn)擊play
后才觸發(fā) (9.1版本是正常的)忧勿;
看到這里是不是覺(jué)得坑大了,想逃瞻讽?不要急鸳吸,接著看。
解決方法
上面問(wèn)題總的來(lái)說(shuō)有倆個(gè)速勇,一個(gè)是加載進(jìn)度晌砾,另外一個(gè)就是播放Bug了。這里主要說(shuō)下問(wèn)題二的解決方法烦磁。
調(diào)用load
事件后养匈,對(duì)加載進(jìn)度進(jìn)行檢測(cè),如果直到canplay
觸發(fā)都伪,加載進(jìn)度一直為0呕乎,就判斷為預(yù)加載失敗。然后在點(diǎn)擊播放的陨晶,設(shè)置進(jìn)度audio.currentTime = 1;
猬仁,這樣就會(huì)再次觸發(fā)加載。這里還有個(gè)問(wèn)題珍逸,如果是用zepto
的tap
監(jiān)聽(tīng)點(diǎn)擊播放事件逐虚,可以再次加載,但一直不播放,要監(jiān)聽(tīng)touchend
這些事件才行(這個(gè)問(wèn)題糾結(jié)N久)茵瀑。
這樣調(diào)整后愿吹,在三星note 3 和錘子T1這些有問(wèn)題的手機(jī)上基本沒(méi)什么問(wèn)題了。
2.加載進(jìn)度條問(wèn)題
加載進(jìn)度买雾,瀏覽器提供了progress
事件把曼,但這個(gè)事件會(huì)有一些小問(wèn)題,所以采用setInterval的去實(shí)行漓穿。正常來(lái)說(shuō)在canplay
的時(shí)候顯示進(jìn)度條:
onCanplay: function () {
this.seekable = this.audio.seekable && this.audio.seekable.length > 0;
if ( this.seekable ) {
this.timer = setInterval(this.onProgress.bind(this), 500);
}
var name = this.list[this.index].name || '',
time = this.list[this.index].time || '';
this.trigger('canplay', time, name, this.list[this.index]);
},
onProgress: function () {
if ( this.audio && this.audio.buffered !== null && this.audio.buffered.length ) {
this.duration = this.audio.duration === Infinity ? null : this.audio.duration;
this.load_percent = ((this.audio.buffered.end(this.audio.buffered.length - 1) / this.duration) * 100).toFixed(4);
if (isNaN(this.load_percent)) {
this.load_percent = 0;
}
if ( this.load_percent >= 100 ) {
this.clearLoadProgress();
}
this.trigger('progress', this.load_percent);
}
},
// 對(duì)于play觸發(fā)后才開(kāi)始加載
play: function () {
if (!this.seekable) {
this.timer = setInterval(this.onProgress.bind(this), 500);
}
this.audio.play();
},
上面代碼的邏輯主要是檢測(cè)audio的buffered
嗤军,因?yàn)椴煌瑸g覽器對(duì)buffered的解析不同,如果跳躍播放晃危,有的會(huì)產(chǎn)生多段buffered叙赚,所以獲取最新的緩存要這樣:this.audio.buffered.end(this.audio.buffered.length - 1)
。
3.多音頻切換問(wèn)題
在播放列表里僚饭,有多個(gè)音頻文件震叮,點(diǎn)擊可以切換。正常的做法是鳍鸵,用tap
綁定點(diǎn)擊事件苇瓣,事件內(nèi)部這樣處理:
audio.pause();
audio.setAttribute('src', url);
audio.play();
在PC的chrome上是很正常的,完美偿乖。但是击罪,在手機(jī)上就嗝屁了。問(wèn)題為:偶發(fā)性的出現(xiàn)贪薪,切換音頻后媳禁,直接觸發(fā)音頻的ended
事件,然后再怎么切換播放/點(diǎn)擊
都是無(wú)效的了古掏。
這個(gè)問(wèn)題的解決方法很簡(jiǎn)單损话,就是在canplay
觸發(fā)的時(shí)候再觸發(fā)play
就好,不要切換了音頻url馬上play
:
_t.audioHandler.on('canplay', function (totalTime, name) {
_t.audioHandler.play();
});
因?yàn)闆](méi)有預(yù)加載的過(guò)程槽唾,每次都是點(diǎn)擊列表的音頻才播放丧枪,所以這樣理論上是可行的。但是如果點(diǎn)擊了播放庞萍,觸發(fā)了加載拧烦,馬上就點(diǎn)暫停,這時(shí)候canplay
還沒(méi)觸發(fā)钝计,會(huì)不會(huì)有問(wèn)題恋博?
4.其他的兼容性問(wèn)題
- 關(guān)于音頻的總時(shí)間,理論來(lái)說(shuō)私恬,正常加載的情況债沮,在
canplay
的時(shí)候是可以讀取到的,但因?yàn)樯厦嬉欢?code>load問(wèn)題本鸣,所以音頻總時(shí)間要手動(dòng)設(shè)置疫衩。 - 用
tab
去綁定播放事件好像會(huì)有奇葩的問(wèn)題,用touch
系列又太靈敏了荣德,都接受不了可以用fastclick
闷煤。
暫時(shí)還沒(méi)發(fā)生其他問(wèn)題童芹,下面就看看例子吧。例子分兩個(gè)鲤拿,一個(gè)是單音頻預(yù)加載播放假褪,另外一個(gè)是多音頻列表播放(UI直接用項(xiàng)目的了)。
例子1:?jiǎn)我纛l預(yù)加載播放
例子2:多音頻切換播放
上面?zhèn)z個(gè)例子的代碼在這里近顷。
最后
實(shí)踐都這里就算完了生音。不過(guò)這里有個(gè)更好玩的東西,有興趣可以看看幕庐,非尘米叮酷炫。
在開(kāi)發(fā)的過(guò)程中异剥,針對(duì)移動(dòng)端,參考了Audio5js絮重,整理出了個(gè)audio
的庫(kù)冤寿。代碼在這里,有興趣可以關(guān)注下青伤。
參考: