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