Audio在移动端的实践

96
士太奇
2015.12.01 19:42* 字数 1447

好久没写blog了,有三点原因,一是懒,二是懒,三是懒。

因为最近项目里面有个需求,要在移动端用web的Audio实现音频播放。本想说臣妾做不到啊~然而,还是开始挖坑了。在这里记录下各种坑死人的问题。

准备

先看兼容性(下图),可以看到在移动端上用是完全可行的(理论上):

compatibility.png

我们再分别看看audio提供的属性,方法和事件

属性

params.png

方法

way.png

事件

event.png

具体的可以戳这里

实践

其实按照上面的方法,随便怎么写怎么玩都可以,但主要有以下几个问题要解决的:

1.预加载的问题;
2.加载进度条问题;
3.多个音频文件切换问题;
4.其他的兼容性问题。

1.预加载的问题

我们先来看预加载的流程(如下),先用load去加载音频,当音频可以播放就会触发canplay事件,表示加载已经完成,可以播放,完美。

patten1.png

但是,理想和现实总是有区别的,在表现不一的手机上就有问题了。

问题一:load方法调用了没效果,根本没有加载音频,要调用play方法才开始加载。

问题二:在三星note3 和锤子T1手机上,有50%的几率预加载失败。如果预加载失败,要切换好几次播放/暂停状态才开始加载播放,或者一直没反应。

问题三:一般触发load加载音频文件后,音频文件缓冲好会触发canplay事件的。

在安卓下,触发canplay事件,会有下面问题:

  • 360浏览器audio.seekablefalse;
  • uc浏览器,魅族自带浏览器,微信audio.buffered.length居然为0;

在iOS下,有以下问题:

  • canplay事件触发后,微信的audio.seekablefalse
  • safariload了之后,canplay事件不触发,点击play后才触发 (9.1版本是正常的);

看到这里是不是觉得坑大了,想逃?不要急,接着看。

解决方法

上面问题总的来说有俩个,一个是加载进度,另外一个就是播放Bug了。这里主要说下问题二的解决方法。

调用load事件后,对加载进度进行检测,如果直到canplay触发,加载进度一直为0,就判断为预加载失败。然后在点击播放的,设置进度audio.currentTime = 1;,这样就会再次触发加载。这里还有个问题,如果是用zeptotap监听点击播放事件,可以再次加载,但一直不播放,要监听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:单音频预加载播放

audio1.gif

例子2:多音频切换播放

audio2.gif

上面俩个例子的代码在这里

最后

实践都这里就算完了。不过这里有个更好玩的东西,有兴趣可以看看,非常酷炫。

在开发的过程中,针对移动端,参考了Audio5js,整理出了个audio的库。代码在这里,有兴趣可以关注下。

参考:

前端技术