好久没写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
的库。代码在这里,有兴趣可以关注下。
参考:
- http://dengo.org/archives/1048
- http://blog.csdn.net/alongken2005/article/details/44569981
- http://my.oschina.net/tommyfok/blog/202234?fromerr=ECkffV18