克服 iOS 操作系统中与 HTML 5 中的 audio 元素相关的限制

5,897 阅读15分钟
原文链接: html5online.com.cn

本文概述

从几年前开始,Web开发者们就试图在浏览器中播放视频或音频信息。通常,这些网站需要用户安装Flash等浏览器插件。后来,随着智能手机与平板电脑的不断普及,在移动设备中播放视频与音频的需求也变得越来越强烈。然而,由于移动设备的处理能力限制,对于开发者来说,通过浏览器插件来播放视频或音频已不再是一个切实可行的方案。

因此,在HTML 5中,新增了video元素与audio元素,以便让用户可以在不安装任何插件的情况下在浏览器中播放视频与音频。目前为止,虽然HTML 5标准本身还处于一个不断发展的过程当中,但是主流浏览器都已经实现了对其已经成熟的元素与API的支持。

在HTML 5中,可以通过audio元素的使用在浏览器中播放音频,尤其是在诸如iOS系统中的Safari浏览器之类的移动设备用浏览器中。尽管HTML 5中的audio元素是一个新增元素,在iOS操作系统中已经实现了对它的支持。据人气最高的移动应用程序Instapaper的开发者们称,在2011年11月,在使用Instapaper应用程序的iOS用户中,98.8%以上的用户使用的是iOS 4以上的操作系统。因为自iOS 3操作系统的Safari浏览器中,就已经实现了对于audio元素的支持,所以可以说在绝大多数的iOS操作系统中都已经实现了对于audio元素的支持。

本文针对在移动版Safari浏览器中使用audio元素时的一些限制及如何克服这些限制做一说明。

另外,在iOS 6中,苹果公司已经加入了对于Web Audio API(稍后介绍),通过该API的使用,可以不需要使用本文中所要介绍的一些对于audio元素所采取的规避措施。然而,iOS 6毕竟才刚刚被推出不久(在本文写作时),所以iOS 5仍然占据大部分的市场份额,所以本文中所介绍的这些规避措施仍然是有用的,是在移动设备用Safari浏览器中播放音频时所需要考虑使用的。

HTML 5中audio元素的限制

在讨论audio元素在移动设备用Safari浏览器中的限制之前,有必要首先让大家了解一下audio元素在桌面浏览器中的限制。HTML 5中的audio元素是一个成熟的元素,但同时也是一个有局限性的元素。它可以被很好地用来播放音乐,可以充当一个音乐播放器,可以播放音效,但不能被用来制作游戏等根据声音进行后续处理的应用程序。

对于音频格式的支持

不幸的是,目前在各浏览器中所支持的音频的编码格式并不统一。目前各浏览器中所支持的音频编码格式如下表所示。

表1.各浏览器中对于HTML 5中audio元素所播放音频格式的支持情况
  Ogg Vorbis WAV PCM AAC
Internet Explorer 9     × ×
Firefox × ×    
Chrome/Safari/移动设备用Safari   × × ×

如果你的应用程序需要兼容上表中所有浏览器,最好是提供多个音频文件源,例如同时使用Ogg Vorbis编码格式与AAC编码格式。

为什么上表中没有包含MP3格式?这是因为当以盈利目的在公开发布的Web网站或Web应用程序中播放MP3文件时,必须支付高额的版税。根据MP3的许可证要求,当使用时带来的盈利超过10万美元时必须支付总收入的2%。因此,我们推荐使用AAC编码格式代替MP3编码格式,使用AAC编码格式的音频并不意味着不需要支付版税,但是AAC编码格式的许可证要求要比MP3音频格式的许可证要求要松一些,它允许免费发布音频文件。同时AAC编码格式拥有更好的压缩率。

使用Ogg Vorbis编码格式时的一个好处是它是开源的,免许可证的,免支付版税的。然而,只有Firefox浏览器支持这种编码格式。

在HTML页面代码中为audio元素指定编码格式(即指定一个或多个音频文件源)的代码如下所示:

