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