前言
html5中對音頻,視頻播放原生支持.最近做了一個音樂播放器,得益於快過年了,才能抽出一點時間來總結一下.總的來說,html5對audio的支持非常強大, 難怪flash要死.瀏覽器上裝播放插件的時代已經接近尾聲了.目前大多數瀏覽器都支持了大部分常用的api,ie8及其以前除外(萬惡的ie啊).
HTML5 音頻雖然很健壯,但有其局限性,這主要取決於它的實現。對於音樂播放器(點唱機播放器)或簡單的聲音效果,它很有效,但是對於聲音密集的應用程序如游戲,它的表現不是很理想。所以有了Web Audio API (Chrome) 和 Audio Data API (Firefox) 無需任何瀏覽器插件即可進行合成和動態處理音頻的能力來幫助人們解決特性缺失的問題.Web Audio與audio標簽不同,它給了開發者對音頻數據進行處理、分析的能力,例如混音、過濾等,類似於對音頻數據進行PS。一般的網站應用應該是用不倒這些API中的,但是一些游戲引擎或者在線音樂編輯等類型的網站應該用得到。請參考文章最后的參考資料.
兼容情況
可見除了ie6,7,8 和opera,其他都支持了.其中安卓在2.1就已經支持了.audio的兼容性還是不錯的,當然這只是audio標簽.JavaScript接口的支持情況就各不相同了,可能會遇到坑.
API
本來想貼上audio的api,后來發現太多了,就放棄了.有很多都是不常用的.我們這里就討論幾個常用的api和屬性.
詳情請參考w3c http://www.w3school.com.cn/tags/html_ref_audio_video_dom.asp
總結的過程中也發現audio和video的api是一模一樣的.這也方便了廣大開發者的學習使用.
重要方法
- 這3個方法是最基礎的了,看方法名就知道是干嘛用的了.其中load方法,一般是在改變audio的一些屬性后,需要重新加載的情況下使用.
重要屬性
- autoplay 設置是否自動播放音頻/視頻,ios的Safari下不能自動播放,需要和用戶交互后才能播放,這是個坑,后面會詳細說.
- buffered 會返回一個TimeRanges對象,包含一個起點和終點時間,如果用戶拖動音頻/視頻,則有多個buffer區間,可以通過index訪問指定buffer區間.
- currentTime 是非常重要的一個屬性了,可以返回當前的播放位置(以秒計),同時也可以設置播放位置,跳躍播放音頻.
- played 返回已經播放部分的TimeRanges對象,可以用於做一些計算
- seekable 表示用戶當前可以跳躍觀看的以緩沖部分區間
- src 音頻/視頻資源地址
- volume 控制音量
重要事件
- 加載過程的事件流對我們做一些特殊處理是很有幫助的
- canplay 加載ok,可以播放時觸發,一般邏輯都從這里開始
- canplaythrough 當全部加載完畢時觸發
- 正在下載時觸發,配合buffered屬性更新加載進度條的邏輯就可以放在這里
- 用戶跳躍播放時觸發
- 當前播放位置改變時觸發,更新播放時間,同步歌詞的邏輯都可以放在這里
封裝audio
從audio提供的api來說,已經非常強大和完善了.如果是在移動端,我們完全可以盡可能的使用原生api,沒必要使用第三方庫.我簡單封裝了一下audio,盡可能的保持原生api的最大靈活性前提下,減少我們的代碼量.
js代碼
;(function(){ var _init = function(){ var audio = this.audio = document.createElement('audio'); for( var prop in this.audioProp ){ audio[prop] = this.audioProp[prop]; } document.body.appendChild(audio); }; var _bindEvt = function(){ var audio = this.audio, audioEvt = this.audioEvt; for( var func in audioEvt ){ audio.addEventListener(func,audioEvt[func].bind(this),false); } }; var _extend = function(o1,o2){ for(var attr in o2) { o1[attr] = o2[attr]; } return o1; }; /* * 構造函數 */ function Constructor(opton){ opton = _extend({ audioProp : { }, audioEvt : { }, cssSelector : { } },opton); _extend(this,opton); _init.call(this); _bindEvt.call(this); } _extend(Constructor.prototype,{ /* * 播放 * second 指定當前的播放時間 */ play : function(second){ second && (this.audio.currentTime = second); this.audio.play(); }, /* * 暫停 * second 指定當前的播放時間 */ pause : function(second){ second && (this.audio.currentTime = second); this.audio.pause(); }, /* * 時間格式化 * 00:00 */ timeFormat : function timeFormat(number) { var minute = parseInt(number / 60); var second = parseInt(number % 60); minute = minute >= 10 ? minute : "0" + minute; second = second >= 10 ? second : "0" + second; return minute + ":" + second; } }); //兼容amd,cmd,原生js接口 if(typeof module !== 'undefined' && typeof exports === 'object' && define.cmd){ module.exports = Constructor; }else if(typeof define === 'function' && define.amd){ define(function(){ return Constructor; }); }else{ window.APlayer = Constructor; } })();
html代碼
var music = new APlayer({
audioProp : {
title : '給我一個理由忘記',
loop : true,
src : "https://ss1.baidu.com/8aQDcnSm2Q5IlBGlnYG/stat/ogg/xinsui.mp3",
},
audioEvt : {
canplay : function(){
},
timeupdate : function(){
$('.currentTime').html(this.timeFormat(this.audio.currentTime));
}
}
});
$(".play").on("touchend",function(){
music.play();
});
$(".pause").on("touchend",function(){
music.pause(5);
});
詳情可以看我的github https://github.com/Alan110/desire/tree/master/APlayer
坑爹集錦
編程總不會這么一帆風順,莫名其妙的問題總是不會少的.下面就羅列一下我遇到的坑吧.
1. 安卓uc瀏覽器下,對timeupdate事件支持不好,只會很少的觸發幾次.這是什么概念,就是說我們基本上不能同步當前的播放時間和進度條了,還有歌詞.
解決方案:暫時沒有
2. 在ios,Safari下,不能自動播放,autoplay,preload屬性無效. 或者audio.src='xx' , 加載完后手動調用audio.play()也是不行的.
原因: 在ios,Safari下要求與用戶交互后才加載歌曲,播放歌曲,如touchstart , touchend , click等事件.
解決方案 : 在iphone4下touchstart無效, 目測是因為性能不夠無法捕捉. 改用touchend就好了
3. ios,Safari同一時間只能播放但音頻/視頻
解決方案 : 使用 audio sprite 是滿足移動 Safari 中多音效需求的最佳解決方案。與 CSS image sprite 類似,audio sprite 可以將所有的音頻綜合到一個音頻流.要注意的是更改 currentTime
並不是百分百正確的。
將 currentTime
設為 6.5,而實際得到的卻是 6.7 或 6.2。每個 A sprite 之間需要少量的空間,以避免尋找到另一個 sprite 的尾部。

// 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();
記得播放完畢手,要手動停止

var handler = function() { if (this.currentTime >= spriteData.meow2.start + spriteData.meow2.length) { this.pause(); } }; audioSprite.addEventListener('timeupdate', handler, false);
音頻在低版本瀏覽器中的兼容
現代瀏覽器基本上都支持audio了,老版本的瀏覽器,如ie8及其以前的.兼容方案多是用flash來播放音頻/視頻.在頁面中嵌入embed標簽,或者object標簽,來調用flash插件.
網上找了2段代碼,手中無ie瀏覽器並不確定是否可用,看到的朋友慎重使用

<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0" width="1" height="1"> <param name="movie" value="flash/music.swf" /> <param name="quality" value="high" /> <embed src="flash/music.swf" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="1" height="1"></embed> </object>

if(navigator.userAgent.indexOf("Chrome") > -1){ 如果是Chrome: <audio src="" type="audio/mp3" autoplay=”autoplay” hidden="true"></audio> }else if(navigator.userAgent.indexOf("Firefox")!=-1){ 如果是Firefox: <embed src="" type="audio/mp3" hidden="true" loop="false" mastersound></embed> }else if(navigator.appName.indexOf("Microsoft Internet Explorer")!=-1 && document.all){ 如果是IE(6,7,8): <object classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95"><param name="AutoStart" value="1" /><param name="Src" value="" /></object> }else if(navigator.appName.indexOf("Opera")!=-1){ 如果是Oprea: <embed src="" type="audio/mpeg" loop="false"></embed> }else{ <embed src="" type="audio/mp3" hidden="true" loop="false" mastersound></embed> }
window.HTMLAudioElement 可以通過這個屬性來判斷是否支持audio
另外一個解決方案就是使用第三方庫了,這里推薦一個庫jplayer,據說是支持ie7+和其他的瀏覽器
總結
過年啦,祝大家新年快樂! 2016年希望我能成長得更多.
參考資料