释放 HTML5 <audio> 的力量

作者:Giorgio Sardo | 高级技术推广师 – HTML5 和 Internet Explorer

各种声音构成了我们的生活背景。现在,HTML5 <audio> 元素使 Web 开发人员可以将声音嵌入他们的应用程序。控制的灵活性加上平台其他功能的集成,使多个应用场景得以实现,包括从简单的声音效果到背景音频,再到游戏体验以及更复杂的音频引擎。

本博客文章介绍了一些在 Web 应用程序中使用<audio> 标签的最佳实践,包括来自真实站点的一些有用的技巧。

在您的页面中添加音频元素

第一步是向您的页面中添加音频元素。您可以通过几种方式完成这一步骤,在标记中声明一个 <audio> 标签,在JavaScript 代码中将一个新的音频元素实例化,或者在页面中嵌入音频流:

<audio src="audio/sample.mp3" autoplay>
</audio>
运行现场演示
var audio = document.createElement("audio");
if (audio != null && audio.canPlayType && audio.canPlayType("audio/mpeg")){
   audio.src = "audio/sample.mp3";
   audio.play();
}
运行现场演示
<audio src="data:audio/mpeg,ID3%02%00%00%00%00%..." autoplay>
</audio>

运行现场演示

第一种方式使您可以在加载页面时初始化音频组件。第二种方式为您提供了更大的灵活性,并使您可以更好地管理网络流,这是由于它将音频剪辑的加载推迟到应用程序生命周期中的某个特定时间。第三种方式(较少推荐)是将音频文件作为data-uri 嵌入到页面中,减少向服务器发送请求的次数。

请注意,您可以播放由 JavaScript 生成的音频元素,即使它并未实际添加到DOM 树中(如上面的代码片段)。但在页面中添加音频元素将可以显示默认的控制条。

虽然本文未有描述,但您可以支持多种音频文件格式。同样,如果您将音频文件放在服务器上,请记住要在服务器端为mp3 文件(“audio/mpeg”) 注册MIME 类型。此处所举的示例是Internet 信息服务(IIS) 上的设置。

在播放之前预加载音频

只要您准备好了音频元素,您就可以选择最佳预加载策略。HTML5 <audio> 规范用三个可能的值描述预加载属性:

  • “none”:提示用户代理,作者不希望用户需要媒体资源,或者是,服务器要尽量减少不必要的流量。
    如果您的应用场景是每个帖子都带有音频文件的一个播客,这就是一个非常合适的选项,因为它减少了初始预加载的带宽。只要用户播放文件(无论是通过默认可视控件还是 JavaScript 方法 load() 或 play()),浏览器将开始提取音频流。
  • “metadata”:提示用户代理,作者不希望用户需要媒体资源,但提取资源元数据(维度、持续时间等)是合理的。
    如果您在构建音频播放器控件,并且您需要关于音频剪辑的基本信息,但尚不需要播放它,推荐该选项。
  • “auto”:提示用户代理,用户代理可以在对服务器不构成风险的情况下把用户的需求放在首位,直至并包括乐观地下载全部资源。
    如果您正在构建一个游戏,这可能是最合适的方式,因为它使您可以在真正开始游戏体验之前预加载全部音频剪辑。

请注意,当您以编程方式设置音频元素的 src 属性时,浏览器将设置preload(预加载)属性,除非它已被设置为 “auto”。基于此原因,如果您的应用场景需要一个不同的值,请务必在设置 src 之前在代码行中指定它。

您可以通过使用 F12 Developer Tools(Network 选项卡)运行本页面来预览这三个选项对网络的影响。为了调试,您可以通过选中“总是从服务器刷新”菜单来模拟新的调用并禁用本地缓存。

preload=none:

preload=metadata:

preload=auto:

虽然这个属性在初始化阶段非常有用,但您可能还需要知道浏览器何时真正下载音频剪辑,并准备好播放。您可以通过监听 “canplaythrough” 事件获取该信息;该事件由用户代理调用,一旦它判断现在准备开始播放,媒体资源就能以当前播放率一直播放至其结束,无需停顿以获取缓冲。

var audio = document.createElement("audio");
   audio.src = "audio/sample.mp3";
   audio.addEventListener("canplaythrough", function () {
   alert('The file is loaded and ready to play!');
}, false);

运行现场演示

循环

在音频应用场景中的另一个常见请求是循环播放声音剪辑的功能。有了 HTML5 <audio>,您可以使用 loop(循环)属性实现该功能;这个设置将一直循环播放您的剪辑,或一直播放直至用户或应用程序激活 pause() 音频控件。

<audio src="audio/sample.mp3" autoplay loop>
</audio>

运行现场演示

