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     : loop attribute failure, need to perform loop manually
 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 support = {ONLY_ONE: false, WEB_AUDIO: supportWebAudio, DELAY_CREATE_CTX: false, ONE_SOURCE: false };
 52 
 53     if (sys.browserType === sys.BROWSER_TYPE_FIREFOX) {
 54         support.DELAY_CREATE_CTX = true;
 55         support.USE_LOADER_EVENT = 'canplay';
 56     }
 57 
 58     if (sys.os === sys.OS_IOS) {
 59         support.USE_LOADER_EVENT = 'loadedmetadata';
 60     }
 61 
 62     if (sys.os === sys.OS_ANDROID) {
 63         if (sys.browserType === sys.BROWSER_TYPE_UC) {
 64             support.ONE_SOURCE = true;
 65         }
 66     }
 67 
 68     window.__audioSupport = support;
 69 
 70     if(DEBUG){
 71         setTimeout(function(){
 72             cc.log("browse type: " + sys.browserType);
 73             cc.log("browse version: " + version);
 74             cc.log("MULTI_CHANNEL: " + window.__audioSupport.MULTI_CHANNEL);
 75             cc.log("WEB_AUDIO: " + window.__audioSupport.WEB_AUDIO);
 76             cc.log("AUTOPLAY: " + window.__audioSupport.AUTOPLAY);
 77         }, 0);
 78     }
 79 
 80 })();
 81 
 82 /**
 83  * Encapsulate DOM and webAudio
 84  */
 85 cc.Audio = cc.Class.extend({
 86     src: null,
 87     _element: null,
 88     _AUDIO_TYPE: "AUDIO",
 89 
 90     ctor: function(url){
 91         this.src = url;
 92     },
 93 
 94     setBuffer: function (buffer) {
 95         this._AUDIO_TYPE = "WEBAUDIO";
 96         this._element = new cc.Audio.WebAudio(buffer);
 97     },
 98 
 99     setElement: function (element) {
100         this._AUDIO_TYPE = "AUDIO";
101         this._element = element;
102 
103         // Prevent partial browser from playing after the end does not reset the paused tag
104         // Will cause the player to judge the status of the error
105         element.addEventListener('ended', function () {
106             if (!element.loop) {
107                 element.paused = true;
108             }
109         });
110     },
111 
112     play: function (offset, loop) {
113         if (!this._element) return;
114         this._element.loop = loop;
115         this._element.play();
116         if (this._AUDIO_TYPE === 'AUDIO' && this._element.paused) {
117             this.stop();
118             cc.Audio.touchPlayList.push({ loop: loop, offset: offset, audio: this._element });
119         }
120 
121         if (cc.Audio.bindTouch === false) {
122             cc.Audio.bindTouch = true;
123             // Listen to the touchstart body event and play the audio when necessary.
124             cc.game.canvas.addEventListener('touchstart', cc.Audio.touchStart);
125         }
126     },
127 
128     getPlaying: function () {
129         if (!this._element) return true;
130         return !this._element.paused;
131     },
132 
133     stop: function () {
134         if (!this._element) return;
135         this._element.pause();
136         try{
137             this._element.currentTime = 0;
138         } catch (err) {}
139     },
140 
141     pause: function () {
142         if (!this._element) return;
143         this._element.pause();
144     },
145 
146     resume: function () {
147         if (!this._element) return;
148         this._element.play();
149     },
150 
151     setVolume: function (volume) {
152         if (!this._element) return;
153         this._element.volume = volume;
154     },
155 
156     getVolume: function () {
157         if (!this._element) return;
158         return this._element.volume;
159     },
160 
161     cloneNode: function () {
162         var audio = new cc.Audio(this.src);
163         if (this._AUDIO_TYPE === "AUDIO") {
164             var elem = document.createElement("audio");
165             var sources = elem.getElementsByTagName('source');
166             for (var i=0; i<sources.length; i++) {
167                 elem.appendChild(sources[i]);
168             }
169             elem.src = this.src;
170             audio.setElement(elem);
171         } else {
172             audio.setBuffer(this._element.buffer);
173         }
174         return audio;
175     }
176 });
177 
178 cc.Audio.touchPlayList = [
179     //{ offset: 0, audio: audio }
180 ];
181 
182 cc.Audio.bindTouch = false;
183 cc.Audio.touchStart = function () {
184     var list = cc.Audio.touchPlayList;
185     var item = null;
186     while (item = list.pop()) {
187         item.audio.loop = !!item.loop;
188         item.audio.play(item.offset);
189     }
190 };
191 
192 cc.Audio.WebAudio = function (buffer) {
193     this.buffer = buffer;
194     this.context = cc.Audio._context;
195 
196     var volume = this.context['createGain']();
197     volume['gain'].value = 1;
198     volume['connect'](this.context['destination']);
199     this._volume = volume;
200 
201     this._loop = false;
202 
203     // The time stamp on the audio time axis when the recording begins to play.
204     this._startTime = -1;
205     // Record the currently playing Source
206     this._currentSource = null;
207     // Record the time has been played
208     this.playedLength = 0;
209 
210     this._currextTimer = null;
211 };
212 
213 cc.Audio.WebAudio.prototype = {
214     constructor: cc.Audio.WebAudio,
215 
216     get paused () {
217         // If the current audio is a loop, then paused is false
218         if (this._currentSource && this._currentSource.loop)
219             return false;
220 
221         // StartTime does not have value, as the default -1, it does not begin to play
222         if (this._startTime === -1)
223             return true;
224 
225         // currentTime - startTime > durationTime
226         return this.context.currentTime - this._startTime > this.buffer.duration;
227     },
228     set paused (bool) {},
229 
230     get loop () { return this._loop; },
231     set loop (bool) { return this._loop = bool; },
232 
233     get volume () { return this._volume['gain'].value; },
234     set volume (num) { return this._volume['gain'].value = num; },
235 
236     get currentTime () { return this.playedLength; },
237     set currentTime (num) { return this.playedLength = num; },
238 
239     play: function (offset) {
240 
241         // If repeat play, you need to stop before an audio
242         if (this._currentSource && !this.paused) {
243             this._currentSource.stop(0);
244             this.playedLength = 0;
245         }
246 
247         var audio = this.context["createBufferSource"]();
248         audio.buffer = this.buffer;
249         audio["connect"](this._volume);
250         audio.loop = this._loop;
251 
252         this._startTime = this.context.currentTime;
253         offset = offset || this.playedLength;
254 
255         var duration = this.buffer.duration;
256         if (!this._loop) {
257             if (audio.start)
258                 audio.start(0, offset, duration - offset);
259             else if (audio["notoGrainOn"])
260                 audio["noteGrainOn"](0, offset, duration - offset);
261             else
262                 audio["noteOn"](0, offset, duration - offset);
263         } else {
264             if (audio.start)
265                 audio.start(0);
266             else if (audio["notoGrainOn"])
267                 audio["noteGrainOn"](0);
268             else
269                 audio["noteOn"](0);
270         }
271 
272         this._currentSource = audio;
273 
274         // If the current audio context time stamp is 0
275         // There may be a need to touch events before you can actually start playing audio
276         // So here to add a timer to determine whether the real start playing audio, if not, then the incoming touchPlay queue
277         if (this.context.currentTime === 0) {
278             var self = this;
279             clearTimeout(this._currextTimer);
280             this._currextTimer = setTimeout(function () {
281                 if (self.context.currentTime === 0) {
282                     cc.Audio.touchPlayList.push({
283                         offset: offset,
284                         audio: self
285                     });
286                 }
287             }, 10);
288         }
289     },
290     pause: function () {
291         // Record the time the current has been played
292         this.playedLength = this.context.currentTime - this._startTime;
293         //If the duration of playedLendth exceeds the audio, you should take the remainder
294         this.playedLength %= this.buffer.duration;
295         var audio = this._currentSource;
296         this._currentSource = null;
297         this._startTime = -1;
298         if (audio)
299             audio.stop(0);
300     }
301 };
302 
303 (function(polyfill){
304 
305     var SWA = polyfill.WEB_AUDIO, SWB = polyfill.ONLY_ONE;
306 
307     var support = [];
308 
309     (function(){
310         var audio = document.createElement("audio");
311         if(audio.canPlayType) {
312             var ogg = audio.canPlayType('audio/ogg; codecs="vorbis"');
313             if (ogg && ogg !== "") support.push(".ogg");
314             var mp3 = audio.canPlayType("audio/mpeg");
315             if (mp3 && mp3 !== "") support.push(".mp3");
316             var wav = audio.canPlayType('audio/wav; codecs="1"');
317             if (wav && wav !== "") support.push(".wav");
318             var mp4 = audio.canPlayType("audio/mp4");
319             if (mp4 && mp4 !== "") support.push(".mp4");
320             var m4a = audio.canPlayType("audio/x-m4a");
321             if (m4a && m4a !== "") support.push(".m4a");
322         }
323     })();
324     try{
325         if(SWA){
326             var context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)();
327             cc.Audio._context = context;
328             if(polyfill.DELAY_CREATE_CTX)
329                 setTimeout(function(){
330                     context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)();
331                     cc.Audio._context = context;
332                 }, 0);
333         }
334     }catch(error){
335         SWA = false;
336         cc.log("browser don't support web audio");
337     }
338 
339     var loader = {
340 
341         cache: {},
342 
343         useWebAudio: false,
344 
345         loadBuffer: function (url, cb) {
346             if (!SWA) return; // WebAudio Buffer
347 
348             var request = new XMLHttpRequest();
349             request.open("GET", url, true);
350             request.responseType = "arraybuffer";
351 
352             // Our asynchronous callback
353             request.onload = function () {
354                 context["decodeAudioData"](request.response, function(buffer){
355                     //success
356                     cb(null, buffer);
357                     //audio.setBuffer(buffer);
358                 }, function(){
359                     //error
360                     cb('decode error - ' + url);
361                 });
362             };
363 
364             request.onerror = function(){
365                 cb('request error - ' + url);
366             };
367 
368             request.send();
369         },
370 
371         load: function(realUrl, url, res, cb){
372 
373             if(support.length === 0)
374                 return cb("can not support audio!");
375 
376             var audio = cc.loader.getRes(url);
377             if (audio)
378                 return cb(null, audio);
379 
380             var i;
381 
382             if(cc.loader.audioPath)
383                 realUrl = cc.path.join(cc.loader.audioPath, realUrl);
384 
385             var extname = cc.path.extname(realUrl);
386 
387             var typeList = [extname];
388             for(i=0; i<support.length; i++){
389                 if(extname !== support[i]){
390                     typeList.push(support[i]);
391                 }
392             }
393 
394             audio = new cc.Audio(realUrl);
395             cc.loader.cache[url] = audio;
396             this.loadAudioFromExtList(realUrl, typeList, audio, cb);
397             return audio;
398         },
399 
400         loadAudioFromExtList: function(realUrl, typeList, audio, cb){
401             if(typeList.length === 0){
402                 var ERRSTR = "can not found the resource of audio! Last match url is : ";
403                 ERRSTR += realUrl.replace(/\.(.*)?$/, "(");
404                 support.forEach(function(ext){
405                     ERRSTR += ext + "|";
406                 });
407                 ERRSTR = ERRSTR.replace(/\|$/, ")");
408                 return cb({status:520, errorMessage:ERRSTR}, null);
409             }
410 
411             if (SWA && this.useWebAudio) {
412                 this.loadBuffer(realUrl, function (error, buffer) {
413                     if (error)
414                         cc.log(error);
415 
416                     if (buffer)
417                         audio.setBuffer(buffer);
418 
419                     cb(null, audio);
420                 });
421                 return;
422             }
423 
424             var num = polyfill.ONE_SOURCE ? 1 : typeList.length;
425 
426             // 加载统一使用dom
427             var dom = document.createElement('audio');
428             for (var i=0; i<num; i++) {
429                 var source = document.createElement('source');
430                 source.src = cc.path.changeExtname(realUrl, typeList[i]);
431                 dom.appendChild(source);
432             }
433 
434             audio.setElement(dom);
435 
436             var timer = setTimeout(function(){
437                 if (dom.readyState === 0) {
438                     failure();
439                 } else {
440                     success();
441                 }
442             }, 8000);
443 
444             var success = function () {
445                 dom.removeEventListener("canplaythrough", success, false);
446                 dom.removeEventListener("error", failure, false);
447                 dom.removeEventListener("emptied", success, false);
448                 if (polyfill.USE_LOADER_EVENT)
449                     dom.removeEventListener(polyfill.USE_LOADER_EVENT, success, false);
450                 clearTimeout(timer);
451                 cb(null, audio);
452             };
453             var failure = function () {
454                 cc.log('load audio failure - ' + realUrl);
455                 success();
456             };
457             dom.addEventListener("canplaythrough", success, false);
458             dom.addEventListener("error", failure, false);
459             if(polyfill.USE_LOADER_EVENT)
460                 dom.addEventListener(polyfill.USE_LOADER_EVENT, success, false);
461         }
462     };
463     cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], loader);
464 
465     /**
466      * cc.audioEngine is the singleton object, it provide simple audio APIs.
467      * @namespace
468      */
469     cc.audioEngine = {
470         _currMusic: null,
471         _musicVolume: 1,
472 
473         features: polyfill,
474 
475         /**
476          * Indicates whether any background music can be played or not.
477          * @returns {boolean} <i>true</i> if the background music is playing, otherwise <i>false</i>
478          */
479         willPlayMusic: function(){return false;},
480 
481         /**
482          * Play music.
483          * @param {String} url The path of the music file without filename extension.
484          * @param {Boolean} loop Whether the music loop or not.
485          * @example
486          * //example
487          * cc.audioEngine.playMusic(path, false);
488          */
489         playMusic: function(url, loop){
490             var bgMusic = this._currMusic;
491             if (bgMusic && bgMusic.getPlaying()) {
492                 bgMusic.stop();
493             }
494             var audio = cc.loader.getRes(url);
495             if (!audio) {
496                 cc.loader.load(url);
497                 audio = cc.loader.getRes(url);
498             }
499             audio.setVolume(this._musicVolume);
500             audio.play(0, loop || false);
501 
502             this._currMusic = audio;
503         },
504 
505         /**
506          * Stop playing music.
507          * @param {Boolean} [releaseData] If release the music data or not.As default value is false.
508          * @example
509          * //example
510          * cc.audioEngine.stopMusic();
511          */
512         stopMusic: function(releaseData){
513             var audio = this._currMusic;
514             if (audio) {
515                 audio.stop();
516                 this._currMusic = null;
517                 if (releaseData)
518                     cc.loader.release(audio.src);
519             }
520         },
521 
522         /**
523          * Pause playing music.
524          * @example
525          * //example
526          * cc.audioEngine.pauseMusic();
527          */
528         pauseMusic: function(){
529             var audio = this._currMusic;
530             if (audio)
531                 audio.pause();
532         },
533 
534         /**
535          * Resume playing music.
536          * @example
537          * //example
538          * cc.audioEngine.resumeMusic();
539          */
540         resumeMusic: function(){
541             var audio = this._currMusic;
542             if (audio)
543                 audio.resume();
544         },
545 
546         /**
547          * Rewind playing music.
548          * @example
549          * //example
550          * cc.audioEngine.rewindMusic();
551          */
552         rewindMusic: function(){
553             var audio = this._currMusic;
554             if (audio){
555                 audio.stop();
556                 audio.play();
557             }
558         },
559 
560         /**
561          * The volume of the music max value is 1.0,the min value is 0.0 .
562          * @return {Number}
563          * @example
564          * //example
565          * var volume = cc.audioEngine.getMusicVolume();
566          */
567         getMusicVolume: function(){
568             return this._musicVolume;
569         },
570 
571         /**
572          * Set the volume of music.
573          * @param {Number} volume Volume must be in 0.0~1.0 .
574          * @example
575          * //example
576          * cc.audioEngine.setMusicVolume(0.5);
577          */
578         setMusicVolume: function(volume){
579             volume = volume - 0;
580             if (isNaN(volume)) volume = 1;
581             if (volume > 1) volume = 1;
582             if (volume < 0) volume = 0;
583 
584             this._musicVolume = volume;
585             var audio = this._currMusic;
586             if (audio) {
587                 audio.setVolume(volume);
588             }
589         },
590 
591         /**
592          * Whether the music is playing.
593          * @return {Boolean} If is playing return true,or return false.
594          * @example
595          * //example
596          *  if (cc.audioEngine.isMusicPlaying()) {
597          *      cc.log("music is playing");
598          *  }
599          *  else {
600          *      cc.log("music is not playing");
601          *  }
602          */
603         isMusicPlaying: function(){
604             var audio = this._currMusic;
605             if (audio) {
606                 return audio.getPlaying();
607             } else {
608                 return false;
609             }
610         },
611 
612         _audioPool: {},
613         _maxAudioInstance: 10,
614         _effectVolume: 1,
615         /**
616          * Play sound effect.
617          * @param {String} url The path of the sound effect with filename extension.
618          * @param {Boolean} loop Whether to loop the effect playing, default value is false
619          * @return {Number|null} the audio id
620          * @example
621          * //example
622          * var soundId = cc.audioEngine.playEffect(path);
623          */
624         playEffect: function(url, loop){
625 
626             if (SWB && this._currMusic && this._currMusic.getPlaying()) {
627                 cc.log('Browser is only allowed to play one audio');
628                 return null;
629             }
630 
631             var effectList = this._audioPool[url];
632             if (!effectList) {
633                 effectList = this._audioPool[url] = [];
634             }
635 
636             var i;
637 
638             for (i = 0; i < effectList.length; i++) {
639                 if (!effectList[i].getPlaying()) {
640                     break;
641                 }
642             }
643 
644             if (!SWA && i > this._maxAudioInstance) {
645                 var first = effectList.shift();
646                 first.stop();
647                 effectList.push(first);
648                 i = effectList.length - 1;
649                 // cc.log("Error: %s greater than %d", url, this._maxAudioInstance);
650             }
651 
652             var audio;
653             if (effectList[i]) {
654                 audio = effectList[i];
655                 audio.setVolume(this._effectVolume);
656                 audio.play(0, loop || false);
657                 return audio;
658             }
659 
660             audio = cc.loader.getRes(url);
661 
662             if (audio && SWA && audio._AUDIO_TYPE === 'AUDIO') {
663                 cc.loader.release(url);
664                 audio = null;
665             }
666 
667             if (audio) {
668 
669                 if (SWA && audio._AUDIO_TYPE === 'AUDIO') {
670                     loader.loadBuffer(url, function (error, buffer) {
671                         audio.setBuffer(buffer);
672                         audio.setVolume(cc.audioEngine._effectVolume);
673                         if (!audio.getPlaying())
674                             audio.play(0, loop || false);
675                     });
676                 } else {
677                     audio = audio.cloneNode();
678                     audio.setVolume(this._effectVolume);
679                     audio.play(0, loop || false);
680                     effectList.push(audio);
681                     return audio;
682                 }
683 
684             }
685 
686             loader.useWebAudio = true;
687             cc.loader.load(url, function (audio) {
688                 audio = cc.loader.getRes(url);
689                 audio = audio.cloneNode();
690                 audio.setVolume(cc.audioEngine._effectVolume);
691                 audio.play(0, loop || false);
692                 effectList.push(audio);
693             });
694             loader.useWebAudio = false;
695 
696             return audio;
697         },
698 
699         /**
700          * Set the volume of sound effects.
701          * @param {Number} volume Volume must be in 0.0~1.0 .
702          * @example
703          * //example
704          * cc.audioEngine.setEffectsVolume(0.5);
705          */
706         setEffectsVolume: function(volume){
707             volume = volume - 0;
708             if(isNaN(volume)) volume = 1;
709             if(volume > 1) volume = 1;
710             if(volume < 0) volume = 0;
711 
712             this._effectVolume = volume;
713             var audioPool = this._audioPool;
714             for(var p in audioPool){
715                 var audioList = audioPool[p];
716                 if(Array.isArray(audioList))
717                     for(var i=0; i<audioList.length; i++){
718                         audioList[i].setVolume(volume);
719                     }
720             }
721         },
722 
723         /**
724          * The volume of the effects max value is 1.0,the min value is 0.0 .
725          * @return {Number}
726          * @example
727          * //example
728          * var effectVolume = cc.audioEngine.getEffectsVolume();
729          */
730         getEffectsVolume: function(){
731             return this._effectVolume;
732         },
733 
734         /**
735          * Pause playing sound effect.
736          * @param {Number} audio The return value of function playEffect.
737          * @example
738          * //example
739          * cc.audioEngine.pauseEffect(audioID);
740          */
741         pauseEffect: function(audio){
742             if(audio){
743                 audio.pause();
744             }
745         },
746 
747         /**
748          * Pause all playing sound effect.
749          * @example
750          * //example
751          * cc.audioEngine.pauseAllEffects();
752          */
753         pauseAllEffects: function(){
754             var ap = this._audioPool;
755             for(var p in ap){
756                 var list = ap[p];
757                 for(var i=0; i<ap[p].length; i++){
758                     if(list[i].getPlaying()){
759                         list[i].pause();
760                     }
761                 }
762             }
763         },
764 
765         /**
766          * Resume playing sound effect.
767          * @param {Number} audio The return value of function playEffect.
768          * @audioID
769          * //example
770          * cc.audioEngine.resumeEffect(audioID);
771          */
772         resumeEffect: function(audio){
773             if(audio)
774                 audio.resume();
775         },
776 
777         /**
778          * Resume all playing sound effect
779          * @example
780          * //example
781          * cc.audioEngine.resumeAllEffects();
782          */
783         resumeAllEffects: function(){
784             var ap = this._audioPool;
785             for(var p in ap){
786                 var list = ap[p];
787                 for(var i=0; i<ap[p].length; i++){
788                     list[i].resume();
789                 }
790             }
791         },
792 
793         /**
794          * Stop playing sound effect.
795          * @param {Number} audio The return value of function playEffect.
796          * @example
797          * //example
798          * cc.audioEngine.stopEffect(audioID);
799          */
800         stopEffect: function(audio){
801             if(audio) {
802                 audio.stop();
803             }
804         },
805 
806         /**
807          * Stop all playing sound effects.
808          * @example
809          * //example
810          * cc.audioEngine.stopAllEffects();
811          */
812         stopAllEffects: function(){
813             var ap = this._audioPool;
814             for(var p in ap){
815                 var list = ap[p];
816                 for(var i=0; i<list.length; i++){
817                     list[i].stop();
818                 }
819                 list.length = 0;
820             }
821         },
822 
823         /**
824          * Unload the preloaded effect from internal buffer
825          * @param {String} url
826          * @example
827          * //example
828          * cc.audioEngine.unloadEffect(EFFECT_FILE);
829          */
830         unloadEffect: function(url){
831             if(!url){
832                 return;
833             }
834 
835             cc.loader.release(url);
836             var pool = this._audioPool[url];
837             if(pool) pool.length = 0;
838             delete this._audioPool[url];
839         },
840 
841         /**
842          * End music and effects.
843          */
844         end: function(){
845             this.stopMusic();
846             this.stopAllEffects();
847         },
848 
849         _pauseCache: [],
850         _pausePlaying: function(){
851             var bgMusic = this._currMusic;
852             if(bgMusic && bgMusic.getPlaying()){
853                 bgMusic.pause();
854                 this._pauseCache.push(bgMusic);
855             }
856             var ap = this._audioPool;
857             for(var p in ap){
858                 var list = ap[p];
859                 for(var i=0; i<ap[p].length; i++){
860                     if(list[i].getPlaying()){
861                         list[i].pause();
862                         this._pauseCache.push(list[i]);
863                     }
864                 }
865             }
866         },
867 
868         _resumePlaying: function(){
869             var list = this._pauseCache;
870             for(var i=0; i<list.length; i++){
871                 list[i].resume();
872             }
873             list.length = 0;
874         }
875     };
876 
877 })(window.__audioSupport);
878