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