<audio>
    // AAC file (Chrome/Safari/IE9)
    <source src="sound.m4a" type="audio/mpeg" />
    // Ogg Vorbis (Firefox)
    <source src="sound.ogg" type="audio/ogg" />
</audio>

声音处理与音效

当使用声音时,一种普遍的需求是对声音进行处理,例如声音合成,音效处理,应用环境音效等等。HTML 5中的audio元素并不具有这种处理声音的能力。audio元素中加载什么声音,浏览器中就只能播放什么声音。

目前为止,Chrome浏览器中支持的Web Audio API与Firefox浏览器中支持的Audio Data API补充实现了这一特性,给予开发者在不依靠任何插件的情况下合成声音、处理音效的能力。目前为止,这两个API均处于发展过程中,而且是两个截然不同的API,其中Chrome浏览器中使用的Web Audio API已经成为HTML 5标准之一。

HTML 5中的audio元素在iOS操作系统中的Safari浏览器中的限制

除了以上所提到的限制之外,在iOS操作系统中,对于HTML 5中的audio元素又添加了一些额外的限制。

单一音频流

在移动设备用Safari浏览器中,对audio元素添加的一个最大的限制就是一次只能播放一个音频数据流,不能同时播放多个音频数据流。在移动设备用Safari浏览器中,HTML 5的video元素与audio元素均属于singleton类,所以一次只能播放一个audio元素(或video元素)中的音频(或视频)数据流。苹果公司并没有解释为什么要添加这个限制,但一种合理的解释是为了减少处理数据时所消耗的资源(与iOS操作系统中对于HTML 5中各元素及API添加限制时的原因相同)。

在移动设备用Safari浏览器中,由于一次只能播放一个音频数据流,所以如果在第一个音频文件的播放过程中开始播放第二个音频文件,第一个音频文件将立即被停止播放,开始播放第二个音频文件。

var audio1 = document.getElementById('audio1');
var audio2 = document.getElementById('audio2');
audio1.play(); //当下一行代码被执行时本音频数据流将被立即停止播放
audio2.play(); //此行代码将中止audio1中正在播放的音频数据

请注意,audio元素与video元素是可被接替使用的。如果在播放视频文件的过程中开始播放音频文件,视频文件将立即被停止播放。一次只能播放一个音频文件或视频文件,不能同时播放音频文件与视频文件。

var audio = document.getElementById('audio');
var video = document.getElementById('video');
video.play();

//中间代码略

audio.play(); //此行代码将停止视频播放

自动播放

在移动设备用Safari浏览器中,音频文件不能被自动播放。音频文件只能通过用户执行点击按钮等操作时被加载,即使开发者在HTML页面代码中为audio元素指定autoplay属性(代码如下所示),移动设备用Safari浏览器将忽略该属性,不会在页面加载时自动播放音频。

<audio id="audio" src="audio_file.mp3" autoplay></audio>

加载音频

只有当用户通过执行点击按钮等操作,触发了onmousedown、onmouseup、onclick或ontouchstart等touch事件时,音频数据流才会被浏览器所加载。例如执行如下所示的代码时,浏览器并不会自动播放音频文件。

var audio = document.getElementById('audio');
audio.play();

即使在HTML页面代码中为audio元素指定preload属性(代码如下所示),移动设备用Safari浏览器将会忽略该属性,并不会自动加载音频文件。

<audio id="audio" src="audio_file.mp3" preload="auto"></audio>

在桌面用浏览器中,上述代码将会使页面加载时自动下载音频文件,然而在移动设备用Safari浏览器中,preload属性将被忽略,页面加载时浏览器并不会自动下载音频文件。

其他注意事项

在移动设备用Safari浏览器中,还有其他一些在使用audio元素时应该注意的事项。

当iOS操作系统初始化一个新的audio对象时,将会产生几秒钟的延时。

var audio1 = document.getElementById('audio1');
var audio2 = document.getElementById('audio2');
audio1.play();

//中间代码略
audio2.play(); 
//当iOS操作系统初始化一个新的audio对象时会带来几秒钟的延时

//中间代码略
audio1.play(); //同样会带来几秒钟的延时,因为在播放audio2中的音频时audio1对象已被销毁

