1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * @class WebAudio声音播放模块。它具有更好的声音播放和控制能力,适合在iOS6+平台使用。 9 * 兼容情况:iOS6+、Chrome33+、Firefox28+支持,但Android浏览器均不支持。 10 * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。 11 * @module hilo/media/WebAudio 12 * @requires hilo/core/Hilo 13 * @requires hilo/core/Class 14 * @requires hilo/event/EventMixin 15 * @property {String} src 播放的音频的资源地址。 16 * @property {Boolean} loop 是否循环播放。默认为false。 17 * @property {Boolean} autoPlay 是否自动播放。默认为false。 18 * @property {Boolean} loaded 音频资源是否已加载完成。只读属性。 19 * @property {Boolean} playing 是否正在播放音频。只读属性。 20 * @property {Number} duration 音频的时长。只读属性。 21 * @property {Number} volume 音量的大小。取值范围:0-1。 22 * @property {Boolean} muted 是否静音。默认为false。 23 */ 24 var WebAudio = (function(){ 25 26 var AudioContext = window.AudioContext || window.webkitAudioContext; 27 var context = AudioContext ? new AudioContext() : null; 28 29 return Class.create(/** @lends WebAudio.prototype */{ 30 Mixes: EventMixin, 31 constructor: function(properties){ 32 Hilo.copy(this, properties, true); 33 34 this._init(); 35 }, 36 37 src: null, 38 loop: false, 39 autoPlay: false, 40 loaded: false, 41 playing: false, 42 duration: 0, 43 volume: 1, 44 muted: false, 45 46 _context: null, //WebAudio上下文 47 _gainNode: null, //音量控制器 48 _buffer: null, //音频缓冲文件 49 _audioNode: null, //音频播放器 50 _startTime: 0, //开始播放时间戳 51 _offset: 0, //播放偏移量 52 53 /** 54 * @private 初始化 55 */ 56 _init:function(){ 57 this._context = context; 58 this._gainNode = context.createGain ? context.createGain() : context.createGainNode(); 59 this._gainNode.connect(context.destination); 60 61 this._onAudioEvent = this._onAudioEvent.bind(this); 62 this._onDecodeComplete = this._onDecodeComplete.bind(this); 63 this._onDecodeError = this._onDecodeError.bind(this); 64 }, 65 /** 66 * 加载音频文件。注意:我们使用XMLHttpRequest进行加载,因此需要注意跨域问题。 67 */ 68 load: function(){ 69 if(!this._buffer){ 70 var request = new XMLHttpRequest(); 71 request.src = this.src; 72 request.open('GET', this.src, true); 73 request.responseType = 'arraybuffer'; 74 request.onload = this._onAudioEvent; 75 request.onprogress = this._onAudioEvent; 76 request.onerror = this._onAudioEvent; 77 request.send(); 78 this._buffer = true; 79 } 80 return this; 81 }, 82 83 /** 84 * @private 85 */ 86 _onAudioEvent: function(e){ 87 // console.log('onAudioEvent:', e.type); 88 var type = e.type; 89 90 switch(type){ 91 case 'load': 92 var request = e.target; 93 request.onload = request.onprogress = request.onerror = null; 94 this._context.decodeAudioData(request.response, this._onDecodeComplete, this._onDecodeError); 95 request = null; 96 break; 97 case 'ended': 98 this.playing = false; 99 this.fire('end'); 100 if(this.loop) this._doPlay(); 101 break; 102 case 'progress': 103 this.fire(e); 104 break; 105 case 'error': 106 this.fire(e); 107 break; 108 } 109 }, 110 111 /** 112 * @private 113 */ 114 _onDecodeComplete: function(audioBuffer){ 115 this._buffer = audioBuffer; 116 this.loaded = true; 117 this.duration = audioBuffer.duration; 118 // console.log('onDecodeComplete:', audioBuffer.duration); 119 this.fire('load'); 120 if(this.autoPlay) this._doPlay(); 121 }, 122 123 /** 124 * @private 125 */ 126 _onDecodeError: function(){ 127 this.fire('error'); 128 }, 129 130 /** 131 * @private 132 */ 133 _doPlay: function(){ 134 this._clearAudioNode(); 135 136 var audioNode = this._context.createBufferSource(); 137 138 //some old browser are noteOn/noteOff -> start/stop 139 if(!audioNode.start){ 140 audioNode.start = audioNode.noteOn; 141 audioNode.stop = audioNode.noteOff; 142 } 143 144 audioNode.buffer = this._buffer; 145 audioNode.onended = this._onAudioEvent; 146 this._gainNode.gain.value = this.muted ? 0 : this.volume; 147 audioNode.connect(this._gainNode); 148 audioNode.start(0, this._offset); 149 150 this._audioNode = audioNode; 151 this._startTime = this._context.currentTime; 152 this.playing = true; 153 }, 154 155 /** 156 * @private 157 */ 158 _clearAudioNode: function(){ 159 var audioNode = this._audioNode; 160 if(audioNode){ 161 audioNode.onended = null; 162 // audioNode.disconnect(this._gainNode); 163 audioNode.disconnect(0); 164 this._audioNode = null; 165 } 166 }, 167 168 /** 169 * 播放音频。如果正在播放,则会重新开始。 170 */ 171 play: function(){ 172 if(this.playing) this.stop(); 173 174 if(this.loaded){ 175 this._doPlay(); 176 }else if(!this._buffer){ 177 this.autoPlay = true; 178 this.load(); 179 } 180 181 return this; 182 }, 183 184 /** 185 * 暂停音频。 186 */ 187 pause: function(){ 188 if(this.playing){ 189 this._audioNode.stop(0); 190 this._offset += this._context.currentTime - this._startTime; 191 this.playing = false; 192 } 193 return this; 194 }, 195 196 /** 197 * 恢复音频播放。 198 */ 199 resume: function(){ 200 if(!this.playing){ 201 this._doPlay(); 202 } 203 return this; 204 }, 205 206 /** 207 * 停止音频播放。 208 */ 209 stop: function(){ 210 if(this.playing){ 211 this._audioNode.stop(0); 212 this._audioNode.disconnect(); 213 this._offset = 0; 214 this.playing = false; 215 } 216 return this; 217 }, 218 219 /** 220 * 设置音量。 221 */ 222 setVolume: function(volume){ 223 if(this.volume != volume){ 224 this.volume = volume; 225 this._gainNode.gain.value = volume; 226 } 227 return this; 228 }, 229 230 /** 231 * 设置是否静音。 232 */ 233 setMute: function(muted){ 234 if(this.muted != muted){ 235 this.muted = muted; 236 this._gainNode.gain.value = muted ? 0 : this.volume; 237 } 238 return this; 239 }, 240 241 Statics: /** @lends WebAudio */ { 242 /** 243 * 浏览器是否支持WebAudio。 244 */ 245 isSupported: AudioContext != null, 246 247 /** 248 * 浏览器是否已激活WebAudio。 249 */ 250 enabled: false, 251 252 /** 253 * 激活WebAudio。注意:需用户事件触发此方法才有效。激活后,无需用户事件也可播放音频。 254 */ 255 enable: function(){ 256 if(!this.enabled && context){ 257 var source = context.createBufferSource(); 258 source.buffer = context.createBuffer(1, 1, 22050); 259 source.connect(context.destination); 260 source.start ? source.start(0, 0, 0) : source.noteOn(0, 0, 0); 261 this.enabled = true; 262 return true; 263 } 264 return this.enabled; 265 } 266 } 267 }); 268 269 })();