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 /**
 28  * Audio support in the browser
 29  *
 30  * multichannel : Multiple audio while playing - If it doesn't, you can only play background music
 31  * webAudio     : Support for WebAudio - Support W3C WebAudio standards, all of the audio can be played
 32  * auto         : Supports auto-play audio - if Don‘t support it, On a touch detecting background music canvas, and then replay
 33  * replay       : The first music will fail, must be replay after touchstart
 34  * emptied      : Whether to use the emptied event to replace load callback
 35  *
 36  * May be modifications for a few browser version
 37  */
 38 (function(){
 39 
 40     var DEBUG = false;
 41 
 42     var sys = cc.sys;
 43 
 44     var supportTable = {
 45         "common" : {multichannel: true , webAudio: cc.sys._supportWebAudio , auto: true }
 46     };
 47     supportTable[sys.BROWSER_TYPE_IE]  = {multichannel: true , webAudio: cc.sys._supportWebAudio , auto: true, emptied: true};
 48     //  ANDROID  //
 49     supportTable[sys.BROWSER_TYPE_ANDROID]  = {multichannel: false, webAudio: false, auto: false};
 50     supportTable[sys.BROWSER_TYPE_CHROME]   = {multichannel: true , webAudio: true , auto: false};
 51     supportTable[sys.BROWSER_TYPE_FIREFOX]  = {multichannel: true , webAudio: true , auto: true };
 52     supportTable[sys.BROWSER_TYPE_UC]       = {multichannel: true , webAudio: false, auto: false};
 53     supportTable[sys.BROWSER_TYPE_QQ]       = {multichannel: false, webAudio: false, auto: true };
 54     supportTable[sys.BROWSER_TYPE_OUPENG]   = {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 55     supportTable[sys.BROWSER_TYPE_WECHAT]   = {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 56     supportTable[sys.BROWSER_TYPE_360]      = {multichannel: false, webAudio: false, auto: true };
 57     supportTable[sys.BROWSER_TYPE_MIUI]     = {multichannel: false, webAudio: false, auto: true };
 58     supportTable[sys.BROWSER_TYPE_LIEBAO]   = {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 59     supportTable[sys.BROWSER_TYPE_SOUGOU]   = {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 60     //"Baidu" browser can automatically play
 61     //But because it may be play failed, so need to replay and auto
 62     supportTable[sys.BROWSER_TYPE_BAIDU]    = {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 63     supportTable[sys.BROWSER_TYPE_BAIDU_APP]= {multichannel: false, webAudio: false, auto: false, replay: true , emptied: true };
 64 
 65     //  APPLE  //
 66     supportTable[sys.BROWSER_TYPE_SAFARI]  = {multichannel: true , webAudio: true , auto: false, webAudioCallback: function(realUrl){
 67         document.createElement("audio").src = realUrl;
 68     }};
 69 
 70     /* Determine the browser version number */
 71     var version, tmp;
 72     try{
 73         var ua = navigator.userAgent.toLowerCase();
 74         switch(sys.browserType){
 75             case sys.BROWSER_TYPE_IE:
 76                 tmp = ua.match(/(msie |rv:)([\d.]+)/);
 77                 break;
 78             case sys.BROWSER_TYPE_FIREFOX:
 79                 tmp = ua.match(/(firefox\/|rv:)([\d.]+)/);
 80                 break;
 81             case sys.BROWSER_TYPE_CHROME:
 82                 tmp = ua.match(/chrome\/([\d.]+)/);
 83                 break;
 84             case sys.BROWSER_TYPE_BAIDU:
 85                 tmp = ua.match(/baidubrowser\/([\d.]+)/);
 86                 break;
 87             case sys.BROWSER_TYPE_UC:
 88                 tmp = ua.match(/ucbrowser\/([\d.]+)/);
 89                 break;
 90             case sys.BROWSER_TYPE_QQ:
 91                 tmp = ua.match(/qqbrowser\/([\d.]+)/);
 92                 break;
 93             case sys.BROWSER_TYPE_OUPENG:
 94                 tmp = ua.match(/oupeng\/([\d.]+)/);
 95                 break;
 96             case sys.BROWSER_TYPE_WECHAT:
 97                 tmp = ua.match(/micromessenger\/([\d.]+)/);
 98                 break;
 99             case sys.BROWSER_TYPE_SAFARI:
100                 tmp = ua.match(/safari\/([\d.]+)/);
101                 break;
102             case sys.BROWSER_TYPE_MIUI:
103                 tmp = ua.match(/miuibrowser\/([\d.]+)/);
104                 break;
105         }
106         version = tmp ? tmp[1] : "";
107     }catch(e){
108         console.log(e);
109     }
110 
111     ///////////////////////////
112     //  Browser compatibility//
113     ///////////////////////////
114     if(version){
115         switch(sys.browserType){
116             case sys.BROWSER_TYPE_CHROME:
117                 if(parseInt(version) < 30){
118                     supportTable[sys.BROWSER_TYPE_CHROME]  = {multichannel: false , webAudio: true , auto: false};
119                 }
120                 break;
121             case sys.BROWSER_TYPE_MIUI:
122                 version = version.match(/\d+/g);
123                 if(version[0] < 2 || (version[0] == 2 && version[1] == 0 && version[2] <= 1)){
124                     supportTable[sys.BROWSER_TYPE_MIUI].auto = false;
125                 }
126                 break;
127         }
128     }
129 
130     if(cc.sys.isMobile){
131         if(cc.sys.os != cc.sys.OS_IOS)
132             cc.__audioSupport = supportTable[sys.browserType] || supportTable["common"];
133         else
134             cc.__audioSupport = supportTable[sys.BROWSER_TYPE_SAFARI];
135     }else{
136         //Desktop support all
137         if(cc.sys.browserType != cc.sys.BROWSER_TYPE_IE)
138             cc.__audioSupport = supportTable["common"];
139         else
140             cc.__audioSupport = supportTable[sys.BROWSER_TYPE_IE];
141     }
142 
143     if(DEBUG){
144         setTimeout(function(){
145             cc.log("browse type: " + sys.browserType);
146             cc.log("browse version: " + version);
147             cc.log("multichannel: " + cc.__audioSupport.multichannel);
148             cc.log("webAudio: " + cc.__audioSupport.webAudio);
149             cc.log("auto: " + cc.__audioSupport.auto);
150         }, 0);
151     }
152 
153 })();
154 
155 /**
156  * Encapsulate DOM and webAudio
157  */
158 cc.Audio = cc.Class.extend({
159     //TODO Maybe loader shift in will be better
160     volume: 1,
161     loop: false,
162     src: null,
163     _touch: false,
164 
165     _playing: false,
166     _AUDIO_TYPE: "AUDIO",
167     _pause: false,
168 
169     //Web Audio
170     _buffer: null,
171     _currentSource: null,
172     _startTime: null,
173     _currentTime: null,
174     _context: null,
175     _volume: null,
176 
177     _ignoreEnded: false,
178 
179     //DOM Audio
180     _element: null,
181 
182     ctor: function(context, volume, url){
183         context && (this._context = context);
184         volume && (this._volume = volume);
185         if(context && volume){
186             this._AUDIO_TYPE = "WEBAUDIO";
187         }
188         this.src = url;
189     },
190 
191     _setBufferCallback: null,
192     setBuffer: function(buffer){
193         if(!buffer) return;
194         var playing = this._playing;
195         this._AUDIO_TYPE = "WEBAUDIO";
196 
197         if(this._buffer && this._buffer != buffer && this.getPlaying())
198             this.stop();
199 
200         this._buffer = buffer;
201         if(playing)
202             this.play();
203 
204         this._volume["gain"].value = this.volume;
205         this._setBufferCallback && this._setBufferCallback(buffer);
206     },
207 
208     _setElementCallback: null,
209     setElement: function(element){
210         if(!element) return;
211         var playing = this._playing;
212         this._AUDIO_TYPE = "AUDIO";
213 
214         if(this._element && this._element != element && this.getPlaying())
215             this.stop();
216 
217         this._element = element;
218         if(playing)
219             this.play();
220 
221         element.volume = this.volume;
222         element.loop = this.loop;
223         this._setElementCallback && this._setElementCallback(element);
224     },
225 
226     play: function(offset, loop){
227         this._playing = true;
228         this.loop = loop === undefined ? this.loop : loop;
229         if(this._AUDIO_TYPE === "AUDIO"){
230             this._playOfAudio(offset);
231         }else{
232             this._playOfWebAudio(offset);
233         }
234     },
235 
236     getPlaying: function(){
237         if(!this._playing){
238             return this._playing;
239         }
240         if(this._AUDIO_TYPE === "AUDIO"){
241             var audio = this._element;
242             if(!audio || this._pause){
243                 this._playing = false;
244                 return false;
245             }else if(audio.ended){
246                 this._playing = false;
247                 return false;
248             }else
249                 return true;
250         }else{
251             var sourceNode = this._currentSource;
252             if(!this._playing && !sourceNode)
253                 return true;
254             if(sourceNode["playbackState"] == null)
255                 return this._playing;
256             else
257                 return this._currentTime + this._context.currentTime - this._startTime < this._currentSource.buffer.duration;
258         }
259     },
260 
261     _playOfWebAudio: function(offset){
262         var cs = this._currentSource;
263         if(!this._buffer){
264             return;
265         }
266         if(!this._pause && cs){
267             if(this._context.currentTime === 0 || this._currentTime + this._context.currentTime - this._startTime > this._currentSource.buffer.duration)
268                 this._stopOfWebAudio();
269             else
270                 return;
271         }
272         var audio = this._context["createBufferSource"]();
273         audio.buffer = this._buffer;
274         audio["connect"](this._volume);
275         audio.loop = this.loop;
276         this._startTime = this._context.currentTime;
277         this._currentTime = 0;
278 
279         /*
280          * Safari on iOS 6 only supports noteOn(), noteGrainOn(), and noteOff() now.(iOS 6.1.3)
281          * The latest version of chrome has supported start() and stop()
282          * start() & stop() are specified in the latest specification (written on 04/26/2013)
283          *      Reference: https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
284          * noteOn(), noteGrainOn(), and noteOff() are specified in Draft 13 version (03/13/2012)
285          *      Reference: http://www.w3.org/2011/audio/drafts/2WD/Overview.html
286          */
287         if(audio.start){
288             audio.start(0, offset || 0);
289         }else if(audio["noteGrainOn"]){
290             var duration = audio.buffer.duration;
291             if (this.loop) {
292                 /*
293                  * On Safari on iOS 6, if loop == true, the passed in @param duration will be the duration from now on.
294                  * In other words, the sound will keep playing the rest of the music all the time.
295                  * On latest chrome desktop version, the passed in duration will only be the duration in this cycle.
296                  * Now that latest chrome would have start() method, it is prepared for iOS here.
297                  */
298                 audio["noteGrainOn"](0, offset, duration);
299             } else {
300                 audio["noteGrainOn"](0, offset, duration - offset);
301             }
302         }else {
303             // if only noteOn() is supported, resuming sound will NOT work
304             audio["noteOn"](0);
305         }
306         this._currentSource = audio;
307         var self = this;
308         audio["onended"] = function(){
309             if(self._ignoreEnded){
310                 self._ignoreEnded = false;
311             }else{
312                 self._playing = false;
313             }
314         };
315     },
316 
317     _playOfAudio: function(){
318         var audio = this._element;
319         if(audio){
320             audio.loop = this.loop;
321             audio.play();
322         }
323     },
324 
325     stop: function(){
326         this._playing = false;
327         if(this._AUDIO_TYPE === "AUDIO"){
328             this._stopOfAudio();
329         }else{
330             this._stopOfWebAudio();
331         }
332     },
333 
334     _stopOfWebAudio: function(){
335         var audio = this._currentSource;
336         this._ignoreEnded = true;
337         if(audio){
338             audio.stop(0);
339             this._currentSource = null;
340         }
341     },
342 
343     _stopOfAudio: function(){
344         var audio = this._element;
345         if(audio){
346             audio.pause();
347             if (audio.duration && audio.duration != Infinity)
348                 audio.currentTime = 0;
349         }
350     },
351 
352     pause: function(){
353         this._playing = false;
354         this._pause = true;
355         if(this._AUDIO_TYPE === "AUDIO"){
356             this._pauseOfAudio();
357         }else{
358             this._pauseOfWebAudio();
359         }
360     },
361 
362     _pauseOfWebAudio: function(){
363         this._currentTime += this._context.currentTime - this._startTime;
364         var audio = this._currentSource;
365         if(audio){
366             audio.stop(0);
367         }
368     },
369 
370     _pauseOfAudio: function(){
371         var audio = this._element;
372         if(audio){
373             audio.pause();
374         }
375     },
376 
377     resume: function(){
378         if(this._pause){
379             if(this._AUDIO_TYPE === "AUDIO"){
380                 this._resumeOfAudio();
381             }else{
382                 this._resumeOfWebAudio();
383             }
384             this._pause = false;
385             this._playing = true;
386         }
387     },
388 
389     _resumeOfWebAudio: function(){
390         var audio = this._currentSource;
391         if(audio){
392             this._startTime = this._context.currentTime;
393             var offset = this._currentTime % audio.buffer.duration;
394             this._playOfWebAudio(offset);
395         }
396     },
397 
398     _resumeOfAudio: function(){
399         var audio = this._element;
400         if(audio){
401             audio.play();
402         }
403     },
404 
405     setVolume: function(volume){
406         if(volume > 1) volume = 1;
407         if(volume < 0) volume = 0;
408         this.volume = volume;
409         if(this._AUDIO_TYPE === "AUDIO"){
410             if(this._element){
411                 this._element.volume = volume;
412             }
413         }else{
414             if(this._volume){
415                 this._volume["gain"].value = volume;
416             }
417         }
418     },
419 
420     getVolume: function(){
421         return this.volume;
422     },
423 
424     cloneNode: function(){
425         var audio, self;
426         if(this._AUDIO_TYPE === "AUDIO"){
427             audio = new cc.Audio();
428 
429             var elem = document.createElement("audio");
430             elem.src = this.src;
431             audio.setElement(elem);
432         }else{
433             var volume = this._context["createGain"]();
434             volume["gain"].value = 1;
435             volume["connect"](this._context["destination"]);
436             audio = new cc.Audio(this._context, volume, this.src);
437             if(this._buffer){
438                 audio.setBuffer(this._buffer);
439             }else{
440                 self = this;
441                 this._setBufferCallback = function(buffer){
442                     audio.setBuffer(buffer);
443                     self._setBufferCallback = null;
444                 };
445             }
446         }
447         audio._AUDIO_TYPE = this._AUDIO_TYPE;
448         return audio;
449     }
450 
451 });
452 
453 (function(polyfill){
454 
455     var SWA = polyfill.webAudio,
456         SWB = polyfill.multichannel,
457         SWC = polyfill.auto;
458 
459     var support = [];
460 
461     (function(){
462         var audio = document.createElement("audio");
463         if(audio.canPlayType) {
464             var ogg = audio.canPlayType('audio/ogg; codecs="vorbis"');
465             if (ogg && ogg !== "") support.push(".ogg");
466             var mp3 = audio.canPlayType("audio/mpeg");
467             if (mp3 && mp3 !== "") support.push(".mp3");
468             var wav = audio.canPlayType('audio/wav; codecs="1"');
469             if (wav && wav !== "") support.push(".wav");
470             var mp4 = audio.canPlayType("audio/mp4");
471             if (mp4 && mp4 !== "") support.push(".mp4");
472             var m4a = audio.canPlayType("audio/x-m4a");
473             if (m4a && m4a !== "") support.push(".m4a");
474         }
475     })();
476     try{
477         if(SWA){
478             var context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)();
479         }
480     }catch(error){
481         SWA = false;
482         cc.log("browser don't support webAudio");
483     }
484 
485     var loader = {
486 
487         cache: {},
488 
489         load: function(realUrl, url, res, cb){
490 
491             if(support.length === 0)
492                 return cb("can not support audio!");
493 
494             var i;
495 
496             var extname = cc.path.extname(realUrl);
497 
498             var typeList = [extname];
499             for(i=0; i<support.length; i++){
500                 if(extname !== support[i]){
501                     typeList.push(support[i]);
502                 }
503             }
504 
505             var audio;
506 
507             if(loader.cache[url])
508                 return cb(null, loader.cache[url]);
509 
510             if(SWA){
511                 var volume = context["createGain"]();
512                 volume["gain"].value = 1;
513                 volume["connect"](context["destination"]);
514                 audio = new cc.Audio(context, volume, realUrl);
515             }else{
516                 audio = new cc.Audio(null, null, realUrl);
517             }
518 
519             this.loadAudioFromExtList(realUrl, typeList, audio, cb);
520 
521             loader.cache[url] = audio;
522 
523         },
524 
525         loadAudioFromExtList: function(realUrl, typeList, audio, cb){
526 
527             if(typeList.length === 0){
528                 var ERRSTR = "can not found the resource of audio! Last match url is : ";
529                 ERRSTR += realUrl.replace(/\.(.*)?$/, "(");
530                 support.forEach(function(ext){
531                     ERRSTR += ext + "|";
532                 });
533                 ERRSTR = ERRSTR.replace(/\|$/, ")");
534                 return cb(ERRSTR);
535             }
536 
537             realUrl = cc.path.changeExtname(realUrl, typeList.splice(0, 1));
538 
539             if(SWA){//Buffer
540                 if(polyfill.webAudioCallback)
541                     polyfill.webAudioCallback(realUrl);
542                 var request = new XMLHttpRequest();
543                 request.open("GET", realUrl, true);
544                 request.responseType = "arraybuffer";
545 
546                 // Our asynchronous callback
547                 request.onload = function () {
548                     context["decodeAudioData"](request.response, function(buffer){
549                         //success
550                         audio.setBuffer(buffer);
551                         cb(null, audio);
552                     }, function(){
553                         //error
554                         loader.loadAudioFromExtList(realUrl, typeList, audio, cb);
555                     });
556                 };
557                 request.send();
558             }else{//DOM
559 
560                 var element = document.createElement("audio");
561                 var cbCheck = false;
562                 var termination = false;
563 
564                 var timer = setTimeout(function(){
565                     if(element.readyState == 0){
566                         emptied();
567                     }else{
568                         termination = true;
569                         cb("audio load timeout : " + realUrl, audio);
570                     }
571                 }, 10000);
572 
573                 var success = function(){
574                     if(!cbCheck){
575                         audio.setElement(element);
576                         element.removeEventListener("canplaythrough", success, false);
577                         element.removeEventListener("error", failure, false);
578                         element.removeEventListener("emptied", emptied, false);
579                         !termination && cb(null, audio);
580                         cbCheck = true;
581                         clearTimeout(timer);
582                     }
583                 };
584 
585                 var failure = function(){
586                     if(!cbCheck) return;
587                     element.removeEventListener("canplaythrough", success, false);
588                     element.removeEventListener("error", failure, false);
589                     element.removeEventListener("emptied", emptied, false);
590                     !termination && loader.loadAudioFromExtList(realUrl, typeList, audio, cb);
591                     cbCheck = true;
592                     clearTimeout(timer);
593                 };
594 
595                 var emptied = function(){
596                     termination = true;
597                     success();
598                     cb(null, audio);
599                 };
600 
601                 cc._addEventListener(element, "canplaythrough", success, false);
602                 cc._addEventListener(element, "error", failure, false);
603                 if(polyfill.emptied)
604                     cc._addEventListener(element, "emptied", emptied, false);
605 
606                 element.src = realUrl;
607                 element.load();
608             }
609 
610         }
611     };
612     cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], loader);
613 
614     /**
615      * cc.audioEngine is the singleton object, it provide simple audio APIs.
616      * @namespace
617      */
618     cc.audioEngine = {
619         _currMusic: null,
620         _musicVolume: 1,
621 
622         /**
623          * Indicates whether any background music can be played or not.
624          * @returns {boolean} <i>true</i> if the background music is playing, otherwise <i>false</i>
625          */
626         willPlayMusic: function(){return false;},
627 
628         /**
629          * Play music.
630          * @param {String} url The path of the music file without filename extension.
631          * @param {Boolean} loop Whether the music loop or not.
632          * @example
633          * //example
634          * cc.audioEngine.playMusic(path, false);
635          */
636         playMusic: function(url, loop){
637             var bgMusic = this._currMusic;
638             if(bgMusic && bgMusic.src !== url && bgMusic.getPlaying()){
639                 bgMusic.stop();
640             }
641             var audio = loader.cache[url];
642             if(!audio){
643                 cc.loader.load(url);
644                 audio = loader.cache[url];
645             }
646             audio.play(0, loop);
647             audio.setVolume(this._musicVolume);
648             this._currMusic = audio;
649         },
650 
651         /**
652          * Stop playing music.
653          * @param {Boolean} [releaseData] If release the music data or not.As default value is false.
654          * @example
655          * //example
656          * cc.audioEngine.stopMusic();
657          */
658         stopMusic: function(releaseData){
659             var audio = this._currMusic;
660             if(audio){
661                 audio.stop();
662                 if (releaseData)
663                     cc.loader.release(audio.src);
664             }
665         },
666 
667         /**
668          * Pause playing music.
669          * @example
670          * //example
671          * cc.audioEngine.pauseMusic();
672          */
673         pauseMusic: function(){
674             var audio = this._currMusic;
675             if(audio)
676                 audio.pause();
677         },
678 
679         /**
680          * Resume playing music.
681          * @example
682          * //example
683          * cc.audioEngine.resumeMusic();
684          */
685         resumeMusic: function(){
686             var audio = this._currMusic;
687             if(audio)
688                 audio.resume();
689         },
690 
691         /**
692          * Rewind playing music.
693          * @example
694          * //example
695          * cc.audioEngine.rewindMusic();
696          */
697         rewindMusic: function(){
698             var audio = this._currMusic;
699             if(audio){
700                 audio.stop();
701                 audio.play();
702             }
703         },
704 
705         /**
706          * The volume of the music max value is 1.0,the min value is 0.0 .
707          * @return {Number}
708          * @example
709          * //example
710          * var volume = cc.audioEngine.getMusicVolume();
711          */
712         getMusicVolume: function(){
713             return this._musicVolume;
714         },
715 
716         /**
717          * Set the volume of music.
718          * @param {Number} volume Volume must be in 0.0~1.0 .
719          * @example
720          * //example
721          * cc.audioEngine.setMusicVolume(0.5);
722          */
723         setMusicVolume: function(volume){
724             volume = volume - 0;
725             if(isNaN(volume)) volume = 1;
726             if(volume > 1) volume = 1;
727             if(volume < 0) volume = 0;
728 
729             this._musicVolume = volume;
730             var audio = this._currMusic;
731             if(audio){
732                 audio.setVolume(volume);
733             }
734         },
735 
736         /**
737          * Whether the music is playing.
738          * @return {Boolean} If is playing return true,or return false.
739          * @example
740          * //example
741          *  if (cc.audioEngine.isMusicPlaying()) {
742          *      cc.log("music is playing");
743          *  }
744          *  else {
745          *      cc.log("music is not playing");
746          *  }
747          */
748         isMusicPlaying: function(){
749             var audio = this._currMusic;
750             if(audio){
751                 return audio.getPlaying();
752             }else{
753                 return false;
754             }
755         },
756 
757         _audioPool: {},
758         _maxAudioInstance: 5,
759         _effectVolume: 1,
760         /**
761          * Play sound effect.
762          * @param {String} url The path of the sound effect with filename extension.
763          * @param {Boolean} loop Whether to loop the effect playing, default value is false
764          * @return {Number|null} the audio id
765          * @example
766          * //example
767          * var soundId = cc.audioEngine.playEffect(path);
768          */
769         playEffect: function(url, loop){
770             //If the browser just support playing single audio
771             if(!SWB){
772                 //Must be forced to shut down
773                 //Because playing multichannel audio will be stuck in chrome 28 (android)
774                 return null;
775             }
776 
777             var effectList = this._audioPool[url];
778             if(!effectList){
779                 effectList = this._audioPool[url] = [];
780             }
781 
782             var i;
783 
784             for(i=0; i<effectList.length; i++){
785                 if(!effectList[i].getPlaying()){
786                     break;
787                 }
788             }
789 
790             if(effectList[i]){
791                 audio = effectList[i];
792                 audio.setVolume(this._effectVolume);
793                 audio.play(0, loop);
794             }else if(!SWA && i > this._maxAudioInstance){
795                 cc.log("Error: %s greater than %d", url, this._maxAudioInstance);
796             }else{
797                 var audio = loader.cache[url];
798                 if(!audio){
799                     cc.loader.load(url);
800                     audio = loader.cache[url];
801                 }
802                 audio = audio.cloneNode();
803                 audio.setVolume(this._effectVolume);
804                 audio.loop = loop || false;
805                 audio.play();
806                 effectList.push(audio);
807             }
808 
809             return audio;
810         },
811 
812         /**
813          * Set the volume of sound effects.
814          * @param {Number} volume Volume must be in 0.0~1.0 .
815          * @example
816          * //example
817          * cc.audioEngine.setEffectsVolume(0.5);
818          */
819         setEffectsVolume: function(volume){
820             volume = volume - 0;
821             if(isNaN(volume)) volume = 1;
822             if(volume > 1) volume = 1;
823             if(volume < 0) volume = 0;
824 
825             this._effectVolume = volume;
826             var audioPool = this._audioPool;
827             for(var p in audioPool){
828                 var audioList = audioPool[p];
829                 if(Array.isArray(audioList))
830                     for(var i=0; i<audioList.length; i++){
831                         audioList[i].setVolume(volume)
832                     }
833             }
834         },
835 
836         /**
837          * The volume of the effects max value is 1.0,the min value is 0.0 .
838          * @return {Number}
839          * @example
840          * //example
841          * var effectVolume = cc.audioEngine.getEffectsVolume();
842          */
843         getEffectsVolume: function(){
844             return this._effectVolume;
845         },
846 
847         /**
848          * Pause playing sound effect.
849          * @param {Number} cc.Audio The return value of function playEffect.
850          * @example
851          * //example
852          * cc.audioEngine.pauseEffect(audioID);
853          */
854         pauseEffect: function(audio){
855             if(audio){
856                 audio.pause();
857             }
858         },
859 
860         /**
861          * Pause all playing sound effect.
862          * @example
863          * //example
864          * cc.audioEngine.pauseAllEffects();
865          */
866         pauseAllEffects: function(){
867             var ap = this._audioPool;
868             for(var p in ap){
869                 var list = ap[p];
870                 for(var i=0; i<ap[p].length; i++){
871                     if(list[i].getPlaying()){
872                         list[i].pause();
873                     }
874                 }
875             }
876         },
877 
878         /**
879          * Resume playing sound effect.
880          * @param {Number} cc.Audio The return value of function playEffect.
881          * @audioID
882          * //example
883          * cc.audioEngine.resumeEffect(audioID);
884          */
885         resumeEffect: function(audio){
886             if(audio)
887                 audio.resume();
888         },
889 
890         /**
891          * Resume all playing sound effect
892          * @example
893          * //example
894          * cc.audioEngine.resumeAllEffects();
895          */
896         resumeAllEffects: function(){
897             var ap = this._audioPool;
898             for(var p in ap){
899                 var list = ap[p];
900                 for(var i=0; i<ap[p].length; i++){
901                     list[i].resume();
902                 }
903             }
904         },
905 
906         /**
907          * Stop playing sound effect.
908          * @param {Number} cc.Audio The return value of function playEffect.
909          * @example
910          * //example
911          * cc.audioEngine.stopEffect(audioID);
912          */
913         stopEffect: function(audio){
914             if(audio)
915                 audio.stop();
916         },
917 
918         /**
919          * Stop all playing sound effects.
920          * @example
921          * //example
922          * cc.audioEngine.stopAllEffects();
923          */
924         stopAllEffects: function(){
925             var ap = this._audioPool;
926             for(var p in ap){
927                 var list = ap[p];
928                 for(var i=0; i<ap[p].length; i++){
929                     list[i].stop();
930                 }
931             }
932         },
933 
934         /**
935          * Unload the preloaded effect from internal buffer
936          * @param {String} url
937          * @example
938          * //example
939          * cc.audioEngine.unloadEffect(EFFECT_FILE);
940          */
941         unloadEffect: function(url){
942             if(!url){
943                 return;
944             }
945 
946             cc.loader.release(url);
947             var pool = this._audioPool[url];
948             if(pool) pool.length = 0;
949             delete this._audioPool[url];
950             delete loader.cache[url];
951         },
952 
953         /**
954          * End music and effects.
955          */
956         end: function(){
957             this.stopMusic();
958             this.stopAllEffects();
959         },
960 
961         _pauseCache: [],
962         _pausePlaying: function(){
963             var bgMusic = this._currMusic;
964             if(bgMusic && bgMusic.getPlaying()){
965                 bgMusic.pause();
966                 this._pauseCache.push(bgMusic);
967             }
968             var ap = this._audioPool;
969             for(var p in ap){
970                 var list = ap[p];
971                 for(var i=0; i<ap[p].length; i++){
972                     if(list[i].getPlaying()){
973                         list[i].pause();
974                         this._pauseCache.push(list[i]);
975                     }
976                 }
977             }
978         },
979 
980         _resumePlaying: function(){
981             var list = this._pauseCache;
982             for(var i=0; i<list.length; i++){
983                 list[i].resume();
984             }
985             list.length = 0;
986         }
987     };
988 
989     /**
990      * ome browsers must click on the page
991      */
992     if(!SWC){
993 
994         //TODO Did not complete loading
995         var reBGM = function(){
996             var bg = cc.audioEngine._currMusic;
997             if(
998                 bg &&
999                 bg._touch === false &&
1000                 bg._playing &&
1001                 bg.getPlaying()
1002             ){
1003                 bg._touch = true;
1004                 bg.play(0, bg.loop);
1005                 !polyfill.replay && cc._canvas.removeEventListener("touchstart", reBGM);
1006             }
1007 
1008         };
1009 
1010         setTimeout(function(){
1011             if(cc._canvas){
1012                 cc._canvas.addEventListener("touchstart", reBGM, false);
1013             }
1014         }, 150);
1015     }
1016 
1017     cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () {
1018         cc.audioEngine._pausePlaying();
1019     });
1020     cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () {
1021         cc.audioEngine._resumePlaying();
1022     });
1023 
1024 })(cc.__audioSupport);
1025