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