另外请注意在你的代码逻辑中不能假定页面加载时已自动加载音频数据流。play()方法的调用将会导致失败,且浏览器不会抛出异常。当设置一个未加载音频数据的audio元素的currentTime属性值时,因为该audio元素中并没有加载到元数据,所以浏览器将抛出异常。

//页面加载时运行
var audio = document.getElementById('audio');
audio.play(); //播放失败,浏览器不会抛出异常
audio.currentTime = 2; //由于audio元素没有被加载到元数据,所以浏览器将抛出异常

音频文件不能被缓存在iOS操作系统中的manifest文件中。manifest文件只能被使用在离线Web应用程序中。即使将音频文件指定在manifest文件中,iOS操作系统将会忽略该文件,并不会缓存该文件。每次当Web应用程序需要访问音频文件时读需要从网络中访问该文件。

在移动设备用Safari浏览器中,在JavaScript脚本代码中不能动态修改audio元素的volume(音量)属性与plackbackRate(播放速度)属性,即使在脚本中书写了这些代码,这些代码也不会起作用。在移动设备用Safari浏览器中,音量只受用户控制(初始值始终为1),而播放速度是不能被改变的。

在iOS 5之前,audio元素的loop属性值是不被支持的。因此如果我们需要在音频播放结束时自动重复播放该音频,我们需要在audio元素的onended事件中调用audio元素的play方法:

var audio = document.getElementById('audio');
audio.play();
var onEnded = function() {
    this.play();
};
audio.addEventListener('ended', onEnded, false);

解决方案

如何解决移动设备用Safari浏览器中audio元素的限制完全取决于开发者需要如何使用audio元素。如果开发者只想播放一个音频文件或一个音频文件列表中的所有音频文件,并不需要添加很多额外的处理。但是,如果开发者需要处理音效,将需要添加很多额外的处理。

修改被播放的音频文件

如果要在播放当前音频文件时切换播放到另一个音频文件,最简单的处理方法是修改audio元素的音频文件来源,代码如下所示。然而这并不是一个理想的解决办法,因为在播放该音频文件之前,用户首先要等待该音频文件被加载完毕。

var audio = document.getElementById('audio');
audio.play();
//中间代码略
audio.src = 'newfile.m4a';//执行此处代码时并不需要用户执行任何操作
audio.play(); //当新的音频数据被加载时会产生一段时间的延迟

一个更好的切换音频文件的方法是使用一个音频sprite。换句话说,你需要将所有音频文件结合在一个音频数据流中,这样当播放音频数据流时就可以只播放音频文件的一部分(稍后详述)。

自动播放

对于移动设备用Safari浏览器中不能自动播放音频文件的限制,没有任何解决方案。正如上面所介绍过的,在iOS设备中,只能通过用户触发touch事件来播放音频数据。在移动设备用Safari浏览器中使用audio元素时,你必须要修改你的工作流以迎合这种限制(尽管一开始没有考虑到这个限制的开发者们可能会修改很多代码流程)。

在iOS4.2.1之前,你可以在一个同步Ajax调用的回调函数中加载音频文件,代码如下所示。

//页面加载时运行
var audio = document.getElementById('audio');
jQuery.ajax({
    url: 'ajax.js',
    async: false,
    success: function() {
        audio.play(); //在iOS4.2.1之前将会自动播放音频
    }
});

在这段代码中,存在一个问题:由于该代码中执行同步Ajax调用,所以浏览器将会被阻塞,直到调用结束。在移动设备用Safari浏览器中,阻塞并不仅仅意味着页面暂停加载,整个应用程序都将被暂停。如果这时发生错误,移动设备中的Safari浏览器将被停留在锁定状态,唯一的解决方法是点击主屏幕按钮强制结束应用程序。

自从iOS4.2.1开始,苹果公司修正了这个问题,所以在之后的版本中只能通过用户触发touch事件来播放音频。

加载音频

