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