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 var cc = cc || {};
 28 
 29 /**
 30  * A simple Audio Engine engine API.
 31  * @class
 32  * @extends   cc.Class
 33  */
 34 cc.AudioEngine = cc.Class.extend(/** @lends cc.AudioEngine# */{
 35     _audioID:0,
 36     _audioIDList:null,
 37     _supportedFormat:null,
 38     _soundSupported:false,                                        // if sound is not enabled, this engine's init() will return false
 39     _effectsVolume:1,                                              // the volume applied to all effects
 40     _playingMusic:null,                                           // the music being played, when null, no music is being played; when not null, it may be playing or paused
 41     _resPath : "",          //root path for resources
 42 
 43     ctor:function(){
 44         this._audioIDList = {};
 45         this._supportedFormat = [];
 46     },
 47 
 48     /**
 49      * Set root path for music resources.
 50      * @param resPath
 51      */
 52     setResPath : function(resPath){
 53         if(!resPath || resPath.length == 0) return;
 54         this._resPath = resPath.substring(resPath.length - 1) == "/" ? resPath : resPath + "/";
 55     },
 56     /**
 57      * Check each type to see if it can be played by current browser
 58      * @param {Object} capabilities The results are filled into this dict
 59      * @protected
 60      */
 61     _checkCanPlay: function(capabilities) {
 62         var au = document.createElement('audio');
 63         if (au.canPlayType) {
 64             // <audio> tag is supported, go on
 65             var _check = function(typeStr) {
 66                 var result = au.canPlayType(typeStr);
 67                 return result != "no" && result != "";
 68             };
 69 
 70             capabilities["mp3"] = _check("audio/mpeg");
 71             capabilities["mp4"] = _check("audio/mp4");
 72             capabilities["m4a"] = _check("audio/x-m4a") || _check("audio/aac");
 73             capabilities["ogg"] = _check('audio/ogg; codecs="vorbis"');
 74             capabilities["wav"] = _check('audio/wav; codecs="1"');
 75         } else {
 76             // <audio> tag is not supported, nothing is supported
 77             var formats = ["mp3", "mp4", "m4a", "ogg", "wav"];
 78             for (var idx in formats) {
 79                 capabilities[formats[idx]] = false;
 80             }
 81         }
 82     },
 83 
 84     /**
 85      * Helper function for cutting out the extension from the path
 86      * @param {String} fullpath
 87      * @return {String|null} path without ext name
 88      * @protected
 89      */
 90     _getPathWithoutExt: function (fullpath) {
 91         if (typeof(fullpath) != "string") {
 92             return null;
 93         }
 94         var endPos = fullpath.lastIndexOf(".");
 95         if (endPos !== -1)
 96             return fullpath.substring(0, endPos);
 97         return fullpath;
 98     },
 99 
100     /**
101      * Helper function for extracting the extension from the path
102      * @param {String} fullpath
103      * @protected
104      */
105     _getExtFromFullPath: function (fullpath) {
106         var startPos = fullpath.lastIndexOf(".");
107         if (startPos !== -1) {
108             return fullpath.substring(startPos + 1, fullpath.length);
109         }
110         return -1;
111     },
112 
113     /**
114      * Indicates whether any background music can be played or not.
115      * @returns {boolean} <i>true</i> if the background music is playing, otherwise <i>false</i>
116      */
117     willPlayMusic: function() {
118         return false;
119     },
120 
121     /**
122      * Preload music resource.
123      * @param {String} path
124      */
125     preloadMusic:function(path){
126         this.preloadSound(path);
127     },
128 
129     /**
130      * Preload effect resource.
131      * @param {String} path
132      */
133     preloadEffect:function(path){
134         this.preloadSound(path);
135     },
136 
137     /**
138      * search in this._supportedFormat if ext is there
139      * @param {String} ext
140      * @returns {Boolean}
141      */
142     isFormatSupported: function (ext) {
143         var locSupportedFormat = this._supportedFormat;
144         for (var i = 0, len = locSupportedFormat.length; i < len; i++) {
145             if (locSupportedFormat[i] == ext)
146                 return true;
147         }
148         return false;
149     },
150 
151     /**
152      * The volume of the effects max value is 1.0,the min value is 0.0 .
153      * @return {Number}
154      * @example
155      * //example
156      * var effectVolume = cc.AudioEngine.getInstance().getEffectsVolume();
157      */
158     getEffectsVolume: function() {
159         return this._effectsVolume;
160     }
161 });
162 
163 /**
164  * the entity stored in soundList and effectList, containing the audio element and the extension name.
165  * used in cc.SimpleAudioEngine
166  */
167 cc.SimpleSFX = function (audio, ext) {
168     this.audio = audio;
169     this.ext = ext || ".ogg";
170 };
171 
172 /**
173  * The Audio Engine implementation via <audio> tag in HTML5.
174  * @class
175  * @extends   cc.AudioEngine
176  */
177 cc.SimpleAudioEngine = cc.AudioEngine.extend(/** @lends cc.SimpleAudioEngine# */{
178     _effectList:null,
179     _soundList:null,
180     _maxAudioInstance:10,
181     _canPlay:true,
182     _musicListenerBound:null,
183     _musicIsStopped: false,
184 
185     /**
186      * Constructor
187      */
188     ctor:function () {
189         cc.AudioEngine.prototype.ctor.call(this);
190         this._effectList = {};
191         this._soundList = {};
192         this._musicListenerBound = this._musicListener.bind(this);
193         var ua = navigator.userAgent;
194         if(/Mobile/.test(ua) && (/iPhone OS/.test(ua)||/iPad/.test(ua)||/Firefox/.test(ua)) || /MSIE/.test(ua)){
195             this._canPlay = false;
196         }
197     },
198 
199     /**
200      * Initialize sound type
201      * @return {Boolean}
202      */
203     init:function () {
204         // gather capabilities information, enable sound if any of the audio format is supported
205         var capabilities = {};
206         this._checkCanPlay(capabilities);
207 
208         var formats = ["ogg", "mp3", "wav", "mp4", "m4a"], locSupportedFormat = this._supportedFormat;
209         for (var idx in formats) {
210             var name = formats[idx];
211             if (capabilities[name])
212                 locSupportedFormat.push(name);
213         }
214         this._soundSupported = locSupportedFormat.length > 0;
215         return this._soundSupported;
216     },
217 
218     /**
219      * Preload music resource.<br />
220      * This method is called when cc.Loader preload  resources.
221      * @param {String} path The path of the music file with filename extension.
222      */
223     preloadSound:function (path) {
224         if (this._soundSupported) {
225             var realPath = this._resPath + path;
226             var extName = this._getExtFromFullPath(path);
227             var keyname = this._getPathWithoutExt(path);
228             if (this.isFormatSupported(extName) && !this._soundList.hasOwnProperty(keyname)) {
229                 if(this._canPlay){
230                     var sfxCache = new cc.SimpleSFX();
231                     sfxCache.ext = extName;
232                     sfxCache.audio = new Audio(realPath);
233                     sfxCache.audio.preload = 'auto';
234                     var soundPreloadCanplayHandler = function () {
235                         cc.Loader.getInstance().onResLoaded();
236                         this.removeEventListener('canplaythrough', soundPreloadCanplayHandler, false);
237                         this.removeEventListener('error', soundPreloadErrorHandler, false);
238                     };
239                     var soundPreloadErrorHandler = function (e) {
240                         cc.Loader.getInstance().onResLoadingErr(e.srcElement.src);
241                         this.removeEventListener('canplaythrough', soundPreloadCanplayHandler, false);
242                         this.removeEventListener('error', soundPreloadErrorHandler, false);
243                     };
244                     sfxCache.audio.addEventListener('canplaythrough', soundPreloadCanplayHandler, false);
245                     sfxCache.audio.addEventListener("error", soundPreloadErrorHandler, false);
246 
247                     this._soundList[keyname] = sfxCache;
248                     sfxCache.audio.load();
249                     return;
250                 }
251             }
252         }
253         cc.Loader.getInstance().onResLoaded();
254     },
255 
256     /**
257      * Play music.
258      * @param {String} path The path of the music file without filename extension.
259      * @param {Boolean} loop Whether the music loop or not.
260      * @example
261      * //example
262      * cc.AudioEngine.getInstance().playMusic(path, false);
263      */
264     playMusic:function (path, loop) {
265         if (!this._soundSupported)
266             return;
267 
268         var keyname = this._getPathWithoutExt(path);
269         var extName = this._getExtFromFullPath(path);
270         var au;
271 
272         var locSoundList = this._soundList;
273         if (locSoundList.hasOwnProperty(this._playingMusic))
274             locSoundList[this._playingMusic].audio.pause();
275 
276         this._playingMusic = keyname;
277         if (locSoundList.hasOwnProperty(this._playingMusic))
278             au = locSoundList[this._playingMusic].audio;
279         else {
280             var sfxCache = new cc.SimpleSFX();
281             sfxCache.ext = extName;
282             au = sfxCache.audio = new Audio(path);
283             sfxCache.audio.preload = 'auto';
284             locSoundList[keyname] = sfxCache;
285             sfxCache.audio.load();
286         }
287 
288         au.addEventListener("pause", this._musicListenerBound , false);
289         au.loop = loop || false;
290         au.play();
291         cc.AudioEngine.isMusicPlaying = true;
292         this._musicIsStopped = false;
293     },
294 
295     _musicListener:function(e){
296         cc.AudioEngine.isMusicPlaying = false;
297         if (this._soundList.hasOwnProperty(this._playingMusic)) {
298             var au = this._soundList[this._playingMusic].audio;
299             au.removeEventListener('pause', arguments.callee, false);
300         }
301     },
302 
303     /**
304      * Stop playing music.
305      * @param {Boolean} releaseData If release the music data or not.As default value is false.
306      * @example
307      * //example
308      * cc.AudioEngine.getInstance().stopMusic();
309      */
310     stopMusic:function (releaseData) {
311         var locSoundList = this._soundList, locPlayingMusic = this._playingMusic;
312         if (locSoundList.hasOwnProperty(locPlayingMusic)) {
313             var au = locSoundList[locPlayingMusic].audio;
314             au.pause();
315             au.duration && (au.currentTime = au.duration);
316             if (releaseData)
317                 delete locSoundList[locPlayingMusic];
318             cc.AudioEngine.isMusicPlaying = false;
319             this._musicIsStopped = true;
320         }
321     },
322 
323     /**
324      * Pause playing music.
325      * @example
326      * //example
327      * cc.AudioEngine.getInstance().pauseMusic();
328      */
329     pauseMusic:function () {
330         if (!this._musicIsStopped && this._soundList.hasOwnProperty(this._playingMusic)) {
331             var au = this._soundList[this._playingMusic].audio;
332             au.pause();
333             cc.AudioEngine.isMusicPlaying = false;
334         }
335     },
336 
337     /**
338      * Resume playing music.
339      * @example
340      * //example
341      * cc.AudioEngine.getInstance().resumeMusic();
342      */
343     resumeMusic:function () {
344         if (!this._musicIsStopped && this._soundList.hasOwnProperty(this._playingMusic)) {
345             var au = this._soundList[this._playingMusic].audio;
346             au.play();
347             au.addEventListener("pause", this._musicListenerBound , false);
348             cc.AudioEngine.isMusicPlaying = true;
349         }
350     },
351 
352     /**
353      * Rewind playing music.
354      * @example
355      * //example
356      * cc.AudioEngine.getInstance().rewindMusic();
357      */
358     rewindMusic:function () {
359         if (this._soundList.hasOwnProperty(this._playingMusic)) {
360             var au = this._soundList[this._playingMusic].audio;
361             au.currentTime = 0;
362             au.play();
363             au.addEventListener("pause", this._musicListenerBound , false);
364             cc.AudioEngine.isMusicPlaying = true;
365             this._musicIsStopped = false;
366         }
367     },
368 
369     /**
370      * The volume of the music max value is 1.0,the min value is 0.0 .
371      * @return {Number}
372      * @example
373      * //example
374      * var volume = cc.AudioEngine.getInstance().getMusicVolume();
375      */
376     getMusicVolume:function () {
377         if (this._soundList.hasOwnProperty(this._playingMusic)) {
378             return this._soundList[this._playingMusic].audio.volume;
379         }
380         return 0;
381     },
382 
383     /**
384      * Set the volume of music.
385      * @param {Number} volume Volume must be in 0.0~1.0 .
386      * @example
387      * //example
388      * cc.AudioEngine.getInstance().setMusicVolume(0.5);
389      */
390     setMusicVolume:function (volume) {
391         if (this._soundList.hasOwnProperty(this._playingMusic)) {
392             var music = this._soundList[this._playingMusic].audio;
393             if (volume > 1) {
394                 music.volume = 1;
395             } else if (volume < 0) {
396                 music.volume = 0;
397             } else {
398                 music.volume = volume;
399             }
400         }
401     },
402 
403     /**
404      * Whether the music is playing.
405      * @return {Boolean} If is playing return true,or return false.
406      * @example
407      * //example
408      *  if (cc.AudioEngine.getInstance().isMusicPlaying()) {
409      *      cc.log("music is playing");
410      *  }
411      *  else {
412      *      cc.log("music is not playing");
413      *  }
414      */
415     isMusicPlaying: function () {
416         return cc.AudioEngine.isMusicPlaying;
417     },
418 
419     /**
420      * Play sound effect.
421      * @param {String} path The path of the sound effect with filename extension.
422      * @param {Boolean} loop Whether to loop the effect playing, default value is false
423      * @return {Number|null} the audio id
424      * @example
425      * //example
426      * var soundId = cc.AudioEngine.getInstance().playEffect(path);
427      */
428     playEffect: function (path, loop) {
429         if (!this._soundSupported)
430             return null;
431 
432         var keyname = this._getPathWithoutExt(path), actExt;
433         if (this._soundList.hasOwnProperty(keyname)) {
434             actExt = this._soundList[keyname].ext;
435         } else {
436             actExt = this._getExtFromFullPath(path);
437         }
438 
439         var reclaim = this._getEffectList(keyname), au;
440         if (reclaim.length > 0) {
441             for (var i = 0; i < reclaim.length; i++) {
442                 //if one of the effect ended, play it
443                 if (reclaim[i].ended) {
444                     au = reclaim[i];
445                     au.currentTime = 0;
446                     if (window.chrome)
447                         au.load();
448                     break;
449                 }
450             }
451         }
452 
453         if (!au) {
454             if (reclaim.length >= this._maxAudioInstance) {
455                 cc.log("Error: " + path + " greater than " + this._maxAudioInstance);
456                 return null;
457             }
458             au = new Audio(keyname + "." + actExt);
459             au.volume = this._effectsVolume;
460             reclaim.push(au);
461         }
462 
463         if (loop)
464             au.loop = loop;
465         au.play();
466         var audioID = this._audioID++;
467         this._audioIDList[audioID] = au;
468         return audioID;
469     },
470 
471     /**
472      * Set the volume of sound effects.
473      * @param {Number} volume Volume must be in 0.0~1.0 .
474      * @example
475      * //example
476      * cc.AudioEngine.getInstance().setEffectsVolume(0.5);
477      */
478     setEffectsVolume:function (volume) {
479         if (volume > 1)
480             this._effectsVolume = 1;
481         else if (volume < 0)
482             this._effectsVolume = 0;
483         else
484             this._effectsVolume = volume;
485 
486         var tmpArr, au, locEffectList = this._effectList;
487         for (var key in locEffectList) {
488             tmpArr = locEffectList[key];
489             if (tmpArr.length > 0) {
490                 for (var j = 0; j < tmpArr.length; j++) {
491                     au = tmpArr[j];
492                     au.volume = this._effectsVolume;
493                 }
494             }
495         }
496     },
497 
498     /**
499      * Pause playing sound effect.
500      * @param {Number} audioID The return value of function playEffect.
501      * @example
502      * //example
503      * cc.AudioEngine.getInstance().pauseEffect(audioID);
504      */
505     pauseEffect:function (audioID) {
506         if (audioID == null) return;
507 
508         if (this._audioIDList.hasOwnProperty(audioID)) {
509             var au = this._audioIDList[audioID];
510             if (!au.ended) {
511                 au.pause();
512             }
513         }
514     },
515 
516     /**
517      * Pause all playing sound effect.
518      * @example
519      * //example
520      * cc.AudioEngine.getInstance().pauseAllEffects();
521      */
522     pauseAllEffects:function () {
523         var tmpArr, au;
524         var locEffectList = this._effectList;
525         for (var i in locEffectList) {
526             tmpArr = locEffectList[i];
527             for (var j = 0; j < tmpArr.length; j++) {
528                 au = tmpArr[j];
529                 if (!au.ended)
530                     au.pause();
531             }
532         }
533     },
534 
535     /**
536      * Resume playing sound effect.
537      * @param {Number} audioID The return value of function playEffect.
538      * @audioID
539      * //example
540      * cc.AudioEngine.getInstance().resumeEffect(audioID);
541      */
542     resumeEffect:function (audioID) {
543         if (audioID == null) return;
544 
545         if (this._audioIDList.hasOwnProperty(audioID)) {
546             var au = this._audioIDList[audioID];
547             if (!au.ended)
548                 au.play();
549         }
550     },
551 
552     /**
553      * Resume all playing sound effect
554      * @example
555      * //example
556      * cc.AudioEngine.getInstance().resumeAllEffects();
557      */
558     resumeAllEffects:function () {
559         var tmpArr, au;
560         var locEffectList = this._effectList;
561         for (var i in locEffectList) {
562             tmpArr = locEffectList[i];
563             if (tmpArr.length > 0) {
564                 for (var j = 0; j < tmpArr.length; j++) {
565                     au = tmpArr[j];
566                     if (!au.ended)
567                         au.play();
568                 }
569             }
570         }
571     },
572 
573     /**
574      * Stop playing sound effect.
575      * @param {Number} audioID The return value of function playEffect.
576      * @example
577      * //example
578      * cc.AudioEngine.getInstance().stopEffect(audioID);
579      */
580     stopEffect:function (audioID) {
581         if (audioID == null) return;
582 
583         if (this._audioIDList.hasOwnProperty(audioID)) {
584             var au = this._audioIDList[audioID];
585             if (!au.ended) {
586                 au.loop = false;
587                 au.duration && (au.currentTime = au.duration);
588             }
589         }
590     },
591 
592     /**
593      * Stop all playing sound effects.
594      * @example
595      * //example
596      * cc.AudioEngine.getInstance().stopAllEffects();
597      */
598     stopAllEffects:function () {
599         var tmpArr, au, locEffectList = this._effectList;
600         for (var i in locEffectList) {
601             tmpArr = locEffectList[i];
602             for (var j = 0; j < tmpArr.length; j++) {
603                 au = tmpArr[j];
604                 if (!au.ended) {
605                     au.loop = false;
606                     au.duration && (au.currentTime = au.duration);
607                 }
608             }
609         }
610     },
611 
612     /**
613      * Unload the preloaded effect from internal buffer
614      * @param {String} path
615      * @example
616      * //example
617      * cc.AudioEngine.getInstance().unloadEffect(EFFECT_FILE);
618      */
619     unloadEffect:function (path) {
620         if (!path) return;
621         var keyname = this._getPathWithoutExt(path);
622         if (this._effectList.hasOwnProperty(keyname)) {
623             delete this._effectList[keyname];
624         }
625 
626         var au, pathName, locAudioIDList = this._audioIDList;
627         for (var k in locAudioIDList) {
628             au = locAudioIDList[k];
629             pathName  = this._getPathWithoutExt(au.src);
630             if(pathName.indexOf(keyname) > -1){
631                 this.stopEffect(k);
632                 delete locAudioIDList[k];
633             }
634         }
635     },
636 
637     _getEffectList:function (elt) {
638         var locEffectList = this._effectList;
639         if (locEffectList.hasOwnProperty(elt)) {
640             return locEffectList[elt];
641         } else {
642             locEffectList[elt] = [];
643             return locEffectList[elt];
644         }
645     }
646 });
647 
648 cc.PlayingTask = function(id, audio,isLoop, status){
649     this.id = id;
650     this.audio = audio;
651     this.isLoop = isLoop || false;
652     this.status = status || cc.PlayingTaskStatus.stop;
653 };
654 
655 cc.PlayingTaskStatus = {playing:1, pause:2, stop:3, waiting:4};
656 
657 cc.SimpleAudioEngineForMobile = cc.SimpleAudioEngine.extend({
658     _playingList: null,
659     _currentTask:null,
660     _isPauseForList: false,
661     _checkFlag: true,
662     _audioEndedCallbackBound: null,
663 
664     ctor:function(){
665         cc.SimpleAudioEngine.prototype.ctor.call(this);
666 
667         this._playingList = [];
668         this._isPauseForList = false;
669         this._checkFlag = true;
670         this._audioEndedCallbackBound = this._audioEndCallback.bind(this);
671     },
672 
673     _stopAllEffectsForList: function(){
674         var tmpArr, au, locEffectList = this._effectList;
675         for (var i in locEffectList) {
676             tmpArr = locEffectList[i];
677             for (var j = 0; j < tmpArr.length; j++) {
678                 au = tmpArr[j];
679                 if (!au.ended) {
680                     au.removeEventListener('ended', this._audioEndedCallbackBound, false);
681                     au.loop = false;
682                     au.duration && (au.currentTime = au.duration);
683                 }
684             }
685         }
686         this._playingList.length = 0;
687         this._currentTask = null;
688     },
689 
690     /**
691      * Play music.
692      * @param {String} path The path of the music file without filename extension.
693      * @param {Boolean} loop Whether the music loop or not.
694      * @example
695      * //example
696      * cc.AudioEngine.getInstance().playMusic(path, false);
697      */
698     playMusic:function (path, loop) {
699         if (!this._soundSupported)
700             return;
701 
702         this._stopAllEffectsForList();
703 
704         var keyname = this._getPathWithoutExt(path);
705         var extName = this._getExtFromFullPath(path);
706         var au;
707 
708         var locSoundList = this._soundList;
709         if (locSoundList.hasOwnProperty(this._playingMusic)){
710             var currMusic = locSoundList[this._playingMusic];
711             currMusic.audio.removeEventListener("pause",this._musicListenerBound , false)
712             currMusic.audio.pause();
713         }
714 
715         this._playingMusic = keyname;
716         if (locSoundList.hasOwnProperty(this._playingMusic))
717             au = locSoundList[this._playingMusic].audio;
718         else {
719             var sfxCache = new cc.SimpleSFX();
720             sfxCache.ext = extName;
721             au = sfxCache.audio = new Audio(path);
722             sfxCache.audio.preload = 'auto';
723             locSoundList[keyname] = sfxCache;
724             sfxCache.audio.load();
725         }
726 
727         au.addEventListener("pause", this._musicListenerBound , false);
728         au.loop = loop || false;
729         au.play();
730         cc.AudioEngine.isMusicPlaying = true;
731         this._musicIsStopped = false;
732     },
733 
734     _musicListener:function(){
735         cc.AudioEngine.isMusicPlaying = false;
736         if (this._soundList.hasOwnProperty(this._playingMusic)) {
737             var au = this._soundList[this._playingMusic].audio;
738             au.removeEventListener('pause', arguments.callee, false);
739         }
740         if(this._checkFlag)
741             this._isPauseForList = false;
742         else
743             this._checkFlag = true;
744     },
745 
746     _stopExpiredTask:function(expendTime){
747         var locPlayingList = this._playingList, locAudioIDList = this._audioIDList;
748         for(var i = 0; i < locPlayingList.length; ){
749             var selTask = locPlayingList[i];
750             if ((selTask.status === cc.PlayingTaskStatus.waiting)){
751                 if (selTask.audio.currentTime + expendTime >= selTask.audio.duration) {
752                     locPlayingList.splice(i, 1);
753                     if (locAudioIDList.hasOwnProperty(selTask.id)) {
754                         var au = locAudioIDList[selTask.id];
755                         if (!au.ended) {
756                             au.removeEventListener('ended', this._audioEndedCallbackBound, false);
757                             au.loop = false;
758                             au.duration && (au.currentTime = au.duration);
759                         }
760                     }
761                     continue;
762                 } else
763                     selTask.audio.currentTime = selTask.audio.currentTime + expendTime;
764             }
765             i++;
766         }
767     },
768 
769     _audioEndCallback: function () {
770         var locCurrentTask = this._currentTask;
771         var expendTime = locCurrentTask.audio.currentTime;
772         this._stopExpiredTask(expendTime);
773 
774         if (locCurrentTask.isLoop) {
775             locCurrentTask.audio.play();
776             return;
777         }
778 
779         locCurrentTask.audio.removeEventListener('ended', this._audioEndedCallbackBound, false);
780         cc.ArrayRemoveObject(this._playingList, locCurrentTask);
781 
782         locCurrentTask = this._getNextTaskToPlay();
783         if (!locCurrentTask) {
784             this._currentTask = null;
785             if (this._isPauseForList) {
786                 this._isPauseForList = false;
787                 this.resumeMusic();
788             }
789         } else {
790             this._currentTask = locCurrentTask;
791             locCurrentTask.status = cc.PlayingTaskStatus.playing;
792             locCurrentTask.audio.play();
793         }
794     },
795 
796     _pushingPlayingTask: function(playingTask){
797         if(!playingTask)
798             throw "cc.SimpleAudioEngineForMobile._pushingPlayingTask(): playingTask should be non-null.";
799 
800         var locPlayingTaskList = this._playingList;
801         if(!this._currentTask){
802             if(this.isMusicPlaying()){
803                 this._checkFlag = false;
804                 this.pauseMusic();
805                 this._isPauseForList = true;
806             }
807         }else{
808             this._currentTask.status = cc.PlayingTaskStatus.waiting;
809             this._currentTask.audio.pause();
810         }
811         locPlayingTaskList.push(playingTask);
812         this._currentTask = playingTask;
813         this._playingAudioTask(playingTask)
814     },
815 
816     _playingAudioTask: function(playTask){
817         playTask.audio.addEventListener("ended", this._audioEndedCallbackBound, false);
818         playTask.audio.play();
819         playTask.status = cc.PlayingTaskStatus.playing;
820     },
821 
822     _getPlayingTaskFromList:function(audioID){
823         var locPlayList = this._playingList;
824         for(var i = 0, len = locPlayList.length;i< len;i++){
825             if(locPlayList[i].id === audioID)
826                 return locPlayList[i];
827         }
828         return null;
829     },
830 
831     _getNextTaskToPlay: function(){
832         var locPlayingList = this._playingList;
833         for(var i = locPlayingList.length -1; i >= 0; i--){
834             var selTask = locPlayingList[i];
835             if(selTask.status === cc.PlayingTaskStatus.waiting)
836                 return selTask;
837         }
838         return null;
839     },
840 
841     _playingNextTask:function(){
842         var locCurrentTask = this._currentTask = this._getNextTaskToPlay();
843         if(locCurrentTask){
844             locCurrentTask.status = cc.PlayingTaskStatus.playing;
845             locCurrentTask.audio.play();
846         } else {
847             if(this._isPauseForList){
848                 this._isPauseForList = false;
849                 this.resumeMusic();
850             }
851         }
852     },
853 
854     _deletePlayingTaskFromList: function(audioID){
855         var locPlayList = this._playingList;
856         for(var i = 0, len = locPlayList.length;i< len;i++){
857             var selTask = locPlayList[i];
858             if(selTask.id === audioID){
859                 locPlayList.splice(i,1);
860                 if(selTask == this._currentTask)
861                     this._playingNextTask();
862                 return;
863             }
864         }
865     },
866 
867     _pausePlayingTaskFromList: function (audioID) {
868         var locPlayList = this._playingList;
869         for (var i = 0, len = locPlayList.length; i < len; i++) {
870             var selTask = locPlayList[i];
871             if (selTask.id === audioID) {
872                 selTask.status = cc.PlayingTaskStatus.pause;
873                 if (selTask == this._currentTask)
874                     this._playingNextTask();
875                 return;
876             }
877         }
878     },
879 
880     _resumePlayingTaskFromList: function(audioID){
881         var locPlayList = this._playingList;
882         for (var i = 0, len = locPlayList.length; i < len; i++) {
883             var selTask = locPlayList[i];
884             if (selTask.id === audioID) {
885                 selTask.status = cc.PlayingTaskStatus.waiting;
886                 if(!this._currentTask){
887                     var locCurrentTask = this._getNextTaskToPlay();
888                     if(locCurrentTask){
889                         //pause music
890                         if(this.isMusicPlaying()){
891                             this._checkFlag = false;
892                             this.pauseMusic();
893                             this._isPauseForList = true;
894                         }
895                         locCurrentTask.status = cc.PlayingTaskStatus.playing;
896                         locCurrentTask.audio.play();
897                     }
898                 }
899                 return;
900             }
901         }
902     },
903 
904     /**
905      * Play sound effect.
906      * @param {String} path The path of the sound effect with filename extension.
907      * @param {Boolean} loop Whether to loop the effect playing, default value is false
908      * @return {Number|null} the audio id
909      * @example
910      * //example
911      * var soundId = cc.AudioEngine.getInstance().playEffect(path);
912      */
913     playEffect: function (path, loop) {
914         if (!this._soundSupported)
915             return null;
916 
917         var keyname = this._getPathWithoutExt(path), actExt;
918         if (this._soundList.hasOwnProperty(keyname))
919             actExt = this._soundList[keyname].ext;
920         else
921             actExt = this._getExtFromFullPath(path);
922 
923         var reclaim = this._getEffectList(keyname), au;
924         if (reclaim.length > 0) {
925             for (var i = 0; i < reclaim.length; i++) {
926                 //if one of the effect ended, play it
927                 if (reclaim[i].ended) {
928                     au = reclaim[i];
929                     au.currentTime = 0;
930                     if (window.chrome)
931                         au.load();
932                     break;
933                 }
934             }
935         }
936 
937         if (!au) {
938             if (reclaim.length >= this._maxAudioInstance) {
939                 cc.log("Error: " + path + " greater than " + this._maxAudioInstance);
940                 return null;
941             }
942             au = new Audio(keyname + "." + actExt);
943             au.volume = this._effectsVolume;
944             reclaim.push(au);
945         }
946 
947         var playingTask = new cc.PlayingTask(this._audioID++, au, loop);
948         this._pushingPlayingTask(playingTask);
949         this._audioIDList[playingTask.id] = au;
950         return playingTask.id;
951     },
952 
953     /**
954      * Pause playing sound effect.
955      * @param {Number} audioID The return value of function playEffect.
956      * @example
957      * //example
958      * cc.AudioEngine.getInstance().pauseEffect(audioID);
959      */
960     pauseEffect:function (audioID) {
961         if (audioID == null) return;
962 
963         var strID = audioID.toString();
964         if (this._audioIDList.hasOwnProperty(strID)) {
965             var au = this._audioIDList[strID];
966             if (!au.ended) au.pause();
967         }
968         this._pausePlayingTaskFromList(audioID);
969     },
970 
971     /**
972      * Pause all playing sound effect.
973      * @example
974      * //example
975      * cc.AudioEngine.getInstance().pauseAllEffects();
976      */
977     pauseAllEffects:function () {
978         var tmpArr, au;
979         var locEffectList = this._effectList;
980         for (var selKey in locEffectList) {
981             tmpArr = locEffectList[selKey];
982             for (var j = 0; j < tmpArr.length; j++) {
983                 au = tmpArr[j];
984                 if (!au.ended) au.pause();
985             }
986         }
987 
988         var locPlayTask = this._playingList;
989         for(var i = 0, len = locPlayTask.length; i < len; i++)
990             locPlayTask[i].status = cc.PlayingTaskStatus.pause;
991         this._currentTask = null;
992 
993         if(this._isPauseForList){
994             this._isPauseForList = false;
995             this.resumeMusic();
996         }
997     },
998 
999     /**
1000      * Resume playing sound effect.
1001      * @param {Number} audioID The return value of function playEffect.
1002      * @audioID
1003      * //example
1004      * cc.AudioEngine.getInstance().resumeEffect(audioID);
1005      */
1006     resumeEffect:function (audioID) {
1007         if (audioID == null) return;
1008 
1009         if (this._audioIDList.hasOwnProperty(audioID)) {
1010             var au = this._audioIDList[audioID];
1011             if (!au.ended)
1012                 au.play();
1013         }
1014         this._resumePlayingTaskFromList(audioID);
1015     },
1016 
1017     /**
1018      * Resume all playing sound effect
1019      * @example
1020      * //example
1021      * cc.AudioEngine.getInstance().resumeAllEffects();
1022      */
1023     resumeAllEffects:function () {
1024         var tmpArr, au;
1025         var locEffectList = this._effectList;
1026         for (var selKey in locEffectList) {
1027             tmpArr = locEffectList[selKey];
1028             if (tmpArr.length > 0) {
1029                 for (var j = 0; j < tmpArr.length; j++) {
1030                     au = tmpArr[j];
1031                     if (!au.ended) au.play();
1032                 }
1033             }
1034         }
1035 
1036         var locPlayingList = this._playingList;
1037         for(var i = 0, len = locPlayingList.length; i < len; i++){
1038              var selTask = locPlayingList[i];
1039             if(selTask.status === cc.PlayingTaskStatus.pause)
1040                 selTask.status = cc.PlayingTaskStatus.waiting;
1041         }
1042         if(this._currentTask == null){
1043             var locCurrentTask = this._getNextTaskToPlay();
1044             if(locCurrentTask){
1045                 //pause music
1046                 if(this.isMusicPlaying()){
1047                     this._checkFlag = false;
1048                     this.pauseMusic();
1049                     this._isPauseForList = true;
1050                 }
1051                 locCurrentTask.status = cc.PlayingTaskStatus.playing;
1052                 locCurrentTask.audio.play();
1053             }
1054         }
1055     },
1056 
1057     /**
1058      * Stop playing sound effect.
1059      * @param {Number} audioID The return value of function playEffect.
1060      * @example
1061      * //example
1062      * cc.AudioEngine.getInstance().stopEffect(audioID);
1063      */
1064     stopEffect:function (audioID) {
1065         if (audioID == null) return;
1066 
1067         if (this._audioIDList.hasOwnProperty(audioID)) {
1068             var au = this._audioIDList[audioID];
1069             if (!au.ended) {
1070                 au.removeEventListener('ended', this._audioEndedCallbackBound, false);
1071                 au.loop = false;
1072                 au.duration && (au.currentTime = au.duration);
1073             }
1074         }
1075         this._deletePlayingTaskFromList(audioID);
1076     },
1077 
1078     /**
1079      * Stop all playing sound effects.
1080      * @example
1081      * //example
1082      * cc.AudioEngine.getInstance().stopAllEffects();
1083      */
1084     stopAllEffects:function () {
1085         var tmpArr, au, locEffectList = this._effectList;
1086         for (var i in locEffectList) {
1087             tmpArr = locEffectList[i];
1088             for (var j = 0; j < tmpArr.length; j++) {
1089                 au = tmpArr[j];
1090                 if (!au.ended) {
1091                     au.removeEventListener('ended', this._audioEndedCallbackBound, false);
1092                     au.loop = false;
1093                     au.duration && (au.currentTime = au.duration);
1094                 }
1095             }
1096         }
1097 
1098         this._playingList.length = 0;
1099         this._currentTask = null;
1100 
1101         if(this._isPauseForList){
1102             this._isPauseForList = false;
1103             this.resumeMusic();
1104         }
1105     }
1106 });
1107 
1108 /**
1109  * The entity stored in cc.WebAudioEngine, representing a sound object
1110  */
1111 cc.WebAudioSFX = function(key, sourceNode, volumeNode, startTime, pauseTime) {
1112     // the name of the relevant audio resource
1113     this.key = key;
1114     // the node used in Web Audio API in charge of the source data
1115     this.sourceNode = sourceNode;
1116     // the node used in Web Audio API in charge of volume
1117     this.volumeNode = volumeNode;
1118     /*
1119      * when playing started from beginning, startTime is set to the current time of AudioContext.currentTime
1120      * when paused, pauseTime is set to the current time of AudioContext.currentTime
1121      * so how long the music has been played can be calculated
1122      * these won't be used in other cases
1123      */
1124     this.startTime = startTime || 0;
1125     this.pauseTime = pauseTime || 0;
1126     // by only sourceNode's playbackState, it cannot distinguish finished state from paused state
1127     this.isPaused = false;
1128 };
1129 
1130 /**
1131  * The Audio Engine implementation via Web Audio API.
1132  * @class
1133  * @extends cc.AudioEngine
1134  */
1135 cc.WebAudioEngine = cc.AudioEngine.extend(/** @lends cc.WebAudioEngine# */{
1136     // the Web Audio Context
1137     _ctx: null,
1138     // containing all binary buffers of loaded audio resources
1139     _audioData: null,
1140     /*
1141      *   Issue: When loading two resources with different suffixes asynchronously, the second one might start loading
1142      * when the first one is already loading!
1143      *   To avoid this duplication, loading synchronously somehow doesn't work. _ctx.decodeAudioData() would throw an
1144      * exception "DOM exception 12", it should be a bug of the browser.
1145      *   So just add something to mark some audios as LOADING so as to avoid duplication.
1146      */
1147     _audiosLoading: null,
1148     // the volume applied to the music
1149     _musicVolume: 1,
1150     // the effects being played: { key => [cc.WebAudioSFX] }, many effects of the same resource may be played simultaneously
1151     _effects: null,
1152 
1153     /*
1154      * _canPlay is a property in cc.SimpleAudioEngine, but not used in cc.WebAudioEngine.
1155      * Only those which support Web Audio API will be using this cc.WebAudioEngine, so no need to add an extra check.
1156      */
1157     // _canPlay: true,
1158     /*
1159      * _maxAudioInstance is also a property in cc.SimpleAudioEngine, but not used here
1160      */
1161     // _maxAudioInstance: 10,
1162 
1163     /**
1164      * Constructor
1165      */
1166     ctor: function() {
1167         cc.AudioEngine.prototype.ctor.call(this);
1168         this._audioData = {};
1169         this._audiosLoading = {};
1170         this._effects = {};
1171     },
1172 
1173     /**
1174      * Initialization
1175      * @return {Boolean}
1176      */
1177     init: function() {
1178         /*
1179          * browser has proved to support Web Audio API in miniFramework.js
1180          * only in that case will cc.WebAudioEngine be chosen to run, thus the following is guaranteed to work
1181          */
1182         this._ctx = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)();
1183 
1184         // gather capabilities information, enable sound if any of the audio format is supported
1185         var capabilities = {};
1186         this._checkCanPlay(capabilities);
1187 
1188         var formats = ["ogg", "mp3", "wav", "mp4", "m4a"], locSupportedFormat = this._supportedFormat;
1189         for (var idx in formats) {
1190             var name = formats[idx];
1191             if (capabilities[name])
1192                 locSupportedFormat.push(name);
1193         }
1194         this._soundSupported = locSupportedFormat.length > 0;
1195         return this._soundSupported;
1196     },
1197 
1198     /**
1199      * Using XMLHttpRequest to retrieve the resource data from server.
1200      * Not using cc.FileUtils.getByteArrayFromFile() because it is synchronous,
1201      * so doing the retrieving here is more handful.
1202      * @param {String} url The url to retrieve data
1203      * @param {Object} onSuccess The callback to run when retrieving succeeds, the binary data array is passed into it
1204      * @param {Object} onError The callback to run when retrieving fails
1205      * @private
1206      */
1207     _fetchData: function(url, onSuccess, onError) {
1208         // currently, only the webkit browsers support Web Audio API, so it should be fine just writing like this.
1209         var req = new window.XMLHttpRequest();
1210         var realPath = this._resPath + url;
1211         req.open('GET', realPath, true);
1212         req.responseType = 'arraybuffer';
1213         var engine = this;
1214         req.onload = function() {
1215             // when context decodes the array buffer successfully, call onSuccess
1216             engine._ctx.decodeAudioData(req.response, onSuccess, onError);
1217         };
1218         req.onerror = onError;
1219         req.send();
1220     },
1221 
1222     /**
1223      * Preload music resource.<br />
1224      * This method is called when cc.Loader preload  resources.
1225      * @param {String} path The path of the music file with filename extension.
1226      */
1227     preloadSound: function(path) {
1228         if (!this._soundSupported)
1229             return;
1230 
1231         var extName = this._getExtFromFullPath(path);
1232         var keyName = this._getPathWithoutExt(path);
1233 
1234         // not supported, already loaded, already loading
1235         if (!this.isFormatSupported(extName) || keyName in this._audioData || keyName in this._audiosLoading) {
1236             cc.Loader.getInstance().onResLoaded();
1237             return;
1238         }
1239 
1240         this._audiosLoading[keyName] = true;
1241         var engine = this;
1242         this._fetchData(path, function(buffer) {
1243             // resource fetched, in @param buffer
1244             engine._audioData[keyName] = buffer;
1245             delete engine._audiosLoading[keyName];
1246             cc.Loader.getInstance().onResLoaded();
1247         }, function() {
1248             // resource fetching failed
1249             delete engine._audiosLoading[keyName];
1250             cc.Loader.getInstance().onResLoadingErr(path);
1251         });
1252     },
1253 
1254     /**
1255      * Init a new WebAudioSFX and play it, return this WebAudioSFX object
1256      * assuming that key exists in this._audioData
1257      * @param {String} key
1258      * @param {Boolean} loop Default value is false
1259      * @param {Number} volume 0.0 - 1.0, default value is 1.0
1260      * @param {Number} [offset] Where to start playing (in seconds)
1261      * @private
1262      */
1263     _beginSound: function(key, loop, volume, offset) {
1264         var sfxCache = new cc.WebAudioSFX();
1265         loop = loop == null ? false : loop;
1266         volume = volume == null ? 1 : volume;
1267         offset = offset || 0;
1268 
1269         var locCtx = this._ctx;
1270         sfxCache.key = key;
1271         sfxCache.sourceNode = this._ctx.createBufferSource();
1272         sfxCache.sourceNode.buffer = this._audioData[key];
1273         sfxCache.sourceNode.loop = loop;
1274         if(locCtx.createGain)
1275             sfxCache.volumeNode = this._ctx.createGain();
1276         else
1277             sfxCache.volumeNode = this._ctx.createGainNode();
1278         sfxCache.volumeNode.gain.value = volume;
1279 
1280         sfxCache.sourceNode.connect(sfxCache.volumeNode);
1281         sfxCache.volumeNode.connect(this._ctx.destination);
1282 
1283         /*
1284          * Safari on iOS 6 only supports noteOn(), noteGrainOn(), and noteOff() now.(iOS 6.1.3)
1285          * The latest version of chrome has supported start() and stop()
1286          * start() & stop() are specified in the latest specification (written on 04/26/2013)
1287          *      Reference: https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
1288          * noteOn(), noteGrainOn(), and noteOff() are specified in Draft 13 version (03/13/2012)
1289          *      Reference: http://www.w3.org/2011/audio/drafts/2WD/Overview.html
1290          */
1291         if (sfxCache.sourceNode.start) {
1292             // starting from offset means resuming from where it paused last time
1293             sfxCache.sourceNode.start(0, offset);
1294         } else if (sfxCache.sourceNode.noteGrainOn) {
1295             var duration = sfxCache.sourceNode.buffer.duration;
1296             if (loop) {
1297                 /*
1298                  * On Safari on iOS 6, if loop == true, the passed in @param duration will be the duration from now on.
1299                  * In other words, the sound will keep playing the rest of the music all the time.
1300                  * On latest chrome desktop version, the passed in duration will only be the duration in this cycle.
1301                  * Now that latest chrome would have start() method, it is prepared for iOS here.
1302                  */
1303                 sfxCache.sourceNode.noteGrainOn(0, offset, duration);
1304             } else {
1305                 sfxCache.sourceNode.noteGrainOn(0, offset, duration - offset);
1306             }
1307         } else {
1308             // if only noteOn() is supported, resuming sound will NOT work
1309             sfxCache.sourceNode.noteOn(0);
1310         }
1311 
1312         // currentTime - offset is necessary for pausing multiple times!
1313         sfxCache.startTime = this._ctx.currentTime - offset;
1314         sfxCache.pauseTime = sfxCache.startTime;
1315         sfxCache.isPaused = false;
1316 
1317         return sfxCache;
1318     },
1319 
1320     /**
1321      * <p>
1322      * According to the spec: dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html                                      <br/>
1323      *      const unsigned short UNSCHEDULED_STATE = 0;                                                                          <br/>
1324      *      const unsigned short SCHEDULED_STATE = 1;                                                                            <br/>
1325      *      const unsigned short PLAYING_STATE = 2;     // this means it is playing                                              <br/>
1326      *      const unsigned short FINISHED_STATE = 3;                                                                             <br/>
1327      * However, the older specification doesn't include this property, such as this one: http://www.w3.org/2011/audio/drafts/2WD/Overview.html
1328      * </p>
1329      * @param {Object} sfxCache Assuming not null
1330      * @returns {Boolean} Whether sfxCache is playing or not
1331      * @private
1332      */
1333     _isSoundPlaying: function(sfxCache) {
1334         return sfxCache.sourceNode.playbackState == 2;
1335     },
1336 
1337     /**
1338      * To distinguish 3 kinds of status for each sound (PLAYING, PAUSED, FINISHED), _isSoundPlaying() is not enough
1339      * @param {Object} sfxCache Assuming not null
1340      * @returns {Boolean}
1341      * @private
1342      */
1343     _isSoundPaused: function(sfxCache) {
1344         // checking _isSoundPlaying() won't hurt
1345         return this._isSoundPlaying(sfxCache) ? false : sfxCache.isPaused;
1346     },
1347 
1348     /**
1349      * Whether it is playing any music
1350      * @return {Boolean} If is playing return true,or return false.
1351      * @example
1352      * //example
1353      *  if (cc.AudioEngine.getInstance().isMusicPlaying()) {
1354      *      cc.log("music is playing");
1355      *  }
1356      *  else {
1357      *      cc.log("music is not playing");
1358      *  }
1359      */
1360     isMusicPlaying: function () {
1361         /*
1362          * cc.AudioEngine.isMusicPlaying property is not going to be used here in cc.WebAudioEngine
1363          * that is only used in cc.SimpleAudioEngine
1364          * WebAudioEngine uses Web Audio API which contains a playbackState property in AudioBufferSourceNode
1365          * So there is also no need to be any method like setMusicPlaying(), it is done automatically
1366          */
1367         return this._playingMusic ? this._isSoundPlaying(this._playingMusic) : false;
1368     },
1369 
1370     /**
1371      * Play music.
1372      * @param {String} path The path of the music file without filename extension.
1373      * @param {Boolean} loop Whether the music loop or not.
1374      * @example
1375      * //example
1376      * cc.AudioEngine.getInstance().playMusic(path, false);
1377      */
1378     playMusic: function (path, loop) {
1379         var keyName = this._getPathWithoutExt(path);
1380         var extName = this._getExtFromFullPath(path);
1381         loop = loop || false;
1382 
1383         if (this._playingMusic) {
1384             // there is a music being played currently, stop it (may be paused)
1385             this.stopMusic();
1386         }
1387 
1388         if (keyName in this._audioData) {
1389             // already loaded, just play it
1390             this._playingMusic = this._beginSound(keyName, loop, this._musicVolume);
1391         } else if (this.isFormatSupported(extName) && !(keyName in this._audiosLoading)) {
1392             // load now only if the type is supported and it is not being loaded currently
1393             this._audiosLoading[keyName] = true;
1394             var engine = this;
1395             this._fetchData(path, function(buffer) {
1396                 // resource fetched, save it and call playMusic() again, this time it should be alright
1397                 engine._audioData[keyName] = buffer;
1398                 delete engine._audiosLoading[keyName];
1399                 engine.playMusic(path, loop);
1400             }, function() {
1401                 // resource fetching failed, doing nothing here
1402                 delete engine._audiosLoading[keyName];
1403                 /*
1404                  * Potential Bug: if fetching data fails every time, loading will be tried again and again.
1405                  * Preloading would prevent this issue: if it fails to fetch, preloading procedure will not achieve 100%.
1406                  */
1407             });
1408         }
1409     },
1410 
1411     /**
1412      * Ends a sound, call stop() or noteOff() accordingly
1413      * @param {Object} sfxCache Assuming not null
1414      * @private
1415      */
1416     _endSound: function(sfxCache) {
1417         if (sfxCache.sourceNode.stop) {
1418             sfxCache.sourceNode.stop(0);
1419         } else {
1420             sfxCache.sourceNode.noteOff(0);
1421         }
1422         // Do not call disconnect()! Otherwise the sourceNode's playbackState may not be updated correctly
1423         // sfxCache.sourceNode.disconnect();
1424         // sfxCache.volumeNode.disconnect();
1425     },
1426 
1427     /**
1428      * Stop playing music.
1429      * @param {Boolean} [releaseData] If release the music data or not.As default value is false.
1430      * @example
1431      * //example
1432      * cc.AudioEngine.getInstance().stopMusic();
1433      */
1434     stopMusic: function(releaseData) {
1435         // can stop when it's playing/paused
1436         var locMusic = this._playingMusic;
1437         if (!locMusic)
1438             return;
1439 
1440         var key = locMusic.key;
1441         this._endSound(locMusic);
1442         this._playingMusic = null;
1443 
1444         if (releaseData)
1445             delete this._audioData[key];
1446     },
1447 
1448     /**
1449      * Used in pauseMusic() & pauseEffect() & pauseAllEffects()
1450      * @param {Object} sfxCache Assuming not null
1451      * @private
1452      */
1453     _pauseSound: function(sfxCache) {
1454         sfxCache.pauseTime = this._ctx.currentTime;
1455         sfxCache.isPaused = true;
1456         this._endSound(sfxCache);
1457     },
1458 
1459     /**
1460      * Pause playing music.
1461      * @example
1462      * //example
1463      * cc.AudioEngine.getInstance().pauseMusic();
1464      */
1465     pauseMusic: function() {
1466         // can pause only when it's playing
1467         if (!this.isMusicPlaying())
1468             return;
1469         this._pauseSound(this._playingMusic);
1470     },
1471 
1472     /**
1473      * Used in resumeMusic() & resumeEffect() & resumeAllEffects()
1474      * @param {Object} paused The paused WebAudioSFX, assuming not null
1475      * @param {Number} volume Can be getMusicVolume() or getEffectsVolume()
1476      * @returns {Object} A new WebAudioSFX object representing the resumed sound
1477      * @private
1478      */
1479     _resumeSound: function(paused, volume) {
1480         var key = paused.key;
1481         var loop = paused.sourceNode.loop;
1482         // the paused sound may have been playing several loops, (pauseTime - startTime) may be too large
1483         var offset = (paused.pauseTime - paused.startTime) % paused.sourceNode.buffer.duration;
1484 
1485         return this._beginSound(key, loop, volume, offset);
1486     },
1487 
1488     /**
1489      * Resume playing music.
1490      * @example
1491      * //example
1492      * cc.AudioEngine.getInstance().resumeMusic();
1493      */
1494     resumeMusic: function() {
1495         var locMusic = this._playingMusic;
1496         // can resume only when it's paused
1497         if (!locMusic || !this._isSoundPaused(locMusic)) {
1498             return;
1499         }
1500         this._playingMusic = this._resumeSound(locMusic, this.getMusicVolume());
1501     },
1502 
1503     /**
1504      * Rewind playing music.
1505      * @example
1506      * //example
1507      * cc.AudioEngine.getInstance().rewindMusic();
1508      */
1509     rewindMusic: function() {
1510         var locMusic = this._playingMusic;
1511         // can rewind when it's playing or paused
1512         if (!locMusic)
1513             return;
1514 
1515         var key = locMusic.key;
1516         var loop = locMusic.sourceNode.loop;
1517         var volume = this.getMusicVolume();
1518 
1519         this._endSound(locMusic);
1520         this._playingMusic = this._beginSound(key, loop, volume);
1521     },
1522 
1523     /**
1524      * The volume of the music max value is 1.0,the min value is 0.0 .
1525      * @return {Number}
1526      * @example
1527      * //example
1528      * var volume = cc.AudioEngine.getInstance().getMusicVolume();
1529      */
1530     getMusicVolume: function() {
1531         return this._musicVolume;
1532     },
1533 
1534     /**
1535      * update volume, used in setMusicVolume() or setEffectsVolume()
1536      * @param {Object} sfxCache Assuming not null
1537      * @param {Number} volume
1538      * @private
1539      */
1540     _setSoundVolume: function(sfxCache, volume) {
1541         sfxCache.volumeNode.gain.value = volume;
1542     },
1543 
1544     /**
1545      * Set the volume of music.
1546      * @param {Number} volume Volume must be in 0.0~1.0 .
1547      * @example
1548      * //example
1549      * cc.AudioEngine.getInstance().setMusicVolume(0.5);
1550      */
1551     setMusicVolume: function(volume) {
1552         if (volume > 1)
1553             volume = 1;
1554          else if (volume < 0)
1555             volume = 0;
1556 
1557         if (this.getMusicVolume() == volume)                   // it is the same, no need to update
1558             return;
1559 
1560         this._musicVolume = volume;
1561         if (this._playingMusic)
1562             this._setSoundVolume(this._playingMusic, volume);
1563     },
1564 
1565     /**
1566      * Play sound effect.
1567      * @param {String} path The path of the sound effect with filename extension.
1568      * @param {Boolean} loop Whether to loop the effect playing, default value is false
1569      * @return {Number|null}
1570      * @example
1571      * //example
1572      * cc.AudioEngine.getInstance().playEffect(path);
1573      */
1574     playEffect: function(path, loop) {
1575         var keyName = this._getPathWithoutExt(path), extName = this._getExtFromFullPath(path), audioID;
1576 
1577         loop = loop || false;
1578 
1579         if (keyName in this._audioData) {
1580             // the resource has been loaded, just play it
1581             var locEffects = this._effects;
1582             if (!(keyName in locEffects)) {
1583                 locEffects[keyName] = [];
1584             }
1585             // a list of sound objects from the same resource
1586             var effectList = locEffects[keyName];
1587             for (var idx = 0, len = effectList.length; idx < len; idx++) {
1588                 var sfxCache = effectList[idx];
1589                 if (!this._isSoundPlaying(sfxCache) && !this._isSoundPaused(sfxCache)) {
1590                     // not playing && not paused => it is finished, this position can be reused
1591                     effectList[idx] = this._beginSound(keyName, loop, this.getEffectsVolume());
1592                     audioID = this._audioID++;
1593                     this._audioIDList[audioID] = effectList[idx];
1594                     return audioID;
1595                 }
1596             }
1597             // no new sound was created to replace an old one in the list, then just append one
1598             var addSFX = this._beginSound(keyName, loop, this.getEffectsVolume());
1599             effectList.push(addSFX);
1600             audioID = this._audioID++;
1601             this._audioIDList[audioID] = addSFX;
1602             return audioID;
1603         } else if (this.isFormatSupported(extName) && !(keyName in this._audiosLoading)) {
1604             // load now only if the type is supported and it is not being loaded currently
1605             this._audiosLoading[keyName] = true;
1606             var engine = this;
1607             audioID = this._audioID++;
1608             this._audioIDList[audioID] = null;
1609             this._fetchData(path, function(buffer) {
1610                 // resource fetched, save it and call playEffect() again, this time it should be alright
1611                 engine._audioData[keyName] = buffer;
1612                 delete engine._audiosLoading[keyName];
1613                 var asynSFX = engine._beginSound(keyName, loop, engine.getEffectsVolume());
1614                 engine._audioIDList[audioID] = asynSFX;
1615                 var locEffects = engine._effects;
1616                 if (!(keyName in locEffects))
1617                     locEffects[keyName] = [];
1618                 locEffects[keyName].push(asynSFX);
1619             }, function() {
1620                 // resource fetching failed, doing nothing here
1621                 delete engine._audiosLoading[keyName];
1622                 delete engine._audioIDList[audioID];
1623                 /*
1624                  * Potential Bug: if fetching data fails every time, loading will be tried again and again.
1625                  * Preloading would prevent this issue: if it fails to fetch, preloading procedure will not achieve 100%.
1626                  */
1627             });
1628             return audioID;
1629         }
1630         return null;
1631     },
1632 
1633     /**
1634      * Set the volume of sound effects.
1635      * @param {Number} volume Volume must be in 0.0~1.0 .
1636      * @example
1637      * //example
1638      * cc.AudioEngine.getInstance().setEffectsVolume(0.5);
1639      */
1640     setEffectsVolume: function(volume) {
1641         if (volume > 1)
1642             volume = 1;
1643         else if (volume < 0)
1644             volume = 0;
1645 
1646         if (this._effectsVolume == volume) {
1647             // it is the same, no need to update
1648             return;
1649         }
1650 
1651         this._effectsVolume = volume;
1652         var locEffects = this._effects;
1653         for (var key in locEffects) {
1654             var effectList = locEffects[key];
1655             for (var idx = 0, len = effectList.length; idx < len; idx++)
1656                 this._setSoundVolume(effectList[idx], volume);
1657         }
1658     },
1659 
1660     /**
1661      * Used in pauseEffect() and pauseAllEffects()
1662      * @param {Array} effectList A list of sounds, each sound may be playing/paused/finished
1663      * @private
1664      */
1665     _pauseSoundList: function(effectList) {
1666         for (var idx = 0, len = effectList.length; idx < len; idx++) {
1667             var sfxCache = effectList[idx];
1668             if (sfxCache && this._isSoundPlaying(sfxCache))
1669                 this._pauseSound(sfxCache);
1670         }
1671     },
1672 
1673     /**
1674      * Pause playing sound effect.
1675      * @param {Number} audioID The return value of function playEffect.
1676      * @example
1677      * //example
1678      * cc.AudioEngine.getInstance().pauseEffect(audioID);
1679      */
1680     pauseEffect: function(audioID) {
1681         if (audioID == null)
1682             return;
1683 
1684         if (this._audioIDList.hasOwnProperty(audioID)){
1685             var sfxCache = this._audioIDList[audioID];
1686             if (sfxCache && this._isSoundPlaying(sfxCache))
1687                 this._pauseSound(sfxCache);
1688         }
1689     },
1690 
1691     /**
1692      * Pause all playing sound effect.
1693      * @example
1694      * //example
1695      * cc.AudioEngine.getInstance().pauseAllEffects();
1696      */
1697     pauseAllEffects: function() {
1698         for (var key in this._effects) {
1699             this._pauseSoundList(this._effects[key]);
1700         }
1701     },
1702 
1703     /**
1704      * Used in resumeEffect() and resumeAllEffects()
1705      * @param {Array} effectList A list of sounds, each sound may be playing/paused/finished
1706      * @param {Number} volume
1707      * @private
1708      */
1709     _resumeSoundList: function(effectList, volume) {
1710         for (var idx = 0, len = effectList.length; idx < len; idx++) {
1711             var sfxCache = effectList[idx];
1712             if (this._isSoundPaused(sfxCache)) {
1713                 effectList[idx] = this._resumeSound(sfxCache, volume);
1714                 this._updateEffectsList(sfxCache, effectList[idx]);
1715             }
1716         }
1717     },
1718 
1719     /**
1720      * Resume playing sound effect.
1721      * @param {Number} audioID The return value of function playEffect.
1722      * @example
1723      * //example
1724      * cc.AudioEngine.getInstance().resumeEffect(audioID);
1725      */
1726     resumeEffect: function(audioID) {
1727         if (audioID == null)
1728             return;
1729 
1730         if (this._audioIDList.hasOwnProperty(audioID)){
1731             var sfxCache = this._audioIDList[audioID];
1732             if (sfxCache && this._isSoundPaused(sfxCache)){
1733                 this._audioIDList[audioID] = this._resumeSound(sfxCache, this.getEffectsVolume());
1734                 this._updateEffectsList(sfxCache, this._audioIDList[audioID]);
1735             }
1736         }
1737     },
1738 
1739     _updateEffectsList:function(oldSFX, newSFX){
1740         var locEffects = this._effects, locEffectList;
1741         for(var eKey in locEffects){
1742             locEffectList = locEffects[eKey];
1743             for(var i = 0; i< locEffectList.length; i++){
1744                 if(locEffectList[i] == oldSFX)
1745                     locEffectList[i] = newSFX;
1746             }
1747         }
1748     },
1749 
1750     /**
1751      * Resume all playing sound effect
1752      * @example
1753      * //example
1754      * cc.AudioEngine.getInstance().resumeAllEffects();
1755      */
1756     resumeAllEffects: function() {
1757         var locEffects = this._effects;
1758         for (var key in locEffects)
1759             this._resumeSoundList(locEffects[key], this.getEffectsVolume());
1760     },
1761 
1762     /**
1763      * Stop playing sound effect.
1764      * @param {Number} audioID The return value of function playEffect.
1765      * @example
1766      * //example
1767      * cc.AudioEngine.getInstance().stopEffect(audioID);
1768      */
1769     stopEffect: function(audioID) {
1770         if (audioID == null)
1771             return;
1772 
1773         var locAudioIDList = this._audioIDList;
1774         if (locAudioIDList.hasOwnProperty(audioID))
1775             this._endSound(locAudioIDList[audioID]);
1776     },
1777 
1778     /**
1779      * Stop all playing sound effects.
1780      * @example
1781      * //example
1782      * cc.AudioEngine.getInstance().stopAllEffects();
1783      */
1784     stopAllEffects: function() {
1785         var locEffects = this._effects;
1786         for (var key in locEffects) {
1787             var effectList = locEffects[key];
1788             for (var idx = 0, len = effectList.length; idx < len; idx++)
1789                 this._endSound(effectList[idx]);
1790             /*
1791              * Another way is to set this._effects = {} outside this for loop.
1792              * However, the cc.Class.extend() put all properties in the prototype.
1793              * If I reassign a new {} to it, that will be appear in the instance.
1794              * In other words, the dict in prototype won't release its children.
1795              */
1796             delete locEffects[key];
1797         }
1798     },
1799 
1800     /**
1801      * Unload the preloaded effect from internal buffer
1802      * @param {String} path
1803      * @example
1804      * //example
1805      * cc.AudioEngine.getInstance().unloadEffect(EFFECT_FILE);
1806      */
1807     unloadEffect: function(path) {
1808         if (!path)
1809             return;
1810 
1811         var keyName = this._getPathWithoutExt(path);
1812         if (this._effects.hasOwnProperty(keyName)){
1813             var locEffect = this._effects[keyName];
1814             delete this._effects[keyName];
1815             var locAudioIDList = this._audioIDList;
1816             for(var auID in locAudioIDList){
1817                 if(locEffect.indexOf(locAudioIDList[auID]) > -1){
1818                     this.stopEffect(auID);
1819                     delete locAudioIDList[auID];
1820                 }
1821             }
1822         }
1823 
1824         if (keyName in this._audioData)
1825             delete this._audioData[keyName];
1826     }
1827 });
1828 
1829 cc.AudioEngine._instance = null;
1830 
1831 cc.AudioEngine.isMusicPlaying = false;
1832 
1833 /**
1834  * Get the shared Engine object, it will new one when first time be called.
1835  * @return {cc.AudioEngine}
1836  */
1837 cc.AudioEngine.getInstance = function () {
1838     if (!this._instance) {
1839         var ua = navigator.userAgent;
1840         if (cc.Browser.supportWebAudio && !(/iPhone OS/.test(ua)||/iPad/.test(ua))) {
1841             this._instance = new cc.WebAudioEngine();
1842         } else {
1843             if(cc.Browser.isMobile)                                                        // TODO construct a supported list for mobile browser
1844                 this._instance = new cc.SimpleAudioEngineForMobile();
1845             else
1846                 this._instance = new cc.SimpleAudioEngine();
1847         }
1848         this._instance.init();
1849     }
1850     return this._instance;
1851 };
1852 
1853 /**
1854  *  Stop all music and sound effects
1855  * @example
1856  * //example
1857  * cc.AudioEngine.end();
1858  */
1859 cc.AudioEngine.end = function () {
1860     if (this._instance) {
1861         this._instance.stopMusic();
1862         this._instance.stopAllEffects();
1863     }
1864     this._instance = null;
1865 };
1866