簡介
html
過去幾年,開發人員一直都在製造完善的交互體驗,努力使其能夠在瀏覽器中正確運行。這樣的站點一般須要使用瀏覽器插件 (Flash)。隨着智能手機和平板電腦的推出,交互體驗看似與新的小部件可以完美匹配。可是,因爲移動設備的處理能力有限,瀏覽器插件再也不是一種可行的開發平臺。html5
AAC:高級音頻編碼 (Advanced Audio Coding)ios
CSS:級聯樣式表 (Cascading Style Sheet)web
HTML:超文本標記語言 (HyperText Markup Language)ajax
MP3:MPEG-1 Audio Layer 3編程
OGG:一種開放容器格式 (open container format)瀏覽器
WAV:波形音頻格式 (Waveform Audio Format)緩存
HTML5 已經添加了大量無需額外插件的使用的工具。W3C 的 HTML5 規範仍在開發之中,可是在規範開發過程當中,瀏覽器已經開始提供支持。網絡
HTML5 音頻是一個巨大的進步,它容許在瀏覽器中嵌入聲音,尤爲是在移動設備中,好比 iOS 的移動版 Safari 瀏覽器上。儘管 HTML5 音頻是一個新特性,但已提供了 iOS 支持。根據流行的移動應用程序 Instapaper 的開發人員報導,2011 年 11 月,其 iOS 用戶中有 98.8% 都在使用 iOS 4 或更高版本(請參閱 參考資料)。因爲 HTML5 音頻是在 iOS 3 中引入到移動版 Safari 中的,因此您能夠放心,iOS 平臺爲 HTML5 音頻提供了普遍的支持。框架
在文本中,您將瞭解 HTML5 在桌面上和移動版 Safari 內的侷限性,並嘗試採用一些解決方案來建立交互的聲音效果。本文涵蓋的其餘內容包括:不受支持的事件、audio sprite 以及如何使用 directCanvas 和 multiSound 加速 HTML5 遊戲性能。
有一點很是值得關注:對於 iOS 6,Apple 已經添加了對 Web Audio API(討論以下)的支持,所以再也不須要使用本文中所討論的許多變通方法。不過,iOS 6 剛剛面世不到幾周時間,因此 iOS 5 仍然是市場的主流。本文中所討論的問題以及所提供的變通方式仍有效,應該在爲移動版 Safari 開發聲頻時考慮使用它們。
您能夠 下載 本文中使用的示例的源代碼。
在討論移動版 Safari 中的侷限性以前,有必要理解 HTML 音頻在桌面上的侷限性。HTML5 音頻雖然很健壯,但有其侷限性,這主要取決於它的實現。對於音樂播放器(點唱機播放器)或簡單的聲音效果,它頗有效,可是對於聲音密集的應用程序如遊戲,它的表現不是很理想。
不幸的是,並非全部瀏覽器都支持相同的視頻文件格式。如表 1 所示,目前有四種主要格式:MP三、OGG、WAV 和 AAC。
Ogg Vorbis | WAV | PCM | AAC | |
---|---|---|---|---|
Internet Explorer 9 | X | X | ||
Firefox | X | X | ||
Chrome/Safari/移動版 Safari | X | X | X |
爲了涵蓋全部瀏覽器,最好是讓全部的視頻流都具備 Ogg Vorbis 和 AAC 兩種格式。
爲何沒有包括 MP3?MP3 在進行商業傳播時須要支付繁重的版稅。MP3 的受權要求對於全部超過 $100K 的數據收取 2% 的傳播費(請參閱參考資料)。出於這個緣由,我更傾向於使用 AAC 而非 MP3。AAC 也並不是徹底免版稅的,但它對於免費傳播的許可沒有那麼嚴格。AAC 還提供了更好的壓縮,文件能夠更小,它是 Web 領域的福音(請參閱 參考資料)。
Ogg Vorbis 之因此壓倒性地得到了個人喜好是由於它是開源的、無專利費而且免版稅的。不過,只有 Firefox 支持它。
清單 1 顯示了跨瀏覽器兼容 HTML 標記。
<audio> // AAC file (Chrome/Safari/IE9) <source src="sound.m4a" type="audio/mpeg" /> // Ogg Vorbis (Firefox) <source src="sound.ogg" type="audio/ogg" /> </audio>
在處理音頻時,一個強大的特性是處理聲音的能力。不管動態合成聲音、處理聲音效果、應用環境效果,仍是進行基本的立體聲平移,HTML5 音頻缺少全部這些處理能力。您加載的視頻就是將要播放的視頻。
Web Audio API (Chrome) 和 Audio Data API (Firefox) 無需任何瀏覽器插件便可進行合成和動態處理音頻的能力幫助您解決了特性缺失的問題(請參閱 參考資料)。這兩種 API 均在開發當中,僅在 Chrome 14+ 和 Firefox 4+ 中受支持。不幸的是,在實現方面這兩者差別很大。目前有一些表現不錯的庫可用來使支持正規化,好比 audiolibjs(請參閱 參考資料)。Chrome 的 Web Audio API 就是經過 W3C 推廣的標準。
要重複播放聲音自己,必須實例化此聲音的一個單獨的音頻對象。在標記和可以播放的音頻之間存在 1:1 的對應。對於當前狀態的 HTML5 音頻,是沒法分層的。其餘平臺,好比 Flash,能夠分出一個單獨的音頻對象,無需建立一個新的音頻對象。
HTML5 音頻自己多少存在一些限制,而移動版 Safari 則向 HTML5 音頻最基本的使用添加了其餘的限制。
移動版 Safari 帶來的最大的侷限之一是一次只能播放一個單音頻流。移動版 Safari 中的 HTML5 媒體元素都是單例的,因此一次只能播放一個 HTML5 音頻(和 HTML5 視頻)流。Apple 爲這一侷限作過解釋,但咱們推斷這是爲了減小數據費用(這也是大多數 iOS HTML5 其餘侷限的緣由所在)。
iOS 爲移動版 Safari 提供了單一 HTML5 媒體(音頻和視頻)容器。若是想要在播放一個音頻流的同時播放另外一個音頻流,那麼就會從容器中刪除前一個音頻流,新的音頻流將會在前一個音頻流的位置上被實例化。
清單 2 顯示了在一個流播放的同時調用 play()
如何會中止以前的流,在本例中該流爲 audio1。
var audio1 = document.getElementById('audio1'); var audio2 = document.getElementById('audio2'); audio1.play(); // this stream will immediately stop when the next line is run audio2.play(); // this will stop audio1
查看並收聽 本例的效果。
有一點很重要,務必牢記,音頻和視頻是能夠互換的。若是在視頻播放的同時要播放音頻文件,那麼視頻就會中止。一次只能播放一個音頻或視頻流,如清單 3 所示。
var audio = document.getElementById('audio'); var video = document.getElementById('video'); video.play(); // at a later time audio.play(); // this will stop video
在移動版 Safari 中加載的頁面上,不能自動播放音頻文件。音頻文件只能從用戶觸發的觸摸(單擊)事件加載。若是在 HTML 標記中使用了autoplay
屬性,那麼移動版 Safari 將會忽略這個屬性,而且不會在加載頁面時播放此文件,以下所示:
<audio id="audio" src="audio_file.mp3" autoplay></audio>
Safari Developer Guide 上有有關於此的詳細信息(請參閱 參考資料)。
除非由用戶接觸事件,好比經過 onmousedown
、onmouseup
、onclick
或 ontouchstart
觸發事件,不然不能加載音頻流。圖 1 顯示了這樣的一個示例。
若是在加載頁面時運行清單 4 中的代碼,那麼在移動版 Safari 中不會加載視頻流,甚至不會下載該視頻流。
var audio = document.getElementById('audio'); audio.play();
即使是 HTML markup 中使用了 preload
屬性,移動版 Safari 仍會忽視此屬性,而且不會加載此文件,除非由用戶觸摸事件,如清單 5 所示。
preload
屬性<audio id="audio" src="audio_file.mp3" preload="auto"></audio>
查看並收聽 本例的效果。
在桌面 Safari 上,在加載頁面時,清單 5 中的代碼會下載此音頻文件。可是,在移動版 Safari 上,此屬性會被忽視,而且不會下載此音頻文件。
在移動版 Safari 中使用 HTML5 音頻還有其餘幾個怪癖須要考慮。
在初始化一個新的音頻流時會有幾秒的延時,這是由於 iOS 須要實例化一個新的音頻對象。清單 6 顯示瞭如何遭遇這種延時。
var audio1 = document.getElementById('audio1'); var audio2 = document.getElementById('audio2'); audio1.play(); // at a later time audio2.play(); // there will be a few-seconds delay as iOS is instantiating a new audio object. // at an even later time audio1.play(); // there will also be a few-seconds delay, as the audio object // for audio1 in iOS was destroyed when we played audio2.
查看並收聽 本例的效果。
重要的是確保您的邏輯不會假設音頻流是在加載頁面時載入的。調用 play()
在默認狀況下會失敗,若是在將要加載但還沒有載入其元數據的音頻流上嘗試設置 currentTime
,則會拋出一個致命錯誤,如清單 7 所示。
currentTime
// run on page load var audio = document.getElementById('audio'); audio.play(); // This will silently fail audio.currentTime = 2; // This will throw a fatal error because the metadata // for the audio does not exist
查看並收聽 本例的效果。
音頻文件不能緩存在 iOS 上的移動版 manifest 中。只有在對某個離線應用程序使用清單 (manifest) 時,這才適用。若是一個音頻文件包含在此清單中,iOS 將會忽略它,而且不會緩存此文件。每當此 Web 應用程序須要訪問此音頻文件時,都須要從該網絡訪問此文件。
用 JavaScript 以編程方式進行相關設置時,移動版 Safari 並不會尊重此音量和 playbackRate
屬性。更改屬性也不會實際調整這些值。音量老是在用戶控制下,而且 playbackRate
在移動版 Safari 中仍然不受支持。音量老是保持設置爲 1,playbackRate
則會設置爲但願設置的新值,可是音頻流回放的實際速度不會發生改變。這會爲 onratechange
事件帶來某些複雜性,咱們將在 不受支持的事件 部分對此進行討論。
在 iOS 5 以前,循環屬性是不受支持的。爲了解決缺少支持的問題,能夠向 onended
事件添加了一個事件偵聽程序,並在該函數中調用play()
。清單 8 顯示了一個例子。
var audio = document.getElementById('audio'); audio.play(); var onEnded = function() { this.play(); }; audio.addEventListener('ended', onEnded, false);
查看並收聽 本例的效果。
用來解決移動版 Safari 中 HTML5 音頻的缺陷的解決方案徹底取決於具體用法。若是您只想播放一個單一音頻文件或音頻文件的播放列表,那麼無需作太多更改。可是,若是須要得到交互的聲音效果,那麼事情就有些棘手了。
單個音頻流侷限的一種解決方案是用所需音頻置換出源文件,如清單 9 所示。這不是一種理想的解決方案,由於您須要等待加載新的音頻流後才能播放它。
var audio = document.getElementById('audio'); audio.play(); // at some later point in your script (does not need to be from a touch event) audio.src = 'newfile.m4a'; audio.play(); // there will be a slight delay while the new audio file loads
查看並收聽 本例的效果。
應對單個音頻流侷限性的一種更好的解決方案是使用 audio sprite。簡言之,您要將全部的音頻綜合到一個單音頻流中,而後播放此流的各個部分。Audio sprite 部分提供了更詳細的信息。
對於自動播放的侷限性,沒有變通方案。正如前面所說起的,音頻流只能從用戶觸摸事件加載。當針對移動版 Safari 進行開發時,重要的一點是要在必要時調整您的工做流,以適應這個侷限性。(以個人經驗來看,我知道若是在一開始沒有將這些考慮在內,那麼未來會發生不少重構。)
在 iOS 4.2.1 以前,能夠從一個同步 Ajax 調用的回調來加載音頻文件,如清單 10 內的示例所示。
// run on page load var audio = document.getElementById('audio'); jQuery.ajax({ url: 'ajax.js', async: false, success: function() { audio.play(); // audio will play in iOS before 4.2.1 } });
收聽 這個例子的效果。
清單 10 中的方法存在一個問題:因它是一個同步 Ajax 調用,因此瀏覽器是鎖定的,直到調用結束爲止。在移動版 Safari 中,鎖定並不僅是意味着頁面鎖定,整個應用程序都會鎖定。若是有錯誤發生,而且移動版 Safari 陷入鎖定狀態(可能性不是很大),那麼退出瀏覽器的唯一方式是單擊 home 按鈕並強制關閉應用程序。
Apple 在 iOS 4.2.1 中修復了這種變通方式,因此這種變通方法在 iOS 4.2.1 和後續版本中是不起做用的。
音頻流只在用戶事件觸發時才能加載。如清單 11 所示,onmousedown
、onmouseup
、onclick
和 ontouchstart
都是一些可用的事件,當在一個回調中調用它們時,可成功加載一個音頻流。請注意,這種狀況只適用於加載一個音頻文件時,在一個已經加載的文件上調用 play()
會按預期的那樣工做。
// run on page load var button = document.getElementById('button'); var audio = document.getElementById('audio'); var onClick = function() { audio.play(); // audio will load and then play }; button.addEventListener('click', onClick, false);
查看並收聽 本例的效果。
乍一看,清單 11 更像是一個煩人的變通方案。可是,最好是爲您的遊戲或交互式體驗提供一個啓動屏幕,如圖 2 所示,要求用戶單擊一個按鈕來啓動。當用戶單擊 start 按鈕時,您可使用該事件在您的項目中加載音頻。
雖然移動版 Safari 中的 HTML5 音頻支持來自桌面的全部媒體事件,但要注意的是,因爲以前提到的幾個不受支持的屬性,某些事件永遠都不會觸發。還有幾個怪癖須要注意。
表 2 列出了此音頻元素的全部事件回調及其在桌面和移動版 Safari 上的兼容性。結果是以做者設置的 HTML5 音頻 事件調試程序 爲基礎的,若是您願意,盡能夠去嘗試。
事件 | 描述 | 桌面 | 移動版 Safari |
---|---|---|---|
abort |
在媒體徹底下載以前,瀏覽器中止獲取媒體。 | X | X |
canplay |
瀏覽器可恢復媒體數據的回放,但會作出評估:若是回放開啓,在不會停下來作進一步的內容緩衝的狀況下,媒體資源在結束操做前不會以目前的回放速度呈現。 | X | X |
canplaythrough |
瀏覽器會作出評估:若是回放如今開始,那麼在不停下來作進一步緩衝的狀況下,媒體資源會以目前的回放速度呈現,一直到結束。 | X | X |
durationchange |
duration 屬性發生變化。 | X | X |
emptied |
媒體元素網絡狀態更改爲 NETWORK_EMPTY 狀態。 | X | X |
ended |
回放在媒體資源的終點中止,且 ended 屬性被設置爲 true。 | X | X |
error |
在獲取媒體數據前會發生錯誤。使用此 error 屬性來得到當前的錯誤。 | X | X |
loadeddata |
瀏覽器能夠初次在目前的回放位置呈現媒體數據。 | X | X |
loadedmetadata |
瀏覽器知道媒體資源的持續時間和大小。 | X | X |
loadstart |
瀏覽器開始加載媒體數據。 | X | X |
pause |
pause 方法返回後,回放暫停。 | X | X |
play |
play 方法返回後,回放開始。 | X | X |
playing |
回放開始。 | X | X |
progress |
瀏覽器在獲取媒體數據。 | X | X |
ratechange |
defaultPlaybackRate 或 playbackRate 屬性發生變化。 |
X | X (shouldn't) |
seeking |
seeking 屬性被設置爲 true ,且有時間發送此事件。 |
X | X* |
seeked |
seeking 屬性被設置爲 false 。 |
X | X* |
stalled |
瀏覽器獲取媒體數據,但媒體數據再也不到達。 | X | X |
suspend |
瀏覽器暫停加載媒體數據,而且沒有下載徹底部的媒體資源。 | X | X |
timeupdate |
currentTime 屬性做爲正常回放的一部分或由於某些其餘條件發生變化。 |
X | X |
volumechange |
volume 屬性或 muted 屬性發生變化。 | X | |
waiting |
瀏覽器中止回放,由於它在等待下一幀。 | X | X |
以下列表提供了有關幾個事件回放的一些注意事項。
ratechange
每當 playbackRate
發生更改,都會觸發 ratechange
事件。正如以前提到的,移動版 Safari 不支持更改音頻流(以及視頻)的回放,因此playbackRate
不該觸發。可是,自 iOS 5.1.1 起,HTML5 音頻仍會觸發 ratechange
事件,即使實際的回放速度並未發生改變。
volumechange
使用 JavaScript 不能更改音量,因此從不會觸發 volumechange
事件。即使在移動版 Safari 打開時用戶在其設備上更改了音量,也不會觸發這個事件。
seeking
/seeked
當這種尋找是經過 JavaScript 完成的時候,移動版 Safari 只支持 seeking
和 seeked
事件,如清單 12 所示。若是顯示了內置控件,而且用戶使用了進度條進行尋找,那麼不會按照預期的方式觸發 seeking
和 seeked
。
currentTime
將會觸發 seeking
和 seeked
事件var audio = document.getElementById('audio'); audio.currentTime = 60; // seeking and seeked will be fired
使用 audio sprite 是知足移動 Safari 中多音效需求的最佳解決方案。與 CSS image sprite 相似,audio sprite 能夠將全部的音頻綜合到一個音頻流,如圖 3 所示。
原理很直觀。您須要存儲每一個 sprite 的數據:開始點、結束點(或長度)和一個 ID。當您想要播放某個 sprite 時,須要將此音頻流的currentTime
設爲開始位置並調用 play()
。清單 13 顯示了這樣的一個示例。
// audioSprite has already been loaded using a user touch event 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 } }; // play meow2 sprite audioSprite.currentTime = spriteData.meow2.start; audioSprite.play();
清單 13 將會播放 meow2 sprite,此外,由於沒有實現當 sprite 完成後即中止的邏輯,它還會播放一個發出嗚嗚聲的精靈。經過向清單 14 中的ontimeupdate
事件添加事件偵聽程序,還能夠觀看 currentTime
,並在 sprite 到達其結尾時結束此音頻。
var handler = function() { if (this.currentTime >= spriteData.meow2.start + spriteData.meow2.length) { this.pause(); } }; audioSprite.addEventListener('timeupdate', handler, false);
查看並收聽 本例的效果。
使用 audio sprite 的一個巨大好處是在 sprite 間切換時沒有延時(相似於在音頻流間切換時,假設整個 audio sprite 已經加載)。讓全部的流位於一個文件中也優於削減 HTTP 請求。
清注意,更改 currentTime
並非百分百正確的。將 currentTime
設爲 6.5,而實際獲得的倒是 6.7 或 6.2。每一個 A sprite 之間須要少許的空間,以免尋找到另外一個 sprite 的尾部。添加這個空間會增長少量延時,若是流尋找到 6.4,而 sprite 開始於 6.8 秒。
在訪問任何 sprite 以前,務必確保整個音頻流均已加載。這很重要,由於若是音頻流沒有徹底加載,那麼在想要訪問已加載的流的任何一個部分時,那麼這個流須要進行緩衝,並且還會在流加載過程當中發生延時。
查看並收聽 一個 audio sprite 框架的示例。這個示例考慮到了本文所討論的這些主題。
AppMobi 開發了一個有趣的解決方案,用 directCanvas 和 multiSound 克服移動設備上的各類 HTML5 侷限性(請參閱 參考資料)。directCanvas 和 multiSound 在一個標準的 HTML5 瀏覽器中應用程序使用了設備的固有功能。緩慢的圖片性能以及本文所討論的這些侷限性都再也不是問題;您能夠得到一個原生應用程序的所有性能益處。
當一個用戶導航到使用了 directCanvas 的站點時,頁面會提醒該用戶從 App Store 下載 MobiUs 應用程序。若是用戶已經在其設備上安裝了此應用程序,那麼頁面就會在 MobiUs 應用程序中打開。
AppMobi 的站點上有在 MobiUs 應用程序中運行遊戲與在移動版 Safari 中運行遊戲的並排對比視頻。結果非常驚人,性能得到了 10 倍的提高,如圖 4 所示。
AppMobi 的 API 站點上有一些不錯的文檔,因此您能夠當即查閱這些文檔。SDK 可免費下載得到,並且還提供一個很順手的 Google Chrome 擴展,可方便您在本身的桌面瀏覽器中進行開發。
雖然要求用戶在其設備上安裝一個應用程序並非很理想,但 AppMobi 有一個可以博得認同的有趣解決方案。目前,App Store 尚未提供 MobiUs 應用程序,但相信 MobiUs 很快就能夠上線了。
儘管本文討論了 HTML5 音頻的一些侷限性,可是 HTML5 音頻是對移動版 Safari 的一個很受歡迎的補充,您應該充分利用它。在本文中,咱們瞭解了 HTML5 音頻在桌面和移動版 Safari 上的限制,遍歷了這些限制的解決方案,並探討了在移動版 Safari 中使用 audio sprite 的優點。意識到移動版 Safari 的侷限性可幫助提高它對您的可用性。
做爲一個仍在制定中的規範,HTML5 音頻必然會不斷髮展,但沒有理由等到規範在 2014 年(推測)最後完成後才使用它。HTML5 音頻提供了對全部 iOS 用戶的幾乎全局的兼容性,沒有理由不使用它。