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