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 return this._currentTime + this._context.currentTime - this._startTime < sourceNode.buffer.duration; 228 }, 229 230 _playOfWebAudio: function(offset){ 231 var cs = this._currentSource; 232 if(!this._buffer){ 233 return; 234 } 235 if(!this._pause && cs){ 236 if(this._context.currentTime === 0 || this._currentTime + this._context.currentTime - this._startTime > cs.buffer.duration) 237 this._stopOfWebAudio(); 238 else 239 return; 240 } 241 var audio = this._context["createBufferSource"](); 242 audio.buffer = this._buffer; 243 audio["connect"](this._volume); 244 if(this._manualLoop) 245 audio.loop = false; 246 else 247 audio.loop = this.loop; 248 this._startTime = this._context.currentTime; 249 this._currentTime = offset || 0; 250 this._ignoreEnded = false; 251 252 /* 253 * Safari on iOS 6 only supports noteOn(), noteGrainOn(), and noteOff() now.(iOS 6.1.3) 254 * The latest version of chrome has supported start() and stop() 255 * start() & stop() are specified in the latest specification (written on 04/26/2013) 256 * Reference: https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html 257 * noteOn(), noteGrainOn(), and noteOff() are specified in Draft 13 version (03/13/2012) 258 * Reference: http://www.w3.org/2011/audio/drafts/2WD/Overview.html 259 */ 260 if(audio.start){ 261 audio.start(0, offset || 0); 262 }else if(audio["noteGrainOn"]){ 263 var duration = audio.buffer.duration; 264 if (this.loop) { 265 /* 266 * On Safari on iOS 6, if loop == true, the passed in @param duration will be the duration from now on. 267 * In other words, the sound will keep playing the rest of the music all the time. 268 * On latest chrome desktop version, the passed in duration will only be the duration in this cycle. 269 * Now that latest chrome would have start() method, it is prepared for iOS here. 270 */ 271 audio["noteGrainOn"](0, offset, duration); 272 } else { 273 audio["noteGrainOn"](0, offset, duration - offset); 274 } 275 }else { 276 // if only noteOn() is supported, resuming sound will NOT work 277 audio["noteOn"](0); 278 } 279 this._currentSource = audio; 280 var self = this; 281 audio["onended"] = function(){ 282 if(self._manualLoop && self._playing && self.loop){ 283 self.stop(); 284 self.play(); 285 return; 286 } 287 if(self._ignoreEnded){ 288 self._ignoreEnded = false; 289 }else{ 290 if(!self._pause) 291 self.stop(); 292 else 293 self._playing = false; 294 } 295 }; 296 }, 297 298 _playOfAudio: function(){ 299 var audio = this._element; 300 if(audio){ 301 audio.loop = this.loop; 302 audio.play(); 303 } 304 }, 305 306 stop: function(){ 307 this._playing = false; 308 if(this._AUDIO_TYPE === "AUDIO"){ 309 this._stopOfAudio(); 310 }else{ 311 this._stopOfWebAudio(); 312 } 313 }, 314 315 _stopOfWebAudio: function(){ 316 var audio = this._currentSource; 317 this._ignoreEnded = true; 318 if(audio){ 319 audio.stop(0); 320 this._currentSource = null; 321 } 322 }, 323 324 _stopOfAudio: function(){ 325 var audio = this._element; 326 if(audio){ 327 audio.pause(); 328 if (audio.duration && audio.duration !== Infinity) 329 audio.currentTime = 0; 330 } 331 }, 332 333 pause: function(){ 334 if(this.getPlaying() === false) 335 return; 336 this._playing = false; 337 this._pause = true; 338 if(this._AUDIO_TYPE === "AUDIO"){ 339 this._pauseOfAudio(); 340 }else{ 341 this._pauseOfWebAudio(); 342 } 343 }, 344 345 _pauseOfWebAudio: function(){ 346 this._currentTime += this._context.currentTime - this._startTime; 347 var audio = this._currentSource; 348 if(audio){ 349 audio.stop(0); 350 } 351 }, 352 353 _pauseOfAudio: function(){ 354 var audio = this._element; 355 if(audio){ 356 audio.pause(); 357 } 358 }, 359 360 resume: function(){ 361 if(this._pause){ 362 if(this._AUDIO_TYPE === "AUDIO"){ 363 this._resumeOfAudio(); 364 }else{ 365 this._resumeOfWebAudio(); 366 } 367 this._pause = false; 368 this._playing = true; 369 } 370 }, 371 372 _resumeOfWebAudio: function(){ 373 var audio = this._currentSource; 374 if(audio){ 375 this._startTime = this._context.currentTime; 376 var offset = this._currentTime % audio.buffer.duration; 377 this._playOfWebAudio(offset); 378 } 379 }, 380 381 _resumeOfAudio: function(){ 382 var audio = this._element; 383 if(audio){ 384 audio.play(); 385 } 386 }, 387 388 setVolume: function(volume){ 389 if(volume > 1) volume = 1; 390 if(volume < 0) volume = 0; 391 this.volume = volume; 392 if(this._AUDIO_TYPE === "AUDIO"){ 393 if(this._element){ 394 this._element.volume = volume; 395 } 396 }else{ 397 if(this._volume){ 398 this._volume["gain"].value = volume; 399 } 400 } 401 }, 402 403 getVolume: function(){ 404 return this.volume; 405 }, 406 407 cloneNode: function(){ 408 var audio, self; 409 if(this._AUDIO_TYPE === "AUDIO"){ 410 audio = new cc.Audio(); 411 412 var elem = document.createElement("audio"); 413 elem.src = this.src; 414 audio.setElement(elem); 415 }else{ 416 var volume = this._context["createGain"](); 417 volume["gain"].value = 1; 418 volume["connect"](this._context["destination"]); 419 audio = new cc.Audio(this._context, volume, this.src); 420 if(this._buffer){ 421 audio.setBuffer(this._buffer); 422 }else{ 423 self = this; 424 this._setBufferCallback = function(buffer){ 425 audio.setBuffer(buffer); 426 self._setBufferCallback = null; 427 }; 428 } 429 audio._manualLoop = this._manualLoop; 430 } 431 audio._AUDIO_TYPE = this._AUDIO_TYPE; 432 return audio; 433 } 434 435 }); 436 437 (function(polyfill){ 438 439 var SWA = polyfill.WEB_AUDIO, 440 SWB = polyfill.MULTI_CHANNEL, 441 SWC = polyfill.AUTOPLAY; 442 443 var support = []; 444 445 (function(){ 446 var audio = document.createElement("audio"); 447 if(audio.canPlayType) { 448 var ogg = audio.canPlayType('audio/ogg; codecs="vorbis"'); 449 if (ogg && ogg !== "") support.push(".ogg"); 450 var mp3 = audio.canPlayType("audio/mpeg"); 451 if (mp3 && mp3 !== "") support.push(".mp3"); 452 var wav = audio.canPlayType('audio/wav; codecs="1"'); 453 if (wav && wav !== "") support.push(".wav"); 454 var mp4 = audio.canPlayType("audio/mp4"); 455 if (mp4 && mp4 !== "") support.push(".mp4"); 456 var m4a = audio.canPlayType("audio/x-m4a"); 457 if (m4a && m4a !== "") support.push(".m4a"); 458 } 459 })(); 460 try{ 461 if(SWA){ 462 var context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)(); 463 if(polyfill.DELAY_CREATE_CTX) 464 setTimeout(function(){ context = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)(); }, 0); 465 } 466 }catch(error){ 467 SWA = false; 468 cc.log("browser don't support web audio"); 469 } 470 471 var loader = { 472 473 cache: {}, 474 475 load: function(realUrl, url, res, cb){ 476 477 if(support.length === 0) 478 return cb("can not support audio!"); 479 480 var i; 481 482 if(cc.loader.audioPath) 483 realUrl = cc.path.join(cc.loader.audioPath, realUrl); 484 485 var extname = cc.path.extname(realUrl); 486 487 var typeList = [extname]; 488 for(i=0; i<support.length; i++){ 489 if(extname !== support[i]){ 490 typeList.push(support[i]); 491 } 492 } 493 494 var audio; 495 496 if(loader.cache[url]) 497 return cb(null, loader.cache[url]); 498 499 if(SWA){ 500 try{ 501 var volume = context["createGain"](); 502 volume["gain"].value = 1; 503 volume["connect"](context["destination"]); 504 audio = new cc.Audio(context, volume, realUrl); 505 if(polyfill.NEED_MANUAL_LOOP) 506 audio._manualLoop = true; 507 }catch(err){ 508 SWA = false; 509 cc.log("browser don't support web audio"); 510 audio = new cc.Audio(null, null, realUrl); 511 } 512 }else{ 513 audio = new cc.Audio(null, null, realUrl); 514 } 515 516 this.loadAudioFromExtList(realUrl, typeList, audio, cb); 517 518 loader.cache[url] = audio; 519 520 }, 521 522 loadAudioFromExtList: function(realUrl, typeList, audio, cb){ 523 524 if(typeList.length === 0){ 525 var ERRSTR = "can not found the resource of audio! Last match url is : "; 526 ERRSTR += realUrl.replace(/\.(.*)?$/, "("); 527 support.forEach(function(ext){ 528 ERRSTR += ext + "|"; 529 }); 530 ERRSTR = ERRSTR.replace(/\|$/, ")"); 531 return cb({status:520, errorMessage:ERRSTR}, null); 532 } 533 534 realUrl = cc.path.changeExtname(realUrl, typeList.splice(0, 1)); 535 536 if(SWA){//Buffer 537 if(polyfill.webAudioCallback) 538 polyfill.webAudioCallback(realUrl); 539 var request = new XMLHttpRequest(); 540 request.open("GET", realUrl, true); 541 request.responseType = "arraybuffer"; 542 543 // Our asynchronous callback 544 request.onload = function () { 545 context["decodeAudioData"](request.response, function(buffer){ 546 //success 547 audio.setBuffer(buffer); 548 cb(null, audio); 549 }, function(){ 550 //error 551 loader.loadAudioFromExtList(realUrl, typeList, audio, cb); 552 }); 553 }; 554 555 request.onerror = function(){ 556 cb({status:520, errorMessage:ERRSTR}, null); 557 }; 558 559 request.send(); 560 }else{//DOM 561 562 var element = document.createElement("audio"); 563 var cbCheck = false; 564 var termination = false; 565 566 var timer = setTimeout(function(){ 567 if(element.readyState === 0){ 568 emptied(); 569 }else{ 570 termination = true; 571 element.pause(); 572 document.body.removeChild(element); 573 cb("audio load timeout : " + realUrl, audio); 574 } 575 }, 10000); 576 577 var success = function(){ 578 if(!cbCheck){ 579 element.pause(); 580 try { element.currentTime = 0; 581 element.volume = 1; } catch (e) {} 582 document.body.removeChild(element); 583 audio.setElement(element); 584 element.removeEventListener("canplaythrough", success, false); 585 element.removeEventListener("error", failure, false); 586 element.removeEventListener("emptied", emptied, false); 587 !termination && cb(null, audio); 588 cbCheck = true; 589 clearTimeout(timer); 590 } 591 }; 592 593 var failure = function(){ 594 if(!cbCheck) return; 595 element.pause(); 596 document.body.removeChild(element); 597 element.removeEventListener("canplaythrough", success, false); 598 element.removeEventListener("error", failure, false); 599 element.removeEventListener("emptied", emptied, false); 600 !termination && loader.loadAudioFromExtList(realUrl, typeList, audio, cb); 601 cbCheck = true; 602 clearTimeout(timer); 603 }; 604 605 var emptied = function(){ 606 termination = true; 607 success(); 608 cb(null, audio); 609 }; 610 611 cc._addEventListener(element, "canplaythrough", success, false); 612 cc._addEventListener(element, "error", failure, false); 613 if(polyfill.USE_EMPTIED_EVENT) 614 cc._addEventListener(element, "emptied", emptied, false); 615 616 element.src = realUrl; 617 document.body.appendChild(element); 618 element.volume = 0; 619 element.play(); 620 } 621 622 } 623 }; 624 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], loader); 625 626 /** 627 * cc.audioEngine is the singleton object, it provide simple audio APIs. 628 * @namespace 629 */ 630 cc.audioEngine = { 631 _currMusic: null, 632 _musicVolume: 1, 633 634 features: polyfill, 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 663 this._currMusic = audio; 664 }, 665 666 /** 667 * Stop playing music. 668 * @param {Boolean} [releaseData] If release the music data or not.As default value is false. 669 * @example 670 * //example 671 * cc.audioEngine.stopMusic(); 672 */ 673 stopMusic: function(releaseData){ 674 var audio = this._currMusic; 675 if(audio){ 676 audio.stop(); 677 if (releaseData) 678 cc.loader.release(audio.src); 679 } 680 }, 681 682 /** 683 * Pause playing music. 684 * @example 685 * //example 686 * cc.audioEngine.pauseMusic(); 687 */ 688 pauseMusic: function(){ 689 var audio = this._currMusic; 690 if(audio) 691 audio.pause(); 692 }, 693 694 /** 695 * Resume playing music. 696 * @example 697 * //example 698 * cc.audioEngine.resumeMusic(); 699 */ 700 resumeMusic: function(){ 701 var audio = this._currMusic; 702 if(audio) 703 audio.resume(); 704 }, 705 706 /** 707 * Rewind playing music. 708 * @example 709 * //example 710 * cc.audioEngine.rewindMusic(); 711 */ 712 rewindMusic: function(){ 713 var audio = this._currMusic; 714 if(audio){ 715 audio.stop(); 716 audio.play(); 717 } 718 }, 719 720 /** 721 * The volume of the music max value is 1.0,the min value is 0.0 . 722 * @return {Number} 723 * @example 724 * //example 725 * var volume = cc.audioEngine.getMusicVolume(); 726 */ 727 getMusicVolume: function(){ 728 return this._musicVolume; 729 }, 730 731 /** 732 * Set the volume of music. 733 * @param {Number} volume Volume must be in 0.0~1.0 . 734 * @example 735 * //example 736 * cc.audioEngine.setMusicVolume(0.5); 737 */ 738 setMusicVolume: function(volume){ 739 volume = volume - 0; 740 if(isNaN(volume)) volume = 1; 741 if(volume > 1) volume = 1; 742 if(volume < 0) volume = 0; 743 744 this._musicVolume = volume; 745 var audio = this._currMusic; 746 if(audio){ 747 audio.setVolume(volume); 748 } 749 }, 750 751 /** 752 * Whether the music is playing. 753 * @return {Boolean} If is playing return true,or return false. 754 * @example 755 * //example 756 * if (cc.audioEngine.isMusicPlaying()) { 757 * cc.log("music is playing"); 758 * } 759 * else { 760 * cc.log("music is not playing"); 761 * } 762 */ 763 isMusicPlaying: function(){ 764 var audio = this._currMusic; 765 if(audio){ 766 return audio.getPlaying(); 767 }else{ 768 return false; 769 } 770 }, 771 772 _audioPool: {}, 773 _maxAudioInstance: 5, 774 _effectVolume: 1, 775 /** 776 * Play sound effect. 777 * @param {String} url The path of the sound effect with filename extension. 778 * @param {Boolean} loop Whether to loop the effect playing, default value is false 779 * @return {Number|null} the audio id 780 * @example 781 * //example 782 * var soundId = cc.audioEngine.playEffect(path); 783 */ 784 playEffect: function(url, loop){ 785 //If the browser just support playing single audio 786 if(!SWB){ 787 //Must be forced to shut down 788 //Because playing MULTI_CHANNEL audio will be stuck in chrome 28 (android) 789 return null; 790 } 791 792 var effectList = this._audioPool[url]; 793 if(!effectList){ 794 effectList = this._audioPool[url] = []; 795 } 796 797 var i; 798 799 for(i=0; i<effectList.length; i++){ 800 if(!effectList[i].getPlaying()){ 801 break; 802 } 803 } 804 805 if(effectList[i]){ 806 audio = effectList[i]; 807 audio.setVolume(this._effectVolume); 808 audio.play(0, loop); 809 }else if(!SWA && i > this._maxAudioInstance){ 810 cc.log("Error: %s greater than %d", url, this._maxAudioInstance); 811 }else{ 812 var audio = loader.cache[url]; 813 if(!audio){ 814 cc.loader.load(url); 815 audio = loader.cache[url]; 816 } 817 audio = audio.cloneNode(); 818 audio.setVolume(this._effectVolume); 819 audio.loop = loop || false; 820 audio.play(); 821 effectList.push(audio); 822 } 823 824 return audio; 825 }, 826 827 /** 828 * Set the volume of sound effects. 829 * @param {Number} volume Volume must be in 0.0~1.0 . 830 * @example 831 * //example 832 * cc.audioEngine.setEffectsVolume(0.5); 833 */ 834 setEffectsVolume: function(volume){ 835 volume = volume - 0; 836 if(isNaN(volume)) volume = 1; 837 if(volume > 1) volume = 1; 838 if(volume < 0) volume = 0; 839 840 this._effectVolume = volume; 841 var audioPool = this._audioPool; 842 for(var p in audioPool){ 843 var audioList = audioPool[p]; 844 if(Array.isArray(audioList)) 845 for(var i=0; i<audioList.length; i++){ 846 audioList[i].setVolume(volume); 847 } 848 } 849 }, 850 851 /** 852 * The volume of the effects max value is 1.0,the min value is 0.0 . 853 * @return {Number} 854 * @example 855 * //example 856 * var effectVolume = cc.audioEngine.getEffectsVolume(); 857 */ 858 getEffectsVolume: function(){ 859 return this._effectVolume; 860 }, 861 862 /** 863 * Pause playing sound effect. 864 * @param {Number} cc.Audio The return value of function playEffect. 865 * @example 866 * //example 867 * cc.audioEngine.pauseEffect(audioID); 868 */ 869 pauseEffect: function(audio){ 870 if(audio){ 871 audio.pause(); 872 } 873 }, 874 875 /** 876 * Pause all playing sound effect. 877 * @example 878 * //example 879 * cc.audioEngine.pauseAllEffects(); 880 */ 881 pauseAllEffects: function(){ 882 var ap = this._audioPool; 883 for(var p in ap){ 884 var list = ap[p]; 885 for(var i=0; i<ap[p].length; i++){ 886 if(list[i].getPlaying()){ 887 list[i].pause(); 888 } 889 } 890 } 891 }, 892 893 /** 894 * Resume playing sound effect. 895 * @param {Number} cc.Audio The return value of function playEffect. 896 * @audioID 897 * //example 898 * cc.audioEngine.resumeEffect(audioID); 899 */ 900 resumeEffect: function(audio){ 901 if(audio) 902 audio.resume(); 903 }, 904 905 /** 906 * Resume all playing sound effect 907 * @example 908 * //example 909 * cc.audioEngine.resumeAllEffects(); 910 */ 911 resumeAllEffects: function(){ 912 var ap = this._audioPool; 913 for(var p in ap){ 914 var list = ap[p]; 915 for(var i=0; i<ap[p].length; i++){ 916 list[i].resume(); 917 } 918 } 919 }, 920 921 /** 922 * Stop playing sound effect. 923 * @param {Number} cc.Audio The return value of function playEffect. 924 * @example 925 * //example 926 * cc.audioEngine.stopEffect(audioID); 927 */ 928 stopEffect: function(audio){ 929 if(audio) 930 audio.stop(); 931 }, 932 933 /** 934 * Stop all playing sound effects. 935 * @example 936 * //example 937 * cc.audioEngine.stopAllEffects(); 938 */ 939 stopAllEffects: function(){ 940 var ap = this._audioPool; 941 for(var p in ap){ 942 var list = ap[p]; 943 for(var i=0; i<ap[p].length; i++){ 944 list[i].stop(); 945 } 946 } 947 }, 948 949 /** 950 * Unload the preloaded effect from internal buffer 951 * @param {String} url 952 * @example 953 * //example 954 * cc.audioEngine.unloadEffect(EFFECT_FILE); 955 */ 956 unloadEffect: function(url){ 957 if(!url){ 958 return; 959 } 960 961 cc.loader.release(url); 962 var pool = this._audioPool[url]; 963 if(pool) pool.length = 0; 964 delete this._audioPool[url]; 965 delete loader.cache[url]; 966 }, 967 968 /** 969 * End music and effects. 970 */ 971 end: function(){ 972 this.stopMusic(); 973 this.stopAllEffects(); 974 }, 975 976 _pauseCache: [], 977 _pausePlaying: function(){ 978 var bgMusic = this._currMusic; 979 if(bgMusic && bgMusic.getPlaying()){ 980 bgMusic.pause(); 981 this._pauseCache.push(bgMusic); 982 } 983 var ap = this._audioPool; 984 for(var p in ap){ 985 var list = ap[p]; 986 for(var i=0; i<ap[p].length; i++){ 987 if(list[i].getPlaying()){ 988 list[i].pause(); 989 this._pauseCache.push(list[i]); 990 } 991 } 992 } 993 }, 994 995 _resumePlaying: function(){ 996 var list = this._pauseCache; 997 for(var i=0; i<list.length; i++){ 998 list[i].resume(); 999 } 1000 list.length = 0; 1001 } 1002 }; 1003 1004 /** 1005 * ome browsers must click on the page 1006 */ 1007 if(!SWC){ 1008 1009 //TODO Did not complete loading 1010 var reBGM = function(){ 1011 var bg = cc.audioEngine._currMusic; 1012 if( 1013 bg && 1014 bg._touch === false && 1015 bg._playing && 1016 bg.getPlaying() 1017 ){ 1018 bg._touch = true; 1019 bg.play(0, bg.loop); 1020 !polyfill.REPLAY_AFTER_TOUCH && cc._canvas.removeEventListener("touchstart", reBGM); 1021 } 1022 1023 }; 1024 1025 setTimeout(function(){ 1026 if(cc._canvas){ 1027 cc._canvas.addEventListener("touchstart", reBGM, false); 1028 } 1029 }, 150); 1030 } 1031 1032 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () { 1033 cc.audioEngine._pausePlaying(); 1034 }); 1035 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () { 1036 cc.audioEngine._resumePlaying(); 1037 }); 1038 1039 })(window.__audioSupport); 1040