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