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