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