1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * @language=en 9 * @class WebAudio audio playing module. It provides a better way to play and control audio, use on iOS6+ platform. 10 * Compatibility:iOS6+、Chrome33+、Firefox28+ supported,but all Android browsers do not support. 11 * @param {Object} properties create object properties, include all writable properties of this class. 12 * @module hilo/media/WebAudio 13 * @requires hilo/core/Hilo 14 * @requires hilo/core/Class 15 * @requires hilo/event/EventMixin 16 * @property {String} src The source of the playing audio. 17 * @property {Boolean} loop Is loop playback, default value is false. 18 * @property {Boolean} autoPlay Is the audio autoplay, default value is false. 19 * @property {Boolean} loaded Is the audio resource loaded, readonly! 20 * @property {Boolean} playing Is the audio playing, readonly! 21 * @property {Number} duration The duration of the audio, readonly! 22 * @property {Number} volume The volume of the audio, value between 0 to 1. 23 * @property {Boolean} muted Is the audio muted, default value is false. 24 */ 25 var WebAudio = (function(){ 26 27 var AudioContext = window.AudioContext || window.webkitAudioContext; 28 var context = AudioContext ? new AudioContext() : null; 29 30 return Class.create(/** @lends WebAudio.prototype */{ 31 Mixes: EventMixin, 32 constructor: function(properties){ 33 Hilo.copy(this, properties, true); 34 35 this._init(); 36 }, 37 38 src: null, 39 loop: false, 40 autoPlay: false, 41 loaded: false, 42 playing: false, 43 duration: 0, 44 volume: 1, 45 muted: false, 46 47 _context: null, //WebAudio上下文 the WebAudio Context 48 _gainNode: null, //音量控制器 the volume controller 49 _buffer: null, //音频缓冲文件 the audio file buffer 50 _audioNode: null, //音频播放器 the audio playing node 51 _startTime: 0, //开始播放时间戳 the start time to play the audio 52 _offset: 0, //播放偏移量 the offset of current playing audio 53 54 /** 55 * @language=en 56 * @private Initialize. 57 */ 58 _init:function(){ 59 this._context = context; 60 this._gainNode = context.createGain ? context.createGain() : context.createGainNode(); 61 this._gainNode.connect(context.destination); 62 63 this._onAudioEvent = this._onAudioEvent.bind(this); 64 this._onDecodeComplete = this._onDecodeComplete.bind(this); 65 this._onDecodeError = this._onDecodeError.bind(this); 66 }, 67 /** 68 * @language=en 69 * Load audio file. Note: use XMLHttpRequest to load the audio, should pay attention to cross-origin problem. 70 */ 71 load: function(){ 72 if(!this._buffer){ 73 var request = new XMLHttpRequest(); 74 request.src = this.src; 75 request.open('GET', this.src, true); 76 request.responseType = 'arraybuffer'; 77 request.onload = this._onAudioEvent; 78 request.onprogress = this._onAudioEvent; 79 request.onerror = this._onAudioEvent; 80 request.send(); 81 this._buffer = true; 82 } 83 return this; 84 }, 85 86 /** 87 * @language=en 88 * @private 89 */ 90 _onAudioEvent: function(e){ 91 // console.log('onAudioEvent:', e.type); 92 var type = e.type; 93 94 switch(type){ 95 case 'load': 96 var request = e.target; 97 request.onload = request.onprogress = request.onerror = null; 98 this._context.decodeAudioData(request.response, this._onDecodeComplete, this._onDecodeError); 99 request = null; 100 break; 101 case 'ended': 102 this.playing = false; 103 this.fire('end'); 104 if(this.loop) this._doPlay(); 105 break; 106 case 'progress': 107 this.fire(e); 108 break; 109 case 'error': 110 this.fire(e); 111 break; 112 } 113 }, 114 115 /** 116 * @language=en 117 * @private 118 */ 119 _onDecodeComplete: function(audioBuffer){ 120 this._buffer = audioBuffer; 121 this.loaded = true; 122 this.duration = audioBuffer.duration; 123 // console.log('onDecodeComplete:', audioBuffer.duration); 124 this.fire('load'); 125 if(this.autoPlay) this._doPlay(); 126 }, 127 128 /** 129 * @language=en 130 * @private 131 */ 132 _onDecodeError: function(){ 133 this.fire('error'); 134 }, 135 136 /** 137 * @language=en 138 * @private 139 */ 140 _doPlay: function(){ 141 this._clearAudioNode(); 142 143 var audioNode = this._context.createBufferSource(); 144 145 //some old browser are noteOn/noteOff -> start/stop 146 if(!audioNode.start){ 147 audioNode.start = audioNode.noteOn; 148 audioNode.stop = audioNode.noteOff; 149 } 150 151 audioNode.buffer = this._buffer; 152 audioNode.onended = this._onAudioEvent; 153 this._gainNode.gain.value = this.muted ? 0 : this.volume; 154 audioNode.connect(this._gainNode); 155 audioNode.start(0, this._offset); 156 157 this._audioNode = audioNode; 158 this._startTime = this._context.currentTime; 159 this.playing = true; 160 }, 161 162 /** 163 * @language=en 164 * @private 165 */ 166 _clearAudioNode: function(){ 167 var audioNode = this._audioNode; 168 if(audioNode){ 169 audioNode.onended = null; 170 // audioNode.disconnect(this._gainNode); 171 audioNode.disconnect(0); 172 this._audioNode = null; 173 } 174 }, 175 176 /** 177 * @language=en 178 * Play the audio. Restart playing the audio from the beginning if already playing. 179 */ 180 play: function(){ 181 if(this.playing) this.stop(); 182 183 if(this.loaded){ 184 this._doPlay(); 185 }else if(!this._buffer){ 186 this.autoPlay = true; 187 this.load(); 188 } 189 190 return this; 191 }, 192 193 /** 194 * @language=en 195 * Pause (halt) playing the audio. 196 */ 197 pause: function(){ 198 if(this.playing){ 199 this._audioNode.stop(0); 200 this._offset += this._context.currentTime - this._startTime; 201 this.playing = false; 202 } 203 return this; 204 }, 205 206 /** 207 * @language=en 208 * Continue to play the audio. 209 */ 210 resume: function(){ 211 if(!this.playing){ 212 this._doPlay(); 213 } 214 return this; 215 }, 216 217 /** 218 * @language=en 219 * Stop playing the audio. 220 */ 221 stop: function(){ 222 if(this.playing){ 223 this._audioNode.stop(0); 224 this._audioNode.disconnect(); 225 this._offset = 0; 226 this.playing = false; 227 } 228 return this; 229 }, 230 231 /** 232 * @language=en 233 * Set the volume. 234 */ 235 setVolume: function(volume){ 236 if(this.volume != volume){ 237 this.volume = volume; 238 this._gainNode.gain.value = volume; 239 } 240 return this; 241 }, 242 243 /** 244 * @language=en 245 * Set mute mode. 246 */ 247 setMute: function(muted){ 248 if(this.muted != muted){ 249 this.muted = muted; 250 this._gainNode.gain.value = muted ? 0 : this.volume; 251 } 252 return this; 253 }, 254 255 Statics: /** @lends WebAudio */ { 256 /** 257 * @language=en 258 * Does the browser support WebAudio. 259 */ 260 isSupported: AudioContext != null, 261 262 /** 263 * @language=en 264 * Does browser activate WebAudio already. 265 */ 266 enabled: false, 267 268 /** 269 * @language=en 270 * Activate WebAudio. Note: Require user action events to activate. Once activated, can play audio without user action events. 271 */ 272 enable: function(){ 273 if(!this.enabled && context){ 274 var source = context.createBufferSource(); 275 source.buffer = context.createBuffer(1, 1, 22050); 276 source.connect(context.destination); 277 source.start ? source.start(0, 0, 0) : source.noteOn(0, 0, 0); 278 this.enabled = true; 279 return true; 280 } 281 return this.enabled; 282 } 283 } 284 }); 285 286 })();