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