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