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