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