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