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