循环播放音频文件的另一个方法是在音频剪辑结束时以编程方式调用play() 方法;这样做最终将使您可以管理两次循环之间的延迟时间。

var audio = document.createElement("audio");
audio.src = "piano/3C.mp3";
audio.addEventListener('ended', function () {
// Wait 500 milliseconds before next loop
   setTimeout(function () { audio.play(); }, 500);
   }, false);
audio.play();

运行现场演示

请注意,在音频元素上执行的任何 play() 调用,在声音真正结束之前将不会生效。如果您希望“取消并重启”当前声音,将需要重置 currentTime。

var audio = null;
audio = document.createElement("audio");
audio.src = "piano/3C.mp3";
audio.addEventListener('ended', function () {
   audio.play();
}, false);
function play() {
      audio.play();
}
function restart() {
   audio.currentTime = 0;
   audio.play();
}

运行现场演示

多个音频标签

如果您的应用场景需要同时多次播放同一个音频文件(就是说,产生重叠的声音),可以通过对同一个文件创建多个音频标签来实现这种效果。如果您同时使用不同的音频文件,这个方式显然也是适用的。正如我们之前在本文中所述的那样,可以采用编程方式添加它们或在标记中将它们实例化。

以下代码片段显示了如何使用标记加载和播放多个音频文件。音频示例的长度都一样;在执行结束时,它们将从头开始重新循环播放。当在 Internet Explorer 9 中播放它们时,可以看到它们将在多个循环中自动同步。您将发现这 5 种声音的组合播放出来与之前的演示中的音频文件 (“sample.mp3”) 相像。

<body>
    <audio src="audio/Bass.mp3" autoplay loop></audio>
    <audio src="audio/Drum.mp3" autoplay loop></audio>
    <audio src="audio/Crunch.mp3" autoplay loop></audio>
    <audio src="audio/Guitar.mp3" autoplay loop></audio>
    <audio src="audio/Pizzicato.mp3" autoplay loop></audio>
</body>

运行现场演示

虽然这种方法很简单直接,但在大多数应用场景中,开发人员还是选择以编程方式创建音频剪辑。以下代码片断显示了如何使用代码动态添加3 个音频剪辑。当它们同时播放时,您将听到C 大调和弦!

AddNote("3C");
AddNote("3E");
AddNote("3G");
function AddNote(name) {
    var audio = document.createElement("audio");
    audio.src = "piano/" + name + ".mp3";
    audio.autoplay = true;
}

运行现场演示

此代码模式在任何浏览器上都可以正常工作,并且将使您可以构建极具吸引力的应用场景!

重要的是要记住,当您的应用程序或游戏变得更复杂时,您将最终面临两个限制:在同一个页面中可以预加载的音频元素数量,以及可以同时播放的音频元素数量。

这些数量取决于浏览器,还有 PC 的能力。根据我的经验,Internet Explorer 9 可以轻松地同时处理几十个并发音频元素。其它浏览器则做不到,当您循环播放多个文件时,可能会遇到明显的延迟和失真

同步策略

您应该总是考虑到在添加标签、获取内容以及准备好播放之间所涉及的延迟,效果取决于网络质量。具体地讲,当您要处理多个文件时,每个文件都有可能随时被提前或延迟播放。例如,这个声音片段是从本机加载的之前使用过的3 个文件。

您可以在 Timings 列中看到,不同的文件可能在不同的时间就绪。

先预加载全部文件是一个很常用的同步策略。一旦它们全部就绪,您就可以快速迭代循环并开始播放这些文件了。

var audios = [];
var loading = 0;
AddNote("2C");
AddNote("2E");
AddNote("2G");
AddNote("3C");
AddNote("3E");
AddNote("3G");
AddNote("4C");
function AddNote(name) {
    loading++;
    var audio = document.createElement("audio");
    audio.loop = true;
    audio.addEventListener("canplaythrough", function () {
    loading--;
    if (loading == 0) // All files are preloaded
        StartPlayingAll();
    }, false);
audio.src = "piano/" + name + ".mp3";
    audios.push(audio);
}
function StartPlayingAll() {
    for (var i = 0; i < audios.length; i++)
    audios[i].play();
}

运行现场演示

让我们现在就把一切融合在一起!以下演示模拟钢琴演奏Frère Jacques(也被称为 Brother John、Brother Peter…或 Fra Martino)。页面开始提取所有音符,当它们被预加载到客户端时显示加载进度。当它们全部就绪时,歌曲开始并保持循环播放。

运行现场演示

真实站点中的音频

现在我们已经看过用于处理多个音频文件的常用模式,我想重点介绍几个 Web 站点作为 <audio> 标签使用的最佳实践示例。

Pirates Love Daises:www.pirateslovedaisies.com

