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