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