另一篇博文中,我谈到了 Pirates Love Daises,这是由 Grant Skinner 构建的一个极棒的 HTML5 游戏。除了优秀的游戏剧本和极具吸引力的视觉效果之外,Grant 的团队还开发了一个先进的音频库,可以在游戏中播放多个音频示例。主逻辑被封装在AudioManager 类中。如之前所建议的,在真正开始游戏之前,站点预加载了所有音频剪辑,并在初始加载屏幕中显示累计进度。该站点也考虑了网络超时或在音频文件下载过程中出现错误的情况。

addAudioChannel:function(b,a,f){
    var h=document.createElement("audio");
    if(f!=true){
        this.currAsset=h;
        this.timeoutId=setTimeout($.proxy(this,"handleAudioTimeout"),e.AUDIO_TIMEOUT);
        h.addEventListener("canplaythrough",$.proxy(this,"handleAudioComplete"),false);
        h.addEventListener("error",$.proxy(this,"handleAudioError"),false)
    }
    h.setAttribute("id",a);
    h.setAttribute("preload","auto");
    $("<source>").attr("src",b).appendTo(h);
    $("<source>").attr("src",b.split(".mp3")[0]+".ogg").appendTo(h);
    document.body.appendChild(h)
}
,handleAudioComplete:function(b){
    if(LoadedAssets.getAsset(b.target.id)!=true){
        LoadedAssets.addAsset(b.target.id,true);
        clearTimeout(this.timeoutId);
        this.calculatePercentLoaded(true)
    }
}
,handleAudioError:function(b){
    trace("Error Loading Audio:",b.target.id);
    LoadedAssets.addAsset(b.target.id,true);
    clearTimeout(this.timeoutId);
    this.calculatePercentLoaded(true)
}
,handleAudioTimeout:function(){
    trace("Audio Timed Out:",this.currAsset.id);
    LoadedAssets.addAsset(this.currAsset.id,true);
    this.calculatePercentLoaded(true)
}

运行现场演示

Grant 目前正在致力于 Sound Library 项目,该项目将允许开发人员在任何其他应用程序中使用其声音引擎的逻辑。期待这一项目的成功!

Firework(作者:Mike Tompkins):www.beautyoftheweb.com/firework

Firework 的演示特别有意思,因为它支持您同时与数条音轨进行互动,动态地更改每条轨道的音量。此外,当您和音频通道互动时,界面将动态响应不同的输入或设置。

这次的音频标签已在 HTML 标记中声明(只有 6 条轨道)。通过监听canplaythrough 事件,以编程方式跟踪该进度。只要全部音频文件已播放就绪,循环就可以检查列表并开始播放。

video.addEventListener('canplaythrough', onCanPlayAudio, false);
        for (var i = 0; i < 5; i++) {
            var aud = document.getElementById("aud" + i);
            targetVolumes.push(0);
            aud.volume = 0;
            audioTags.push({
                "tag": aud,
                "ready": false
            });
            aud.addEventListener('canplaythrough', onCanPlayAudio, false);
        }
        // Set audio/video tracks
        document.getElementById("tompkins").src = MediaHelper.GetVideoUrl("Firework_3");
        for (var i = 0; i < audioTracks.length; i++) {
            document.getElementById("aud" + i).src = MediaHelper.GetAudioUrl(audioTracks[i]);
}

运行现场演示

本例中的开发人员还决定,开始时将音量设置为 0,并在播放就绪的同时将音量动态上调至 1。这个小技巧减少了在音频开始时听见最初的“敲打”噪声的可能性,效果取决于声卡和驱动程序的质量。

BeatKeep:www.beatkeep.net

最后的应用场景可能是此处所展示的示例中最复杂的。在本例中,您可以使用节拍设备构建自己的歌曲,并循环播放多个音频剪辑。在该应用程序中,关键是要具有音频通道的完美同步,以及可以加载多个剪辑的灵活的缓冲系统。

节拍设备为您提供对节奏和时间签名的完整控制;使用成熟的计时逻辑和绑定模型,最终结果是非常流畅的体验!

结束语

我鼓励您使用 Internet Explorer 9 或其他浏览器尝试本文中的所有示例和应用程序,并让我们了解您的体验!您可以在这里下载本文中使用的所有示例代码。

如果您希望了解有关音频及视频控制的更多信息,建议您观看 MIX 半小时的会谈“现在就开始使用<audio>和<video>所需要知道的5件事”或在 MSDN 上阅读这些有趣的文章

感谢 DoubleDominant 提供本篇博客所使用的音频剪辑,并感谢Grant SkinnerArchetype 提供优秀的 HTML5 体验。

转自:http://msdn.microsoft.com/zh-CN/ie/hh377903

posted @ 2013-12-24 16:50  hdchangchang  阅读(416)  评论(0编辑  收藏  举报