各種聲音構成了我們的生活背景。現在,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 Skinner 和 Archetype 提供優秀的 HTML5 體驗。