好久沒寫blog了,有三點原因,一是懶,二是懶,三是懶。
因為最近項目里面有個需求,要在移動端用web的Audio
實現音頻播放。本想說臣妾做不到啊~然而,還是開始挖坑了。在這里記錄下各種坑死人的問題。
準備
先看兼容性(下圖),可以看到在移動端上用是完全可行的(理論上):
我們再分別看看audio提供的屬性,方法和事件
:
屬性
方法
事件
具體的可以戳這里。
實踐
其實按照上面的方法,隨便怎么寫怎么玩都可以,但主要有以下幾個問題要解決的:
1.預加載的問題;
2.加載進度條問題;
3.多個音頻文件切換問題;
4.其他的兼容性問題。
1.預加載的問題
我們先來看預加載的流程(如下),先用load
去加載音頻,當音頻可以播放就會觸發canplay
事件,表示加載已經完成,可以播放,完美。
但是,理想和現實總是有區別的,在表現不一的手機上就有問題了。
問題一:load
方法調用了沒效果,根本沒有加載音頻,要調用play
方法才開始加載。
問題二:在三星note3 和錘子T1手機上,有50%的幾率預加載失敗。如果預加載失敗,要切換好幾次播放/暫停
狀態才開始加載播放,或者一直沒反應。
問題三:一般觸發load
加載音頻文件后,音頻文件緩沖好會觸發canplay
事件的。
在安卓下,觸發canplay
事件,會有下面問題:
-
360瀏覽器
的audio.seekable
為false
; -
uc瀏覽器,魅族自帶瀏覽器,微信
的audio.buffered.length
居然為0;
在iOS下,有以下問題:
-
canplay
事件觸發后,微信的audio.seekable
為false
; -
safari
在load
了之后,canplay
事件不觸發,點擊play
后才觸發 (9.1版本是正常的);
看到這里是不是覺得坑大了,想逃?不要急,接著看。
解決方法
上面問題總的來說有倆個,一個是加載進度,另外一個就是播放Bug了。這里主要說下問題二的解決方法。
調用load
事件后,對加載進度進行檢測,如果直到canplay
觸發,加載進度一直為0,就判斷為預加載失敗。然后在點擊播放的,設置進度audio.currentTime = 1;
,這樣就會再次觸發加載。這里還有個問題,如果是用zepto
的tap
監聽點擊播放事件,可以再次加載,但一直不播放,要監聽touchend
這些事件才行(這個問題糾結N久)。
這樣調整后,在三星note 3 和錘子T1這些有問題的手機上基本沒什么問題了。
2.加載進度條問題
加載進度,瀏覽器提供了progress
事件,但這個事件會有一些小問題,所以采用setInterval的去實行。正常來說在canplay
的時候顯示進度條:
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);
}
},
// 對于play觸發后才開始加載
play: function () {
if (!this.seekable) {
this.timer = setInterval(this.onProgress.bind(this), 500);
}
this.audio.play();
},
上面代碼的邏輯主要是檢測audio的buffered
,因為不同瀏覽器對buffered的解析不同,如果跳躍播放,有的會產生多段buffered,所以獲取最新的緩存要這樣:this.audio.buffered.end(this.audio.buffered.length - 1)
。
3.多音頻切換問題
在播放列表里,有多個音頻文件,點擊可以切換。正常的做法是,用tap
綁定點擊事件,事件內部這樣處理:
audio.pause();
audio.setAttribute('src', url);
audio.play();
在PC的chrome上是很正常的,完美。但是,在手機上就嗝屁了。問題為:偶發性的出現,切換音頻后,直接觸發音頻的ended
事件,然后再怎么切換播放/點擊
都是無效的了。
這個問題的解決方法很簡單,就是在canplay
觸發的時候再觸發play
就好,不要切換了音頻url馬上play
:
_t.audioHandler.on('canplay', function (totalTime, name) {
_t.audioHandler.play();
});
因為沒有預加載的過程,每次都是點擊列表的音頻才播放,所以這樣理論上是可行的。但是如果點擊了播放,觸發了加載,馬上就點暫停,這時候canplay
還沒觸發,會不會有問題?
4.其他的兼容性問題
- 關于音頻的總時間,理論來說,正常加載的情況,在
canplay
的時候是可以讀取到的,但因為上面一堆load
問題,所以音頻總時間要手動設置。 - 用
tab
去綁定播放事件好像會有奇葩的問題,用touch
系列又太靈敏了,都接受不了可以用fastclick
。
暫時還沒發生其他問題,下面就看看例子吧。例子分兩個,一個是單音頻預加載播放,另外一個是多音頻列表播放(UI直接用項目的了)。
例子1:單音頻預加載播放
例子2:多音頻切換播放
上面倆個例子的代碼在這里。
最后
實踐都這里就算完了。不過這里有個更好玩的東西,有興趣可以看看,非常酷炫。
在開發的過程中,針對移動端,參考了Audio5js,整理出了個audio
的庫。代碼在這里,有興趣可以關注下。
參考: