Audio在移動(dòng)端的實(shí)踐

好久沒(méi)寫blog了,有三點(diǎn)原因,一是懶,二是懶,三是懶。

因?yàn)樽罱?xiàng)目里面有個(gè)需求,要在移動(dòng)端用web的Audio實(shí)現(xiàn)音頻播放。本想說(shuō)臣妾做不到啊~然而,還是開(kāi)始挖坑了。在這里記錄下各種坑死人的問(wèn)題。

準(zhǔn)備

先看兼容性(下圖),可以看到在移動(dòng)端上用是完全可行的(理論上):

compatibility.png

我們?cè)俜謩e看看audio提供的屬性,方法和事件

屬性

params.png

方法

way.png

事件

event.png

具體的可以戳這里。

實(shí)踐

其實(shí)按照上面的方法,隨便怎么寫怎么玩都可以,但主要有以下幾個(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)完成,可以播放,完美。

patten1.png

但是,理想和現(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.seekablefalse;
  • uc瀏覽器,魅族自帶瀏覽器,微信audio.buffered.length居然為0;

在iOS下,有以下問(wèn)題:

  • canplay事件觸發(fā)后,微信的audio.seekablefalse;
  • safariload了之后,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)題,如果是用zeptotap監(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ù)加載播放

audio1.gif

例子2:多音頻切換播放

audio2.gif

上面?zhèn)z個(gè)例子的代碼在這里。

最后

實(shí)踐都這里就算完了。不過(guò)這里有個(gè)更好玩的東西,有興趣可以看看,非??犰拧?/p>

在開(kāi)發(fā)的過(guò)程中,針對(duì)移動(dòng)端,參考了Audio5js,整理出了個(gè)audio的庫(kù)。代碼在這里,有興趣可以關(guān)注下。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容