在移动设备用Safari浏览器中,音频文件只在用户触发touch事件时被加载。例如onmousedown、onmouseup、onclick与ontouchstart都是有效的touch事件,都可以在事件处理函数中加载音频数据,代码如下所示。请注意在这段代码中,当音频加载完毕后,我们可以通过执行audio元素的play方法来播放音频数据。

//页面加载时运行
var button = document.getElementById('button');
var audio = document.getElementById('audio');

var onClick = function() {
    audio.play(); //音频将被加载并播放
};

button.addEventListener('click', onClick, false);

使用sprite对象

在移动设备用Safari浏览器中为多个音频文件使用sprite对象是在多个音频文件之间进行切换的一种最好的解决方法。一个sprite对象可以将多个音频文件结合到一个单一的音频数据流中。

在定义sprite对象时,我们需要指定每个音频文件的开始位置(单位为秒),结束位置或长度(单位为秒),以及ID。当你想播放某个sprite对象时,你可以将audio元素的currentTime属性值设置为某个sprite对象的开始位置,然后调用该audio元素的play方法,代码如下所示:

//audioSprite对象中所使用的所有音频文件都已在用户触发touch事件时加载完毕
var audioSprite = document.getElementById('audio');
var spriteData = {
    meow1: {
        start: 0,
        length: 1.1
    },
    meow2: {
        start: 1.3,
        length: 1.1
    },
    whine: {
        start: 2.7,
        length: 0.8
    },
    purr: {
        start: 5,
        length: 5
    }
};

//播放meow2声音片断
audioSprite.currentTime = spriteData.meow2.start;
audioSprite.play();

在这段代码中,我们指定播放meow2声音片断,因为该声音片断被播放结束时我们没有定义任何后续处理,所以将会自动播放whine声音片断与purr声音片断。通过添加使用一个audio元素的timeupdate事件,开发者可以监视音频已被播放时间并在meow2声音片断播放结束时暂停或停止音频播放,代码如下所示。

var handler = function() {
    if (this.currentTime >= spriteData.meow2.start + spriteData.meow2.length) {
        this.pause();
    }
};
audioSprite.addEventListener('timeupdate', handler, false);

为音频使用sprite对象的最大好处是当在多个音频文件之间进行切换播放时将不会有延迟(就好像所有音频文件都已被下载结束后在多个音频文件之间进行切换播放一样)。另外,将所有数据流结合在一个文件中也是一种减少HTTP请求的有效手段。

请注意:在iOS操作系统中,对于当前播放位置(currentTime属性值)的更改并不是非常准确的,将currentTime属性值设定为6.5时,播放位置可能被定位到6.7秒或6.2秒处。所以在指定sprite对象时,在多个音频文件之间保留一段时间是有必要的,以免将播放位置定位到另一个视频文件之中,但是保留一段时间也可能在sprite对象中指定从6.8秒处开始播放,但是当前播放位置被定位到6.4秒处时产生一段时间的延迟。

请确保在开始播放sprite对象中使用的任何音频文件时,所有音频文件都已被加载完毕。这一点是重要的,因为如果没有将所有音频文件加载完毕,当切换播放未被加载的音频文件时浏览器仍然需要加载并缓存该音频文件,从而产生一段时间的延迟。

本文总结

尽管在iOS操作系统中的Safari浏览器中,对于audio元素存在许多限制,该元素仍然是一个备受欢迎的元素,我们应该根据需要在iOS操作系统中正确使用该元素。在本文中,我们介绍了桌面用Safari浏览器与移动设备用Safari浏览器中audio元素所存在的限制,我们也介绍了对于这些限制的一些解决方案,同时介绍了在移动设备用Safari浏览器中使用音频sprite对象的一些好处。如果想更多了解HTML 5中audio元素或video元素的更多知识,可以参阅笔者所著《HTML 5与CSS 3权威指南》,或点击此处报名参加我们所办的面向企业(可赴企业现场培训)或面向个人的培训班,为了保证学员真正掌握所学知识,参加培训后一年内,凡学员上机时遇到所学课程内的各种问题,可在本站“技术论坛”栏目内提出后由本站专门回答。