1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 if (cc.sys._supportWebAudio) {
 28     var _ctx = cc.webAudioContext = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)();
 29     /**
 30      * A class of Web Audio.
 31      * @class
 32      * @extends cc.Class
 33      */
 34     cc.WebAudio = cc.Class.extend({
 35         _events: null,
 36         _buffer: null,
 37         _sourceNode: null,
 38         _volumeNode: null,
 39 
 40         src: null,
 41         preload: null,//"none" or "metadata" or "auto" or "" (empty string) or empty    TODO not used here
 42         autoplay: null,  //"autoplay" or "" (empty string) or empty
 43         controls: null,  //"controls" or "" (empty string) or empty    TODO not used here
 44         mediagroup: null,
 45 
 46         //The following IDL attributes and methods are exposed to dynamic scripts.
 47         currentTime: 0,
 48         startTime: 0,
 49         duration: 0,   //    TODO not used here
 50 
 51         _loop: null,      //"loop" or "" (empty string) or empty
 52         _volume: 1,
 53 
 54         _pauseTime: 0,
 55         _paused: false,
 56         _stopped: true,
 57 
 58         _loadState: -1,//-1 : not loaded, 0 : waiting, 1 : loaded, -2 : load failed
 59 
 60         ctor: function (src) {
 61             var self = this;
 62             self._events = {};
 63             self.src = src;
 64 
 65             if (_ctx["createGain"])
 66                 self._volumeNode = _ctx["createGain"]();
 67             else
 68                 self._volumeNode = _ctx["createGainNode"]();
 69 
 70             self._onSuccess1 = self._onSuccess.bind(this);
 71             self._onError1 = self._onError.bind(this);
 72         },
 73 
 74         _play: function (offset) {
 75             var self = this;
 76             var sourceNode = self._sourceNode = _ctx["createBufferSource"]();
 77             var volumeNode = self._volumeNode;
 78 
 79             sourceNode.buffer = self._buffer;
 80             volumeNode["gain"].value = self._volume;
 81             sourceNode["connect"](volumeNode);
 82             volumeNode["connect"](_ctx["destination"]);
 83             sourceNode.loop = self._loop;
 84 
 85             self._paused = false;
 86             self._stopped = false;
 87 
 88             /*
 89              * Safari on iOS 6 only supports noteOn(), noteGrainOn(), and noteOff() now.(iOS 6.1.3)
 90              * The latest version of chrome has supported start() and stop()
 91              * start() & stop() are specified in the latest specification (written on 04/26/2013)
 92              *      Reference: https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
 93              * noteOn(), noteGrainOn(), and noteOff() are specified in Draft 13 version (03/13/2012)
 94              *      Reference: http://www.w3.org/2011/audio/drafts/2WD/Overview.html
 95              */
 96             if (sourceNode.start) {
 97                 // starting from offset means resuming from where it paused last time
 98                 sourceNode.start(0, offset);
 99             } else if (sourceNode["noteGrainOn"]) {
100                 var duration = sourceNode.buffer.duration;
101                 if (self.loop) {
102                     /*
103                      * On Safari on iOS 6, if loop == true, the passed in @param duration will be the duration from now on.
104                      * In other words, the sound will keep playing the rest of the music all the time.
105                      * On latest chrome desktop version, the passed in duration will only be the duration in this cycle.
106                      * Now that latest chrome would have start() method, it is prepared for iOS here.
107                      */
108                     sourceNode["noteGrainOn"](0, offset, duration);
109                 } else {
110                     sourceNode["noteGrainOn"](0, offset, duration - offset);
111                 }
112             } else {
113                 // if only noteOn() is supported, resuming sound will NOT work
114                 sourceNode["noteOn"](0);
115             }
116             self._pauseTime = 0;
117         },
118         _stop: function () {
119             var self = this, sourceNode = self._sourceNode;
120             if (self._stopped) return;
121             if (sourceNode.stop) sourceNode.stop(0);
122             else sourceNode.noteOff(0);
123             self._stopped = true;
124         },
125         play: function () {
126             var self = this;
127             if (self._loadState == -1) {
128                 self._loadState = 0;
129                 return;
130             } else if (self._loadState != 1) return;
131 
132             var sourceNode = self._sourceNode;
133             if (!self._stopped && sourceNode && sourceNode["playbackState"] == 2) return;//playing
134             self.startTime = _ctx.currentTime;
135             this._play(0);
136         },
137         pause: function () {
138             this._pauseTime = _ctx.currentTime;
139             this._paused = true;
140             this._stop();
141         },
142         resume: function () {
143             var self = this;
144             if (self._paused) {
145                 var offset = self._buffer ? (self._pauseTime - self.startTime) % self._buffer.duration : 0;
146                 this._play(offset);
147             }
148         },
149         stop: function () {
150             this._pauseTime = 0;
151             this._paused = false;
152             this._stop();
153         },
154         load: function () {
155             var self = this;
156             if (self._loadState == 1) return;
157             self._loadState = -1;//not loaded
158 
159             self.played = false;
160             self.ended = true;
161             var request = new XMLHttpRequest();
162             request.open("GET", self.src, true);
163             request.responseType = "arraybuffer";
164 
165             // Our asynchronous callback
166             request.onload = function () {
167                 _ctx["decodeAudioData"](request.response, self._onSuccess1, self._onError1);
168             };
169             request.send();
170         },
171 
172         addEventListener: function (eventName, event) {
173             this._events[eventName] = event.bind(this);
174         },
175         removeEventListener: function (eventName) {
176             delete this._events[eventName];
177         },
178 
179         canplay: function () {
180             return cc.sys._supportWebAudio;
181         },
182         _onSuccess: function (buffer) {
183             var self = this;
184             self._buffer = buffer;
185 
186             var success = self._events["success"], canplaythrough = self._events["canplaythrough"];
187             if (success) success();
188             if (canplaythrough) canplaythrough();
189             if (self._loadState == 0 || self.autoplay == "autoplay" || self.autoplay == true) self._play();
190             self._loadState = 1;//loaded
191         },
192         _onError: function () {
193             var error = this._events["error"]
194             if (error) error();
195             this._loadState = -2;//load failed
196         },
197         cloneNode: function () {
198             var self = this, obj = new cc.WebAudio(self.src);
199             obj.volume = self.volume;
200             obj._loadState = self._loadState;
201             obj._buffer = self._buffer;
202             if (obj._loadState == 0 || obj._loadState == -1) obj.load();
203             return obj;
204         }
205 
206     });
207     var _p = cc.WebAudio.prototype;
208     /** @expose */
209     _p.loop;
210     cc.defineGetterSetter(_p, "loop", function () {
211         return this._loop;
212     }, function (loop) {
213         this._loop = loop;
214         if (this._sourceNode) this._sourceNode.loop = loop;
215     });
216     /** @expose */
217     _p.volume;
218     cc.defineGetterSetter(_p, "volume", function () {
219         return this._volume;
220     }, function (volume) {
221         this._volume = volume;
222         this._volumeNode["gain"].value = volume;
223     });
224     /** @expose */
225     _p.ended;
226     cc.defineGetterSetter(_p, "paused", function () {
227         return this._paused;
228     });
229     /** @expose */
230     _p.ended;
231     cc.defineGetterSetter(_p, "ended", function () {
232         var sourceNode = this._sourceNode;
233         return !this._paused && (this._stopped || !sourceNode || sourceNode["playbackState"] == 3);
234     });
235     /** @expose */
236     _p.played;
237     cc.defineGetterSetter(_p, "played", function () {
238         var sourceNode = this._sourceNode;
239         return sourceNode && sourceNode["playbackState"] == 2;
240     });
241 
242 }
243 
244 /**
245  * @namespace A simple Audio Engine engine API.
246  * @name cc.audioEngine
247  */
248 cc.AudioEngine = cc.Class.extend(/** @lends cc.audioEngine# */{
249     _soundSupported: false,      // if sound is not enabled, this engine's init() will return false
250 
251     _currMusic: null,
252     _currMusicPath: null,
253     _musicPlayState: 0, //0 : stopped, 1 : paused, 2 : playing
254 
255     _audioID: 0,
256     _effects: {},        //effects cache
257     _audioPool: {},    //audio pool for effects
258     _effectsVolume: 1,   // the volume applied to all effects
259     _maxAudioInstance: 5,//max count of audios that has same url
260 
261     _effectPauseCb: null,
262 
263     _playings: [],//only store when window is hidden
264 
265     ctor: function () {
266         var self = this;
267         self._soundSupported = cc._audioLoader._supportedAudioTypes.length > 0;
268         if (self._effectPauseCb) self._effectPauseCb = self._effectPauseCb.bind(self);
269     },
270 
271     /**
272      * Indicates whether any background music can be played or not.
273      * @returns {boolean} <i>true</i> if the background music is playing, otherwise <i>false</i>
274      */
275     willPlayMusic: function () {
276         return false;
277     },
278 
279     /**
280      * The volume of the effects max value is 1.0,the min value is 0.0 .
281      * @return {Number}
282      * @example
283      * //example
284      * var effectVolume = cc.audioEngine.getEffectsVolume();
285      */
286     getEffectsVolume: function () {
287         return this._effectsVolume;
288     },
289 
290     //music begin
291     /**
292      * Play music.
293      * @param {String} url The path of the music file without filename extension.
294      * @param {Boolean} loop Whether the music loop or not.
295      * @example
296      * //example
297      * cc.audioEngine.playMusic(path, false);
298      */
299     playMusic: function (url, loop) {
300         var self = this;
301         if (!self._soundSupported) return;
302 
303         var audio = self._currMusic;
304         if (audio) this._stopAudio(audio);
305         if (url != self._currMusicPath) {
306             audio = self._getAudioByUrl(url);
307             self._currMusic = audio;
308             self._currMusicPath = url;
309         }
310         if (!audio) return;
311         audio.loop = loop || false;
312         self._playMusic(audio);
313     },
314     _getAudioByUrl: function (url) {
315         var locLoader = cc.loader, audio = locLoader.getRes(url);
316         if (!audio) {
317             locLoader.load(url);
318             audio = locLoader.getRes(url);
319         }
320         return audio;
321     },
322     _playMusic: function (audio) {
323         if (!audio.ended) {
324             if (audio.stop) {//cc.WebAudio
325                 audio.stop();
326             } else {
327                 audio.pause();
328                 audio.duration && (audio.currentTime = audio.duration);
329             }
330         }
331         this._musicPlayState = 2;
332         audio.play();
333     },
334     /**
335      * Stop playing music.
336      * @param {Boolean} releaseData If release the music data or not.As default value is false.
337      * @example
338      * //example
339      * cc.audioEngine.stopMusic();
340      */
341     stopMusic: function (releaseData) {
342         if (this._musicPlayState > 0) {
343             var audio = this._currMusic;
344             if (!audio) return;
345             this._stopAudio(audio);
346             if (releaseData) cc.loader.release(this._currMusicPath);
347             this._currMusic = null;
348             this._currMusicPath = null;
349             this._musicPlayState = 0;
350         }
351     },
352 
353     _stopAudio: function (audio) {
354         if (audio && !audio.ended) {
355             if (audio.stop) {//cc.WebAudio
356                 audio.stop();
357             } else {
358                 audio.pause();
359                 audio.duration && (audio.currentTime = audio.duration);
360             }
361         }
362     },
363 
364     /**
365      * Pause playing music.
366      * @example
367      * //example
368      * cc.audioEngine.pauseMusic();
369      */
370     pauseMusic: function () {
371         if (this._musicPlayState == 2) {
372             this._currMusic.pause();
373             this._musicPlayState = 1;
374         }
375     },
376 
377     /**
378      * Resume playing music.
379      * @example
380      * //example
381      * cc.audioEngine.resumeMusic();
382      */
383     resumeMusic: function () {
384         if (this._musicPlayState == 1) {
385             var audio = this._currMusic;
386             this._resumeAudio(audio);
387             this._musicPlayState = 2;
388         }
389     },
390     _resumeAudio: function (audio) {
391         if (audio && !audio.ended) {
392             if (audio.resume) audio.resume();//cc.WebAudio
393             else audio.play();
394         }
395     },
396 
397     /**
398      * Rewind playing music.
399      * @example
400      * //example
401      * cc.audioEngine.rewindMusic();
402      */
403     rewindMusic: function () {
404         if (this._currMusic) this._playMusic(this._currMusic);
405     },
406 
407     /**
408      * The volume of the music max value is 1.0,the min value is 0.0 .
409      * @return {Number}
410      * @example
411      * //example
412      * var volume = cc.audioEngine.getMusicVolume();
413      */
414     getMusicVolume: function () {
415         return this._musicPlayState == 0 ? 0 : this._currMusic.volume;
416     },
417 
418     /**
419      * Set the volume of music.
420      * @param {Number} volume Volume must be in 0.0~1.0 .
421      * @example
422      * //example
423      * cc.audioEngine.setMusicVolume(0.5);
424      */
425     setMusicVolume: function (volume) {
426         if (this._musicPlayState > 0) {
427             this._currMusic.volume = Math.min(Math.max(volume, 0), 1);
428         }
429     },
430     /**
431      * Whether the music is playing.
432      * @return {Boolean} If is playing return true,or return false.
433      * @example
434      * //example
435      *  if (cc.audioEngine.isMusicPlaying()) {
436      *      cc.log("music is playing");
437      *  }
438      *  else {
439      *      cc.log("music is not playing");
440      *  }
441      */
442     isMusicPlaying: function () {
443         return this._musicPlayState == 2 && this._currMusic && !this._currMusic.ended;
444     },
445     //music end
446 
447     //effect begin
448     _getEffectList: function (url) {
449         var list = this._audioPool[url];
450         if (!list) list = this._audioPool[url] = [];
451         return list;
452     },
453     _getEffect: function (url) {
454         var self = this, audio;
455         if (!self._soundSupported) return null;
456 
457         var effList = this._getEffectList(url);
458         for (var i = 0, li = effList.length; i < li; i++) {
459             var eff = effList[i];
460             if (eff.ended) {
461                 audio = eff;
462                 audio.currentTime = 0;
463                 if (window.chrome) audio.load();
464                 break;
465             }
466         }
467         if (!audio) {
468             if (effList.length >= this._maxAudioInstance) {
469                 cc.log("Error: " + url + " greater than " + this._maxAudioInstance);
470                 return null;
471             }
472             audio = self._getAudioByUrl(url);
473             if (!audio) return null;
474             audio = audio.cloneNode(true);
475             if (self._effectPauseCb) cc._addEventListener(audio, "pause", self._effectPauseCb);
476             audio.volume = this._effectsVolume;
477             effList.push(audio);
478         }
479         return audio;
480     },
481     /**
482      * Play sound effect.
483      * @param {String} url The path of the sound effect with filename extension.
484      * @param {Boolean} loop Whether to loop the effect playing, default value is false
485      * @return {Number|null} the audio id
486      * @example
487      * //example
488      * var soundId = cc.audioEngine.playEffect(path);
489      */
490     playEffect: function (url, loop) {
491         var audio = this._getEffect(url);
492         if (!audio) return null;
493         audio.loop = loop || false;
494         audio.play();
495         var audioId = this._audioID++;
496         this._effects[audioId] = audio;
497         return audioId;
498     },
499 
500     /**
501      * Set the volume of sound effects.
502      * @param {Number} volume Volume must be in 0.0~1.0 .
503      * @example
504      * //example
505      * cc.audioEngine.setEffectsVolume(0.5);
506      */
507     setEffectsVolume: function (volume) {
508         volume = this._effectsVolume = Math.min(Math.max(volume, 0), 1);
509         var effects = this._effects;
510         for (var key in effects) {
511             effects[key].volume = volume;
512         }
513     },
514 
515     /**
516      * Pause playing sound effect.
517      * @param {Number} audioID The return value of function playEffect.
518      * @example
519      * //example
520      * cc.audioEngine.pauseEffect(audioID);
521      */
522     pauseEffect: function (audioID) {
523         var audio = this._effects[audioID];
524         if (audio && !audio.ended) {
525             audio.pause();
526         }
527     },
528 
529     /**
530      * Pause all playing sound effect.
531      * @example
532      * //example
533      * cc.audioEngine.pauseAllEffects();
534      */
535     pauseAllEffects: function () {
536         var effects = this._effects;
537         for (var key in effects) {
538             var eff = effects[key];
539             if (!eff.ended) eff.pause();
540         }
541     },
542 
543     /**
544      * Resume playing sound effect.
545      * @param {Number} effectId The return value of function playEffect.
546      * @audioID
547      * //example
548      * cc.audioEngine.resumeEffect(audioID);
549      */
550     resumeEffect: function (effectId) {
551         this._resumeAudio(this._effects[effectId])
552     },
553 
554     /**
555      * Resume all playing sound effect
556      * @example
557      * //example
558      * cc.audioEngine.resumeAllEffects();
559      */
560     resumeAllEffects: function () {
561         var effects = this._effects;
562         for (var key in effects) {
563             this._resumeAudio(effects[key]);
564         }
565     },
566 
567     /**
568      * Stop playing sound effect.
569      * @param {Number} effectId The return value of function playEffect.
570      * @example
571      * //example
572      * cc.audioEngine.stopEffect(audioID);
573      */
574     stopEffect: function (effectId) {
575         this._stopAudio(this._effects[effectId]);
576         delete this._effects[effectId];
577     },
578 
579     /**
580      * Stop all playing sound effects.
581      * @example
582      * //example
583      * cc.audioEngine.stopAllEffects();
584      */
585     stopAllEffects: function () {
586         var effects = this._effects;
587         for (var key in effects) {
588             this._stopAudio(effects[key]);
589             delete effects[key];
590         }
591     },
592 
593     /**
594      * Unload the preloaded effect from internal buffer
595      * @param {String} url
596      * @example
597      * //example
598      * cc.audioEngine.unloadEffect(EFFECT_FILE);
599      */
600     unloadEffect: function (url) {
601         var locLoader = cc.loader, locEffects = this._effects, effectList = this._getEffectList(url);
602         locLoader.release(url);//release the resource in cc.loader first.
603         if (effectList.length == 0) return;
604         var realUrl = effectList[0].src;
605         delete this._audioPool[url];
606         for (var key in locEffects) {
607             if (locEffects[key].src == realUrl) {
608                 this._stopAudio(locEffects[key]);
609                 delete locEffects[key];
610             }
611         }
612     },
613     //effect end
614 
615     /**
616      * End music and effects.
617      */
618     end: function () {
619         this.stopMusic();
620         this.stopAllEffects();
621     },
622 
623     /**
624      * Called only when the hidden event of window occurs.
625      * @private
626      */
627     _pausePlaying: function () {//in this function, do not change any status of audios
628         var self = this, effects = self._effects, eff;
629         for (var key in effects) {
630             eff = effects[key];
631             if (eff && !eff.ended && !eff.paused) {
632                 self._playings.push(eff);
633                 eff.pause();
634             }
635         }
636         if (self.isMusicPlaying()) {
637             self._playings.push(self._currMusic);
638             self._currMusic.pause();
639         }
640     },
641     /**
642      * Called only when the hidden event of window occurs.
643      * @private
644      */
645     _resumePlaying: function () {//in this function, do not change any status of audios
646         var self = this, playings = this._playings;
647         for (var i = 0, li = playings.length; i < li; i++) {
648             self._resumeAudio(playings[i]);
649         }
650         playings.length = 0;
651     }
652 
653 });
654 
655 
656 if (!cc.sys._supportWebAudio && cc.sys._supportMultipleAudio < 0) {
657     /**
658      * Extended AudioEngine for single audio mode.
659      * @class
660      */
661     cc.AudioEngineForSingle = cc.AudioEngine.extend({
662         _waitingEffIds: [],
663         _pausedEffIds: [],
664         _currEffect: null,
665         _maxAudioInstance: 2,
666         _effectCache4Single: {},//{url:audio},
667         _needToResumeMusic: false,
668         _expendTime4Music: 0,
669 
670         _isHiddenMode: false,
671 
672         _playMusic: function (audio) {
673             this._stopAllEffects();
674             this._super(audio);
675         },
676 
677         resumeMusic: function () {
678             var self = this;
679             if (self._musicPlayState == 1) {
680                 self._stopAllEffects();
681                 self._needToResumeMusic = false;
682                 self._expendTime4Music = 0;
683                 self._super();
684             }
685         },
686 
687         playEffect: function (url, loop) {
688             var self = this, currEffect = self._currEffect;
689             var audio = loop ? self._getEffect(url) : self._getSingleEffect(url);
690             if (!audio) return null;
691             audio.loop = loop || false;
692             var audioId = self._audioID++;
693             self._effects[audioId] = audio;
694 
695             if (self.isMusicPlaying()) {
696                 self.pauseMusic();
697                 self._needToResumeMusic = true;
698             }
699             if (currEffect) {
700                 if (currEffect != audio) self._waitingEffIds.push(self._currEffectId);
701                 self._waitingEffIds.push(audioId);
702                 currEffect.pause();
703             } else {
704                 self._currEffect = audio;
705                 self._currEffectId = audioId;
706                 audio.play();
707             }
708             return audioId;
709         },
710         pauseEffect: function (effectId) {
711             cc.log("pauseEffect not supported in single audio mode!")
712 //        var currEffect = this._currEffect;
713 //        if(this._currEffectId != effectId || !currEffect || currEffect.ended) return;
714 //        this._pausedEffIds.push(this._currEffectId);
715 //        currEffect.pause();
716         },
717         pauseAllEffects: function () {
718             var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds, currEffect = self._currEffect;
719             if (!currEffect) return;
720             for (var i = 0, li = waitings.length; i < li; i++) {
721                 pauseds.push(waitings[i]);
722             }
723             waitings.length = 0;//clear
724             pauseds.push(self._currEffectId);
725             currEffect.pause();
726         },
727         resumeEffect: function (effectId) {
728             cc.log("resumeEffect not supported in single audio mode!")
729 //        var self = this, currEffect = self._currEffect, pauseds = self._pausedEffIds;
730 //        if(self._currEffectId == effectId) return;//the effect is playing
731 //        var index = pauseds.indexOf(effectId);
732 //        if(index >= 0){
733 //            pauseds.splice(index, 1);//delete item
734 //            var eff = self._effects[effectId];
735 //            var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0;
736 //            if(eff && eff.currentTime + expendTime < eff.duration) {
737 //                self._waitingEffIds.push(self._currEffectId);
738 //                self._waitingEffIds.push(effectId);
739 //                self._currEffect.pause();
740 //            }
741 //        }
742         },
743         resumeAllEffects: function () {
744             var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
745 
746             if (self.isMusicPlaying()) {//if music is playing, pause it first
747                 self.pauseMusic();
748                 self._needToResumeMusic = true;
749             }
750 
751             for (var i = 0, li = pauseds.length; i < li; i++) {//move pauseds to waitings
752                 waitings.push(pauseds[i]);
753             }
754             pauseds.length = 0;//clear
755             if (!self._currEffect && waitings.length >= 0) {//is none currEff, resume the newest effect in waitings
756                 var effId = waitings.pop();
757                 var eff = self._effects[effId];
758                 if (eff) {
759                     self._currEffectId = effId;
760                     self._currEffect = eff;
761                     self._resumeAudio(eff);
762                 }
763             }
764         },
765         stopEffect: function (effectId) {
766             var self = this, currEffect = self._currEffect, waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
767             if (currEffect && this._currEffectId == effectId) {//if the eff to be stopped is currEff
768                 this._stopAudio(currEffect);
769             } else {//delete from waitings or pauseds
770                 var index = waitings.indexOf(effectId);
771                 if (index >= 0) {
772                     waitings.splice(index, 1);
773                 } else {
774                     index = pauseds.indexOf(effectId);
775                     if (index >= 0) pauseds.splice(index, 1);
776                 }
777             }
778         },
779         stopAllEffects: function () {
780             var self = this;
781             self._stopAllEffects();
782             if (!self._currEffect && self._needToResumeMusic) {//need to resume music
783                 self._resumeAudio(self._currMusic);
784                 self._musicPlayState = 2;
785                 self._needToResumeMusic = false;
786                 self._expendTime4Music = 0;
787             }
788         },
789 
790         unloadEffect: function (url) {
791             var self = this, locLoader = cc.loader, locEffects = self._effects, effCache = self._effectCache4Single,
792                 effectList = self._getEffectList(url), currEffect = self._currEffect;
793             locLoader.release(url);//release the resource in cc.loader first.
794             if (effectList.length == 0 && !effCache[url]) return;
795             var realUrl = effectList.length > 0 ? effectList[0].src : effCache[url].src;
796             delete self._audioPool[url];
797             delete effCache[url];
798             for (var key in locEffects) {
799                 if (locEffects[key].src == realUrl) {
800                     delete locEffects[key];
801                 }
802             }
803             if (currEffect && currEffect.src == realUrl) self._stopAudio(currEffect);//need to stop currEff
804         },
805 
806         /**
807          * When `loop == false`, one url one audio.
808          * @param url
809          * @returns {*}
810          * @private
811          */
812         _getSingleEffect: function (url) {
813             var self = this, audio = self._effectCache4Single[url], locLoader = cc.loader,
814                 waitings = self._waitingEffIds, pauseds = self._pausedEffIds, effects = self._effects;
815             if (audio) {
816                 audio.duration && (audio.currentTime = 0);//reset current time
817             } else {
818                 audio = self._getAudioByUrl(url);
819                 if (!audio) return null;
820                 audio = audio.cloneNode(true);
821                 if (self._effectPauseCb)
822                     cc._addEventListener(audio, "pause", self._effectPauseCb);
823                 audio.volume = self._effectsVolume;
824                 self._effectCache4Single[url] = audio;
825             }
826             for (var i = 0, li = waitings.length; i < li;) {//reset waitings
827                 if (effects[waitings[i]] == audio) {
828                     waitings.splice(i, 1);
829                 } else i++;
830             }
831             for (var i = 0, li = pauseds.length; i < li;) {//reset pauseds
832                 if (effects[pauseds[i]] == audio) {
833                     pauseds.splice(i, 1);
834                 } else i++;
835             }
836             audio._isToPlay = true;//custom flag
837             return audio;
838         },
839         _stopAllEffects: function () {
840             var self = this, currEffect = self._currEffect, audioPool = self._audioPool, sglCache = self._effectCache4Single,
841                 waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
842             if (!currEffect && waitings.length == 0 && pauseds.length == 0) return;
843             for (var key in sglCache) {
844                 var eff = sglCache[key];
845                 eff.duration && (eff.currentTime = eff.duration);
846             }
847             waitings.length = 0;
848             pauseds.length = 0;
849             for (var key in audioPool) {//reset audios in pool to be ended
850                 var list = audioPool[key];
851                 for (var i = 0, li = list.length; i < li; i++) {
852                     var eff = list[i];
853                     eff.loop = false;
854                     eff.duration && (eff.currentTime = eff.duration);
855                 }
856             }
857             if (currEffect) self._stopAudio(currEffect);
858         },
859         _effectPauseCb: function () {
860             var self = this;
861             if (self._isHiddenMode) return;//in this mode, return
862             var currEffect = self._getWaitingEffToPlay();//get eff to play
863             if (currEffect) {
864                 if (currEffect._isToPlay) {
865                     delete currEffect._isToPlay;
866                     currEffect.play();
867                 }
868                 else self._resumeAudio(currEffect);
869             } else if (self._needToResumeMusic) {
870                 var currMusic = self._currMusic;
871                 if (currMusic.duration) {//calculate current time
872                     var temp = currMusic.currentTime + self._expendTime4Music;
873                     temp = temp - currMusic.duration * ((temp / currMusic.duration) | 0);
874                     currMusic.currentTime = temp;
875                 }
876                 self._expendTime4Music = 0;
877                 self._resumeAudio(currMusic);
878                 self._musicPlayState = 2;
879                 self._needToResumeMusic = false;
880             }
881             ;
882         },
883         _getWaitingEffToPlay: function () {
884             var self = this, waitings = self._waitingEffIds, effects = self._effects,
885                 currEffect = self._currEffect;
886 
887             var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0;
888             self._expendTime4Music += expendTime;
889 
890             while (true) {//get a audio to play
891                 if (waitings.length == 0) break;
892                 var effId = waitings.pop();
893                 var eff = effects[effId];
894                 if (!eff) continue;
895                 if (eff._isToPlay || eff.loop || (eff.duration && eff.currentTime + expendTime < eff.duration)) {
896                     self._currEffectId = effId;
897                     self._currEffect = eff;
898                     if (!eff._isToPlay && eff.duration) {
899                         var temp = eff.currentTime + expendTime;
900                         temp = temp - eff.duration * ((temp / eff.duration) | 0);
901                         eff.currentTime = temp;
902                     }
903                     eff._isToPlay = false;
904                     return eff;
905                 } else {
906                     eff.duration && (eff.currentTime = eff.duration);
907                 }
908             }
909             self._currEffectId = null;
910             self._currEffect = null;
911             return null;
912         },
913 
914         _pausePlaying: function () {//in this function, do not change any status of audios
915             var self = this, currEffect = self._currEffect;
916             self._isHiddenMode = true;
917             var audio = self._musicPlayState == 2 ? self._currMusic : currEffect;
918             if (audio) {
919                 self._playings.push(audio);
920                 audio.pause();
921             }
922 
923         },
924         _resumePlaying: function () {//in this function, do not change any status of audios
925             var self = this, playings = self._playings;
926             self._isHiddenMode = false;
927             if (playings.length > 0) {
928                 self._resumeAudio(playings[0]);
929                 playings.length = 0;
930             }
931         }
932 
933     });
934 }
935 
936 /**
937  * Resource loader for audio.
938  */
939 cc._audioLoader = {
940     _supportedAudioTypes: null,
941     getBasePath: function () {
942         return cc.loader.audioPath;
943     },
944     _load: function (realUrl, url, res, count, tryArr, audio, cb) {
945         var self = this, locLoader = cc.loader, path = cc.path;
946         var types = this._supportedAudioTypes;
947         var extname = "";
948         if (types.length == 0) return cb("can not support audio!");
949         if (count == -1) {
950             extname = (path.extname(realUrl) || "").toLowerCase();
951             if (!self.audioTypeSupported(extname)) {
952                 extname = types[0];
953                 count = 0;
954             }
955         } else if (count < types.length) {
956             extname = types[count];
957         } else {
958             return cb("can not found the resource of audio! Last match url is : " + realUrl);
959         }
960         if (tryArr.indexOf(extname) >= 0) return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);
961         realUrl = path.changeExtname(realUrl, extname);
962         tryArr.push(extname);
963         audio = self._loadAudio(realUrl, audio, function (err) {
964             if (err) return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);//can not found
965             cb(null, audio);
966         });
967         locLoader.cache[url] = audio;
968     },
969 
970     audioTypeSupported: function (type) {
971         if (!type) return false;
972         return this._supportedAudioTypes.indexOf(type.toLowerCase()) >= 0;
973     },
974     _loadAudio: function (url, audio, cb) {
975         var _Audio = cc.WebAudio || Audio;
976         if (arguments.length == 2) {
977             cb = audio, audio = new _Audio();
978         } else if (arguments.length == 3 && !audio) {
979             audio = new _Audio();
980         }
981         audio.src = url;
982         audio.preload = "auto";
983 
984         var ua = navigator.userAgent;
985         if (/Mobile/.test(ua) && (/iPhone OS/.test(ua) || /iPad/.test(ua) || /Firefox/.test(ua)) || /MSIE/.test(ua)) {
986             audio.load();
987             cb(null, audio);
988         } else {
989             var canplaythrough = "canplaythrough", error = "error";
990             cc._addEventListener(audio, canplaythrough, function () {
991                 cb(null, audio);
992                 this.removeEventListener(canplaythrough, arguments.callee, false);
993                 this.removeEventListener(error, arguments.callee, false);
994             }, false);
995             cc._addEventListener(audio, error, function () {
996                 cb("load " + url + " failed")
997                 this.removeEventListener(canplaythrough, arguments.callee, false);
998                 this.removeEventListener(error, arguments.callee, false);
999             }, false);
1000             audio.load();
1001         }
1002         return audio;
1003     },
1004     load: function (realUrl, url, res, cb) {
1005         var tryArr = [];
1006         this._load(realUrl, url, res, -1, tryArr, null, cb);
1007     }
1008 };
1009 cc._audioLoader._supportedAudioTypes = function () {
1010     var au = cc.newElement('audio'), arr = [];
1011     ;
1012     if (au.canPlayType) {
1013         // <audio> tag is supported, go on
1014         var _check = function (typeStr) {
1015             var result = au.canPlayType(typeStr);
1016             return result != "no" && result != "";
1017         };
1018         if (_check('audio/ogg; codecs="vorbis"')) arr.push(".ogg");
1019         if (_check("audio/mpeg")) arr.push(".mp3");
1020         if (_check('audio/wav; codecs="1"')) arr.push(".wav");
1021         if (_check("audio/mp4")) arr.push(".mp4");
1022         if (_check("audio/x-m4a") || _check("audio/aac")) arr.push(".m4a");
1023     }
1024     return arr;
1025 }();
1026 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], cc._audioLoader);
1027 
1028 // Initialize Audio engine singleton
1029 cc.audioEngine = cc.AudioEngineForSingle ? new cc.AudioEngineForSingle() : new cc.AudioEngine();
1030 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () {
1031     cc.audioEngine._pausePlaying();
1032 });
1033 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () {
1034     cc.audioEngine._resumePlaying();
1035 });
1036