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 
777 if (!cc.sys._supportWebAudio && cc.sys._supportMultipleAudio < 0) {
778 
779     cc.AudioEngineForSingle = cc.AudioEngine.extend({
780         _waitingEffIds: [],
781         _pausedEffIds: [],
782         _currEffect: null,
783         _maxAudioInstance: 2,
784         _effectCache4Single: {},//{url:audio},
785         _needToResumeMusic: false,
786         _expendTime4Music: 0,
787 
788         _isHiddenMode: false,
789 
790         _playMusic: function (audio) {
791             this._stopAllEffects();
792             this._super(audio);
793         },
794 
795         resumeMusic: function () {
796             var self = this;
797             if (self._musicPlayState == 1) {
798                 self._stopAllEffects();
799                 self._needToResumeMusic = false;
800                 self._expendTime4Music = 0;
801                 self._super();
802             }
803         },
804 
805         playEffect: function (url, loop) {
806             var self = this, currEffect = self._currEffect;
807             var audio = loop ? self._getEffect(url) : self._getSingleEffect(url);
808             if (!audio) return null;
809             audio.loop = loop || false;
810             var audioId = self._audioID++;
811             self._effects[audioId] = audio;
812 
813             if (self.isMusicPlaying()) {
814                 self.pauseMusic();
815                 self._needToResumeMusic = true;
816             }
817             if (currEffect) {
818                 if (currEffect != audio) self._waitingEffIds.push(self._currEffectId);
819                 self._waitingEffIds.push(audioId);
820                 currEffect.pause();
821             } else {
822                 self._currEffect = audio;
823                 self._currEffectId = audioId;
824                 audio.play();
825             }
826             return audioId;
827         },
828 
829         pauseEffect: function (effectId) {
830             cc.log("pauseEffect not supported in single audio mode!");
831         },
832 
833         pauseAllEffects: function () {
834             var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds, currEffect = self._currEffect;
835             if (!currEffect) return;
836             for (var i = 0, li = waitings.length; i < li; i++) {
837                 pauseds.push(waitings[i]);
838             }
839             waitings.length = 0;//clear
840             pauseds.push(self._currEffectId);
841             currEffect.pause();
842         },
843 
844         resumeEffect: function (effectId) {
845             cc.log("resumeEffect not supported in single audio mode!");
846         },
847 
848         resumeAllEffects: function () {
849             var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
850 
851             if (self.isMusicPlaying()) {//if music is playing, pause it first
852                 self.pauseMusic();
853                 self._needToResumeMusic = true;
854             }
855 
856             for (var i = 0, li = pauseds.length; i < li; i++) {//move pauseds to waitings
857                 waitings.push(pauseds[i]);
858             }
859             pauseds.length = 0;//clear
860             if (!self._currEffect && waitings.length >= 0) {//is none currEff, resume the newest effect in waitings
861                 var effId = waitings.pop();
862                 var eff = self._effects[effId];
863                 if (eff) {
864                     self._currEffectId = effId;
865                     self._currEffect = eff;
866                     self._resumeAudio(eff);
867                 }
868             }
869         },
870 
871         stopEffect: function (effectId) {
872             var self = this, currEffect = self._currEffect, waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
873             if (currEffect && this._currEffectId == effectId) {//if the eff to be stopped is currEff
874                 this._stopAudio(currEffect);
875             } else {//delete from waitings or pauseds
876                 var index = waitings.indexOf(effectId);
877                 if (index >= 0) {
878                     waitings.splice(index, 1);
879                 } else {
880                     index = pauseds.indexOf(effectId);
881                     if (index >= 0) pauseds.splice(index, 1);
882                 }
883             }
884         },
885 
886         stopAllEffects: function () {
887             var self = this;
888             self._stopAllEffects();
889             if (!self._currEffect && self._needToResumeMusic) {//need to resume music
890                 self._resumeAudio(self._currMusic);
891                 self._musicPlayState = 2;
892                 self._needToResumeMusic = false;
893                 self._expendTime4Music = 0;
894             }
895         },
896 
897         unloadEffect: function (url) {
898             var self = this, locLoader = cc.loader, locEffects = self._effects, effCache = self._effectCache4Single,
899                 effectList = self._getEffectList(url), currEffect = self._currEffect;
900             locLoader.release(url);//release the resource in cc.loader first.
901             if (effectList.length == 0 && !effCache[url]) return;
902             var realUrl = effectList.length > 0 ? effectList[0].src : effCache[url].src;
903             delete self._audioPool[url];
904             delete effCache[url];
905             for (var key in locEffects) {
906                 if (locEffects[key].src == realUrl) {
907                     delete locEffects[key];
908                 }
909             }
910             if (currEffect && currEffect.src == realUrl) self._stopAudio(currEffect);//need to stop currEff
911         },
912 
913         //When `loop == false`, one url one audio.
914         _getSingleEffect: function (url) {
915             var self = this, audio = self._effectCache4Single[url], locLoader = cc.loader,
916                 waitings = self._waitingEffIds, pauseds = self._pausedEffIds, effects = self._effects;
917             if (audio) {
918                 if (audio.readyState > 2)
919                     audio.currentTime = 0;                          //reset current time
920             } else {
921                 audio = self._getAudioByUrl(url);
922                 if (!audio) return null;
923                 audio = audio.cloneNode(true);
924                 if (self._effectPauseCb)
925                     cc._addEventListener(audio, "pause", self._effectPauseCb);
926                 audio.volume = self._effectsVolume;
927                 self._effectCache4Single[url] = audio;
928             }
929             for (var i = 0, li = waitings.length; i < li;) {//reset waitings
930                 if (effects[waitings[i]] == audio) {
931                     waitings.splice(i, 1);
932                 } else
933                     i++;
934             }
935             for (var i = 0, li = pauseds.length; i < li;) {//reset pauseds
936                 if (effects[pauseds[i]] == audio) {
937                     pauseds.splice(i, 1);
938                 } else
939                     i++;
940             }
941             audio._isToPlay = true;//custom flag
942             return audio;
943         },
944 
945         _stopAllEffects: function () {
946             var self = this, currEffect = self._currEffect, audioPool = self._audioPool, sglCache = self._effectCache4Single,
947                 waitings = self._waitingEffIds, pauseds = self._pausedEffIds;
948             if (!currEffect && waitings.length == 0 && pauseds.length == 0)
949                 return;
950             for (var key in sglCache) {
951                 var eff = sglCache[key];
952                 if (eff.readyState > 2 && eff.duration && eff.duration != Infinity)
953                     eff.currentTime = eff.duration;
954             }
955             waitings.length = 0;
956             pauseds.length = 0;
957             for (var key in audioPool) {//reset audios in pool to be ended
958                 var list = audioPool[key];
959                 for (var i = 0, li = list.length; i < li; i++) {
960                     var eff = list[i];
961                     eff.loop = false;
962                     if (eff.readyState > 2 && eff.duration && eff.duration != Infinity)
963                         eff.currentTime = eff.duration;
964                 }
965             }
966             if (currEffect) self._stopAudio(currEffect);
967         },
968 
969         _effectPauseCb: function () {
970             var self = this;
971             if (self._isHiddenMode) return;//in this mode, return
972             var currEffect = self._getWaitingEffToPlay();//get eff to play
973             if (currEffect) {
974                 if (currEffect._isToPlay) {
975                     delete currEffect._isToPlay;
976                     currEffect.play();
977                 }
978                 else self._resumeAudio(currEffect);
979             } else if (self._needToResumeMusic) {
980                 var currMusic = self._currMusic;
981                 if (currMusic.readyState > 2 && currMusic.duration && currMusic.duration != Infinity) {//calculate current time
982                     var temp = currMusic.currentTime + self._expendTime4Music;
983                     temp = temp - currMusic.duration * ((temp / currMusic.duration) | 0);
984                     currMusic.currentTime = temp;
985                 }
986                 self._expendTime4Music = 0;
987                 self._resumeAudio(currMusic);
988                 self._musicPlayState = 2;
989                 self._needToResumeMusic = false;
990             }
991         },
992 
993         _getWaitingEffToPlay: function () {
994             var self = this, waitings = self._waitingEffIds, effects = self._effects,
995                 currEffect = self._currEffect;
996 
997             var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0;
998             self._expendTime4Music += expendTime;
999 
1000             while (true) {//get a audio to play
1001                 if (waitings.length == 0)
1002                     break;
1003                 var effId = waitings.pop();
1004                 var eff = effects[effId];
1005                 if (!eff)
1006                     continue;
1007                 if (eff._isToPlay || eff.loop || (eff.duration && eff.currentTime + expendTime < eff.duration)) {
1008                     self._currEffectId = effId;
1009                     self._currEffect = eff;
1010                     if (!eff._isToPlay && eff.readyState > 2 && eff.duration && eff.duration != Infinity) {
1011                         var temp = eff.currentTime + expendTime;
1012                         temp = temp - eff.duration * ((temp / eff.duration) | 0);
1013                         eff.currentTime = temp;
1014                     }
1015                     eff._isToPlay = false;
1016                     return eff;
1017                 } else {
1018                     if (eff.readyState > 2 && eff.duration && eff.duration != Infinity)
1019                         eff.currentTime = eff.duration;
1020                 }
1021             }
1022             self._currEffectId = null;
1023             self._currEffect = null;
1024             return null;
1025         },
1026 
1027         _pausePlaying: function () {//in this function, do not change any status of audios
1028             var self = this, currEffect = self._currEffect;
1029             self._isHiddenMode = true;
1030             var audio = self._musicPlayState == 2 ? self._currMusic : currEffect;
1031             if (audio) {
1032                 self._playings.push(audio);
1033                 audio.pause();
1034             }
1035 
1036         },
1037         _resumePlaying: function () {//in this function, do not change any status of audios
1038             var self = this, playings = self._playings;
1039             self._isHiddenMode = false;
1040             if (playings.length > 0) {
1041                 self._resumeAudio(playings[0]);
1042                 playings.length = 0;
1043             }
1044         }
1045 
1046     });
1047 }
1048 
1049 cc._audioLoader = {
1050     _supportedAudioTypes: null,
1051 
1052     // Get audio default path.
1053     getBasePath: function () {
1054         return cc.loader.audioPath;
1055     },
1056 
1057     // pre-load the audio.                                                                                                                                                     <br/>
1058     // 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/>
1059     // 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.
1060     _load: function (realUrl, url, res, count, tryArr, audio, cb) {
1061         var self = this, locLoader = cc.loader, path = cc.path;
1062         var types = this._supportedAudioTypes;
1063         var extname = "";
1064         if (types.length == 0)
1065             return cb("can not support audio!");
1066         if (count == -1) {
1067             extname = (path.extname(realUrl) || "").toLowerCase();
1068             if (!self.audioTypeSupported(extname)) {
1069                 extname = types[0];
1070                 count = 0;
1071             }
1072         } else if (count < types.length) {
1073             extname = types[count];
1074         } else {
1075             return cb("can not found the resource of audio! Last match url is : " + realUrl);
1076         }
1077         if (tryArr.indexOf(extname) >= 0)
1078             return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);
1079         realUrl = path.changeExtname(realUrl, extname);
1080         tryArr.push(extname);
1081         var delFlag = (count == types.length -1);
1082         audio = self._loadAudio(realUrl, audio, function (err) {
1083             if (err)
1084                 return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);//can not found
1085             cb(null, audio);
1086         }, delFlag);
1087         locLoader.cache[url] = audio;
1088     },
1089 
1090     //Check whether to support this type of file
1091     audioTypeSupported: function (type) {
1092         if (!type) return false;
1093         return this._supportedAudioTypes.indexOf(type.toLowerCase()) >= 0;
1094     },
1095 
1096     _loadAudio: function (url, audio, cb, delFlag) {
1097         var _Audio;
1098         if (typeof(window["cc"]) != "object" && cc.sys.browserType == "firefox")
1099             _Audio = Audio;                  //The WebAudio of FireFox  doesn't work after google closure compiler compiled with advanced mode
1100         else
1101             _Audio = (location.origin == "file://") ? Audio : (cc.WebAudio || Audio);
1102         if (arguments.length == 2) {
1103             cb = audio;
1104             audio = new _Audio();
1105         } else if ((arguments.length > 3 ) && !audio) {
1106             audio = new _Audio();
1107         }
1108         audio.src = url;
1109         audio.preload = "auto";
1110 
1111         var ua = navigator.userAgent;
1112         if (/Mobile/.test(ua) && (/iPhone OS/.test(ua) || /iPad/.test(ua) || /Firefox/.test(ua)) || /MSIE/.test(ua)) {
1113             audio.load();
1114             cb(null, audio);
1115         } else {
1116             var canplaythrough = "canplaythrough", error = "error";
1117             cc._addEventListener(audio, canplaythrough, function () {
1118                 cb(null, audio);
1119                 this.removeEventListener(canplaythrough, arguments.callee, false);
1120                 this.removeEventListener(error, arguments.callee, false);
1121             }, false);
1122             cc._addEventListener(audio, error, function () {
1123                 cb("load " + url + " failed");
1124                 if(delFlag){
1125                     this.removeEventListener(canplaythrough, arguments.callee, false);
1126                     this.removeEventListener(error, arguments.callee, false);
1127                 }
1128             }, false);
1129             audio.load();
1130         }
1131         return audio;
1132     },
1133 
1134     // Load this audio.
1135     load: function (realUrl, url, res, cb) {
1136         var tryArr = [];
1137         this._load(realUrl, url, res, -1, tryArr, null, cb);
1138     }
1139 };
1140 
1141 cc._audioLoader._supportedAudioTypes = function () {
1142     var au = cc.newElement('audio'), arr = [];
1143     if (au.canPlayType) {
1144         // <audio> tag is supported, go on
1145         var _check = function (typeStr) {
1146             var result = au.canPlayType(typeStr);
1147             return result != "no" && result != "";
1148         };
1149         if (_check('audio/ogg; codecs="vorbis"')) arr.push(".ogg");
1150         if (_check("audio/mpeg")) arr.push(".mp3");
1151         if (_check('audio/wav; codecs="1"')) arr.push(".wav");
1152         if (_check("audio/mp4")) arr.push(".mp4");
1153         if (_check("audio/x-m4a") || _check("audio/aac")) arr.push(".m4a");
1154     }
1155     return arr;
1156 }();
1157 
1158 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], cc._audioLoader);
1159 
1160 // Initialize Audio engine singleton
1161 cc.audioEngine = cc.AudioEngineForSingle ? new cc.AudioEngineForSingle() : new cc.AudioEngine();
1162 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () {
1163     cc.audioEngine._pausePlaying();
1164 });
1165 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () {
1166     cc.audioEngine._resumePlaying();
1167 });
1168