1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga 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 if (cc.sys._supportWebAudio) { 28 var _ctx = cc.webAudioContext = new (window.AudioContext || window.webkitAudioContext || window.mozAudioContext)(); 29 /** 30 * A class of Web Audio. 31 * @class 32 * @extends cc.Class 33 */ 34 cc.WebAudio = cc.Class.extend({ 35 _events : null, 36 _buffer : null, 37 _sourceNode : null, 38 _volumeNode : null, 39 40 src : null, 41 preload : null,//"none" or "metadata" or "auto" or "" (empty string) or empty TODO not used here 42 autoplay : null, //"autoplay" or "" (empty string) or empty 43 controls : null, //"controls" or "" (empty string) or empty TODO not used here 44 mediagroup : null, 45 46 //The following IDL attributes and methods are exposed to dynamic scripts. 47 currentTime : 0, 48 startTime : 0, 49 duration : 0, // TODO not used here 50 51 _loop : null, //"loop" or "" (empty string) or empty 52 _volume : 1, 53 54 _pauseTime : 0, 55 _paused : false, 56 _stopped : true, 57 58 _loadState : -1,//-1 : not loaded, 0 : waiting, 1 : loaded, -2 : load failed 59 60 ctor : function(src){ 61 var self = this; 62 self._events = {}; 63 self.src = src; 64 65 if(_ctx["createGain"]) 66 self._volumeNode = _ctx["createGain"](); 67 else 68 self._volumeNode = _ctx["createGainNode"](); 69 70 self._onSuccess1 = self._onSuccess.bind(this); 71 self._onError1 = self._onError.bind(this); 72 }, 73 74 _play : function(offset){ 75 var self = this; 76 var sourceNode = self._sourceNode = _ctx["createBufferSource"](); 77 var volumeNode = self._volumeNode; 78 79 sourceNode.buffer = self._buffer; 80 volumeNode["gain"].value = self._volume; 81 sourceNode["connect"](volumeNode); 82 volumeNode["connect"](_ctx["destination"]); 83 sourceNode.loop = self._loop; 84 85 self._paused = false; 86 self._stopped = false; 87 88 /* 89 * Safari on iOS 6 only supports noteOn(), noteGrainOn(), and noteOff() now.(iOS 6.1.3) 90 * The latest version of chrome has supported start() and stop() 91 * start() & stop() are specified in the latest specification (written on 04/26/2013) 92 * Reference: https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html 93 * noteOn(), noteGrainOn(), and noteOff() are specified in Draft 13 version (03/13/2012) 94 * Reference: http://www.w3.org/2011/audio/drafts/2WD/Overview.html 95 */ 96 if (sourceNode.start) { 97 // starting from offset means resuming from where it paused last time 98 sourceNode.start(0, offset); 99 } else if (sourceNode["noteGrainOn"]) { 100 var duration = sourceNode.buffer.duration; 101 if (self.loop) { 102 /* 103 * On Safari on iOS 6, if loop == true, the passed in @param duration will be the duration from now on. 104 * In other words, the sound will keep playing the rest of the music all the time. 105 * On latest chrome desktop version, the passed in duration will only be the duration in this cycle. 106 * Now that latest chrome would have start() method, it is prepared for iOS here. 107 */ 108 sourceNode["noteGrainOn"](0, offset, duration); 109 } else { 110 sourceNode["noteGrainOn"](0, offset, duration - offset); 111 } 112 } else { 113 // if only noteOn() is supported, resuming sound will NOT work 114 sourceNode["noteOn"](0); 115 } 116 self._pauseTime = 0; 117 }, 118 _stop : function(){ 119 var self = this, sourceNode = self._sourceNode; 120 if(self._stopped) return; 121 if(sourceNode.stop) sourceNode.stop(0); 122 else sourceNode.noteOff(0); 123 self._stopped = true; 124 }, 125 play : function(){ 126 var self = this; 127 if(self._loadState == -1){ 128 self._loadState = 0; 129 return; 130 }else if(self._loadState != 1) return; 131 132 var sourceNode = self._sourceNode; 133 if(!self._stopped && sourceNode && sourceNode["playbackState"] == 2) return;//playing 134 self.startTime = _ctx.currentTime; 135 this._play(0); 136 }, 137 pause : function(){ 138 this._pauseTime = _ctx.currentTime; 139 this._paused = true; 140 this._stop(); 141 }, 142 resume : function(){ 143 var self = this; 144 if(self._paused){ 145 var offset = self._buffer ? (self._pauseTime - self.startTime) % self._buffer.duration : 0; 146 this._play(offset); 147 } 148 }, 149 stop : function(){ 150 this._pauseTime = 0; 151 this._paused = false; 152 this._stop(); 153 }, 154 load : function(){ 155 var self = this; 156 if(self._loadState == 1) return; 157 self._loadState = -1;//not loaded 158 159 self.played = false; 160 self.ended = true; 161 var request = new XMLHttpRequest(); 162 request.open("GET", self.src, true); 163 request.responseType = "arraybuffer"; 164 165 // Our asynchronous callback 166 request.onload = function() { 167 _ctx["decodeAudioData"](request.response, self._onSuccess1, self._onError1); 168 }; 169 request.send(); 170 }, 171 172 addEventListener : function(eventName, event){ 173 this._events[eventName] = event.bind(this); 174 }, 175 removeEventListener : function(eventName){ 176 delete this._events[eventName]; 177 }, 178 179 canplay : function(){ 180 return cc.sys._supportWebAudio; 181 }, 182 _onSuccess : function(buffer){ 183 var self = this; 184 self._buffer = buffer; 185 186 var success = self._events["success"], canplaythrough = self._events["canplaythrough"]; 187 if(success) success(); 188 if(canplaythrough) canplaythrough(); 189 if(self._loadState == 0 || self.autoplay == "autoplay" || self.autoplay == true) self._play(); 190 self._loadState = 1;//loaded 191 }, 192 _onError : function(){ 193 var error = this._events["error"] 194 if(error) error(); 195 this._loadState = -2;//load failed 196 }, 197 cloneNode : function(){ 198 var self = this, obj = new cc.WebAudio(self.src); 199 obj.volume = self.volume; 200 obj._loadState = self._loadState; 201 obj._buffer = self._buffer; 202 if(obj._loadState == 0 || obj._loadState == -1) obj.load(); 203 return obj; 204 } 205 206 }); 207 window._p = cc.WebAudio.prototype; 208 /** @expose */ 209 _p.loop; 210 cc.defineGetterSetter(_p, "loop", function(){ 211 return this._loop; 212 }, function(loop){ 213 this._loop = loop; 214 if(this._sourceNode) this._sourceNode.loop = loop; 215 }); 216 /** @expose */ 217 _p.volume; 218 cc.defineGetterSetter(_p, "volume", function(){ 219 return this._volume; 220 }, function(volume){ 221 this._volume = volume; 222 this._volumeNode["gain"].value = volume; 223 }); 224 /** @expose */ 225 _p.ended; 226 cc.defineGetterSetter(_p, "paused", function(){ 227 return this._paused; 228 }); 229 /** @expose */ 230 _p.ended; 231 cc.defineGetterSetter(_p, "ended", function(){ 232 var sourceNode = this._sourceNode; 233 return !this._paused && (this._stopped || !sourceNode || sourceNode["playbackState"] == 3); 234 }); 235 /** @expose */ 236 _p.played; 237 cc.defineGetterSetter(_p, "played", function(){ 238 var sourceNode = this._sourceNode; 239 return sourceNode && sourceNode["playbackState"] == 2; 240 }); 241 delete window._p; 242 } 243 244 /** 245 * @namespace A simple Audio Engine engine API. 246 * @name cc.audioEngine 247 */ 248 cc.AudioEngine = cc.Class.extend(/** @lends cc.audioEngine# */{ 249 _soundSupported:false, // if sound is not enabled, this engine's init() will return false 250 251 _currMusic : null, 252 _currMusicPath : null, 253 _musicPlayState : 0, //0 : stopped, 1 : paused, 2 : playing 254 255 _audioID:0, 256 _effects:{}, //effects cache 257 _audioPool : {}, //audio pool for effects 258 _effectsVolume:1, // the volume applied to all effects 259 _maxAudioInstance : 5,//max count of audios that has same url 260 261 _effectPauseCb : null, 262 263 _playings : [],//only store when window is hidden 264 265 ctor:function(){ 266 var self = this; 267 self._soundSupported = cc._audioLoader._supportedAudioTypes.length > 0; 268 if(self._effectPauseCb) self._effectPauseCb = self._effectPauseCb.bind(self); 269 }, 270 271 /** 272 * Indicates whether any background music can be played or not. 273 * @returns {boolean} <i>true</i> if the background music is playing, otherwise <i>false</i> 274 */ 275 willPlayMusic: function() { 276 return false; 277 }, 278 279 /** 280 * The volume of the effects max value is 1.0,the min value is 0.0 . 281 * @return {Number} 282 * @example 283 * //example 284 * var effectVolume = cc.audioEngine.getEffectsVolume(); 285 */ 286 getEffectsVolume: function() { 287 return this._effectsVolume; 288 }, 289 290 //music begin 291 /** 292 * Play music. 293 * @param {String} url The path of the music file without filename extension. 294 * @param {Boolean} loop Whether the music loop or not. 295 * @example 296 * //example 297 * cc.audioEngine.playMusic(path, false); 298 */ 299 playMusic : function (url, loop) { 300 var self = this; 301 if(!self._soundSupported) return; 302 303 var audio = self._currMusic; 304 if(audio) this._stopAudio(audio); 305 if(url != self._currMusicPath){ 306 audio = self._getAudioByUrl(url); 307 self._currMusic = audio; 308 self._currMusicPath = url; 309 } 310 if(!audio) return; 311 audio.loop = loop || false; 312 self._playMusic(audio); 313 }, 314 _getAudioByUrl : function(url){ 315 var locLoader = cc.loader, audio = locLoader.getRes(url); 316 if(!audio) { 317 locLoader.load(url); 318 audio = locLoader.getRes(url); 319 } 320 return audio; 321 }, 322 _playMusic : function(audio){ 323 if(!audio.ended){ 324 if(audio.stop){//cc.WebAudio 325 audio.stop(); 326 }else{ 327 audio.pause(); 328 audio.duration && (audio.currentTime = audio.duration); 329 } 330 } 331 this._musicPlayState = 2; 332 audio.play(); 333 }, 334 /** 335 * Stop playing music. 336 * @param {Boolean} releaseData If release the music data or not.As default value is false. 337 * @example 338 * //example 339 * cc.audioEngine.stopMusic(); 340 */ 341 stopMusic : function (releaseData) { 342 if(this._musicPlayState > 0){ 343 var audio = this._currMusic; 344 if(!audio) return; 345 this._stopAudio(audio); 346 if (releaseData) cc.loader.release(this._currMusicPath); 347 this._currMusic = null; 348 this._currMusicPath = null; 349 this._musicPlayState = 0; 350 } 351 }, 352 353 _stopAudio : function(audio){ 354 if(audio && !audio.ended){ 355 if(audio.stop){//cc.WebAudio 356 audio.stop(); 357 }else{ 358 audio.pause(); 359 audio.duration && (audio.currentTime = audio.duration); 360 } 361 } 362 }, 363 364 /** 365 * Pause playing music. 366 * @example 367 * //example 368 * cc.audioEngine.pauseMusic(); 369 */ 370 pauseMusic:function () { 371 if(this._musicPlayState == 2){ 372 this._currMusic.pause(); 373 this._musicPlayState = 1; 374 } 375 }, 376 377 /** 378 * Resume playing music. 379 * @example 380 * //example 381 * cc.audioEngine.resumeMusic(); 382 */ 383 resumeMusic:function () { 384 if(this._musicPlayState == 1){ 385 var audio = this._currMusic; 386 this._resumeAudio(audio); 387 this._musicPlayState = 2; 388 } 389 }, 390 _resumeAudio : function(audio){ 391 if(audio && !audio.ended){ 392 if(audio.resume) audio.resume();//cc.WebAudio 393 else audio.play(); 394 } 395 }, 396 397 /** 398 * Rewind playing music. 399 * @example 400 * //example 401 * cc.audioEngine.rewindMusic(); 402 */ 403 rewindMusic:function () { 404 if(this._currMusic) this._playMusic(this._currMusic); 405 }, 406 407 /** 408 * The volume of the music max value is 1.0,the min value is 0.0 . 409 * @return {Number} 410 * @example 411 * //example 412 * var volume = cc.audioEngine.getMusicVolume(); 413 */ 414 getMusicVolume:function () { 415 return this._musicPlayState == 0 ? 0 : this._currMusic.volume; 416 }, 417 418 /** 419 * Set the volume of music. 420 * @param {Number} volume Volume must be in 0.0~1.0 . 421 * @example 422 * //example 423 * cc.audioEngine.setMusicVolume(0.5); 424 */ 425 setMusicVolume:function (volume) { 426 if(this._musicPlayState > 0){ 427 this._currMusic.volume = Math.min(Math.max(volume, 0), 1); 428 } 429 }, 430 /** 431 * Whether the music is playing. 432 * @return {Boolean} If is playing return true,or return false. 433 * @example 434 * //example 435 * if (cc.audioEngine.isMusicPlaying()) { 436 * cc.log("music is playing"); 437 * } 438 * else { 439 * cc.log("music is not playing"); 440 * } 441 */ 442 isMusicPlaying: function () { 443 return this._musicPlayState == 2 && this._currMusic && !this._currMusic.ended; 444 }, 445 //music end 446 447 //effect begin 448 _getEffectList : function(url){ 449 var list = this._audioPool[url]; 450 if(!list) list = this._audioPool[url] = []; 451 return list; 452 }, 453 _getEffect : function(url){ 454 var self = this, audio; 455 if(!self._soundSupported) return; 456 457 var effList = this._getEffectList(url); 458 for(var i = 0, li = effList.length; i < li; i++){ 459 var eff = effList[i]; 460 if(eff.ended){ 461 audio = eff; 462 audio.currentTime = 0; 463 if (window.chrome) audio.load(); 464 break; 465 } 466 } 467 if(!audio){ 468 if (effList.length >= this._maxAudioInstance) { 469 cc.log("Error: " + url + " greater than " + this._maxAudioInstance); 470 return null; 471 } 472 audio = self._getAudioByUrl(url); 473 if(!audio) return null; 474 audio = audio.cloneNode(true); 475 if(self._effectPauseCb) audio.addEventListener("pause", self._effectPauseCb); 476 audio.volume = this._effectsVolume; 477 effList.push(audio); 478 } 479 return audio; 480 }, 481 /** 482 * Play sound effect. 483 * @param {String} url The path of the sound effect with filename extension. 484 * @param {Boolean} loop Whether to loop the effect playing, default value is false 485 * @return {Number|null} the audio id 486 * @example 487 * //example 488 * var soundId = cc.audioEngine.playEffect(path); 489 */ 490 playEffect: function (url, loop) { 491 var audio = this._getEffect(url); 492 if(!audio) return null; 493 audio.loop = loop || false; 494 audio.play(); 495 var audioId = this._audioID++; 496 this._effects[audioId] = audio; 497 return audioId; 498 }, 499 500 /** 501 * Set the volume of sound effects. 502 * @param {Number} volume Volume must be in 0.0~1.0 . 503 * @example 504 * //example 505 * cc.audioEngine.setEffectsVolume(0.5); 506 */ 507 setEffectsVolume:function (volume) { 508 volume = this._effectsVolume = Math.min(Math.max(volume, 0), 1); 509 var effects = this._effects; 510 for (var key in effects) { 511 effects[key].volume = volume; 512 } 513 }, 514 515 /** 516 * Pause playing sound effect. 517 * @param {Number} audioID The return value of function playEffect. 518 * @example 519 * //example 520 * cc.audioEngine.pauseEffect(audioID); 521 */ 522 pauseEffect:function (audioID) { 523 var audio = this._effects[audioID]; 524 if(audio && !audio.ended){ 525 audio.pause(); 526 } 527 }, 528 529 /** 530 * Pause all playing sound effect. 531 * @example 532 * //example 533 * cc.audioEngine.pauseAllEffects(); 534 */ 535 pauseAllEffects:function () { 536 var effects = this._effects; 537 for (var key in effects) { 538 var eff = effects[key]; 539 if(!eff.ended) eff.pause(); 540 } 541 }, 542 543 /** 544 * Resume playing sound effect. 545 * @param {Number} effectId The return value of function playEffect. 546 * @audioID 547 * //example 548 * cc.audioEngine.resumeEffect(audioID); 549 */ 550 resumeEffect:function (effectId) { 551 this._resumeAudio(this._effects[effectId]) 552 }, 553 554 /** 555 * Resume all playing sound effect 556 * @example 557 * //example 558 * cc.audioEngine.resumeAllEffects(); 559 */ 560 resumeAllEffects:function () { 561 var effects = this._effects; 562 for (var key in effects) { 563 this._resumeAudio(effects[key]); 564 } 565 }, 566 567 /** 568 * Stop playing sound effect. 569 * @param {Number} effectId The return value of function playEffect. 570 * @example 571 * //example 572 * cc.audioEngine.stopEffect(audioID); 573 */ 574 stopEffect:function (effectId) { 575 this._stopAudio(this._effects[effectId]); 576 delete this._effects[effectId]; 577 }, 578 579 /** 580 * Stop all playing sound effects. 581 * @example 582 * //example 583 * cc.audioEngine.stopAllEffects(); 584 */ 585 stopAllEffects:function () { 586 var effects = this._effects; 587 for (var key in effects) { 588 this._stopAudio(effects[key]); 589 delete effects[key]; 590 } 591 }, 592 593 /** 594 * Unload the preloaded effect from internal buffer 595 * @param {String} url 596 * @example 597 * //example 598 * cc.audioEngine.unloadEffect(EFFECT_FILE); 599 */ 600 unloadEffect:function (url) { 601 var locLoader = cc.loader, locEffects = this._effects, effectList = this._getEffectList(url); 602 locLoader.release(url);//release the resource in cc.loader first. 603 if(effectList.length == 0) return; 604 var realUrl = effectList[0].src; 605 delete this._audioPool[url]; 606 for (var key in locEffects) { 607 if(locEffects[key].src == realUrl){ 608 this._stopAudio(locEffects[key]); 609 delete locEffects[key]; 610 } 611 } 612 }, 613 //effect end 614 615 /** 616 * End music and effects. 617 */ 618 end : function(){ 619 this.stopMusic(); 620 this.stopAllEffects(); 621 }, 622 623 /** 624 * Called only when the hidden event of window occurs. 625 * @private 626 */ 627 _pausePlaying : function(){//in this function, do not change any status of audios 628 var self = this, effects = self._effects, eff; 629 for (var key in effects){ 630 eff = effects[key]; 631 if(eff && !eff.ended && !eff.paused){ 632 self._playings.push(eff); 633 eff.pause(); 634 } 635 } 636 if(self.isMusicPlaying()) { 637 self._playings.push(self._currMusic); 638 self._currMusic.pause(); 639 } 640 }, 641 /** 642 * Called only when the hidden event of window occurs. 643 * @private 644 */ 645 _resumePlaying : function(){//in this function, do not change any status of audios 646 var self = this, playings = this._playings; 647 for(var i = 0, li = playings.length; i < li; i++){ 648 self._resumeAudio(playings[i]); 649 } 650 playings.length = 0; 651 } 652 653 }); 654 655 656 if (!cc.sys._supportWebAudio && cc.sys._supportMultipleAudio < 0){ 657 /** 658 * Extended AudioEngine for single audio mode. 659 * @class 660 */ 661 cc.AudioEngineForSingle = cc.AudioEngine.extend({ 662 _waitingEffIds : [], 663 _pausedEffIds : [], 664 _currEffect : null, 665 _maxAudioInstance : 2, 666 _effectCache4Single : {},//{url:audio}, 667 _needToResumeMusic : false, 668 _expendTime4Music : 0, 669 670 _isHiddenMode : false, 671 672 _playMusic:function (audio) { 673 this._stopAllEffects(); 674 this._super(audio); 675 }, 676 677 resumeMusic : function(){ 678 var self = this; 679 if(self._musicPlayState == 1){ 680 self._stopAllEffects(); 681 self._needToResumeMusic = false; 682 self._expendTime4Music = 0; 683 self._super(); 684 } 685 }, 686 687 playEffect : function(url, loop){ 688 var self = this, currEffect = self._currEffect; 689 var audio = loop ? self._getEffect(url) : self._getSingleEffect(url); 690 if(!audio) return null; 691 audio.loop = loop || false; 692 var audioId = self._audioID++; 693 self._effects[audioId] = audio; 694 695 if(self.isMusicPlaying()){ 696 self.pauseMusic(); 697 self._needToResumeMusic = true; 698 } 699 if(currEffect) { 700 if(currEffect != audio) self._waitingEffIds.push(self._currEffectId); 701 self._waitingEffIds.push(audioId); 702 currEffect.pause(); 703 }else{ 704 self._currEffect = audio; 705 self._currEffectId = audioId; 706 audio.play(); 707 } 708 return audioId; 709 }, 710 pauseEffect : function(effectId){ 711 cc.log("pauseEffect not supported in single audio mode!") 712 // var currEffect = this._currEffect; 713 // if(this._currEffectId != effectId || !currEffect || currEffect.ended) return; 714 // this._pausedEffIds.push(this._currEffectId); 715 // currEffect.pause(); 716 }, 717 pauseAllEffects:function () { 718 var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds, currEffect = self._currEffect; 719 if(!currEffect) return; 720 for(var i = 0, li = waitings.length; i < li; i++){ 721 pauseds.push(waitings[i]); 722 } 723 waitings.length = 0;//clear 724 pauseds.push(self._currEffectId); 725 currEffect.pause(); 726 }, 727 resumeEffect:function (effectId) { 728 cc.log("resumeEffect not supported in single audio mode!") 729 // var self = this, currEffect = self._currEffect, pauseds = self._pausedEffIds; 730 // if(self._currEffectId == effectId) return;//the effect is playing 731 // var index = pauseds.indexOf(effectId); 732 // if(index >= 0){ 733 // pauseds.splice(index, 1);//delete item 734 // var eff = self._effects[effectId]; 735 // var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0; 736 // if(eff && eff.currentTime + expendTime < eff.duration) { 737 // self._waitingEffIds.push(self._currEffectId); 738 // self._waitingEffIds.push(effectId); 739 // self._currEffect.pause(); 740 // } 741 // } 742 }, 743 resumeAllEffects:function(){ 744 var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 745 746 if(self.isMusicPlaying()){//if music is playing, pause it first 747 self.pauseMusic(); 748 self._needToResumeMusic = true; 749 } 750 751 for(var i = 0, li = pauseds.length; i < li; i++){//move pauseds to waitings 752 waitings.push(pauseds[i]); 753 } 754 pauseds.length = 0;//clear 755 if(!self._currEffect && waitings.length >= 0){//is none currEff, resume the newest effect in waitings 756 var effId = waitings.pop(); 757 var eff = self._effects[effId]; 758 if(eff){ 759 self._currEffectId = effId; 760 self._currEffect = eff; 761 self._resumeAudio(eff); 762 } 763 } 764 }, 765 stopEffect:function(effectId){ 766 var self = this, currEffect = self._currEffect, waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 767 if(currEffect && this._currEffectId == effectId){//if the eff to be stopped is currEff 768 this._stopAudio(currEffect); 769 }else{//delete from waitings or pauseds 770 var index = waitings.indexOf(effectId); 771 if(index>=0){ 772 waitings.splice(index, 1); 773 }else{ 774 index = pauseds.indexOf(effectId); 775 if(index >= 0) pauseds.splice(index, 1); 776 } 777 } 778 }, 779 stopAllEffects:function () { 780 var self = this; 781 self._stopAllEffects(); 782 if(!self._currEffect && self._needToResumeMusic){//need to resume music 783 self._resumeAudio(self._currMusic); 784 self._musicPlayState = 2; 785 self._needToResumeMusic = false; 786 self._expendTime4Music = 0; 787 } 788 }, 789 790 unloadEffect:function (url) { 791 var self = this, locLoader = cc.loader, locEffects = self._effects, effCache = self._effectCache4Single, 792 effectList = self._getEffectList(url), currEffect = self._currEffect; 793 locLoader.release(url);//release the resource in cc.loader first. 794 if(effectList.length == 0 && !effCache[url]) return; 795 var realUrl = effectList.length > 0 ? effectList[0].src : effCache[url].src; 796 delete self._audioPool[url]; 797 delete effCache[url]; 798 for (var key in locEffects) { 799 if(locEffects[key].src == realUrl){ 800 delete locEffects[key]; 801 } 802 } 803 if(currEffect && currEffect.src == realUrl) self._stopAudio(currEffect);//need to stop currEff 804 }, 805 806 /** 807 * When `loop == false`, one url one audio. 808 * @param url 809 * @returns {*} 810 * @private 811 */ 812 _getSingleEffect : function(url){ 813 var self = this, audio = self._effectCache4Single[url], locLoader = cc.loader, 814 waitings = self._waitingEffIds, pauseds = self._pausedEffIds, effects = self._effects; 815 if(audio){ 816 audio.duration && (audio.currentTime = 0);//reset current time 817 }else{ 818 audio = self._getAudioByUrl(url); 819 if(!audio) return null; 820 audio = audio.cloneNode(true); 821 if(self._effectPauseCb) audio.addEventListener("pause", self._effectPauseCb); 822 audio.volume = self._effectsVolume; 823 self._effectCache4Single[url] = audio; 824 } 825 for(var i = 0, li = waitings.length; i < li;){//reset waitings 826 if(effects[waitings[i]] == audio){ 827 waitings.splice(i, 1); 828 }else i++; 829 } 830 for(var i = 0, li = pauseds.length; i < li;){//reset pauseds 831 if(effects[pauseds[i]] == audio){ 832 pauseds.splice(i, 1); 833 }else i++; 834 } 835 audio._isToPlay = true;//custom flag 836 return audio; 837 }, 838 _stopAllEffects: function(){ 839 var self = this, currEffect = self._currEffect, audioPool = self._audioPool, sglCache = self._effectCache4Single, 840 waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 841 if(!currEffect && waitings.length == 0 && pauseds.length == 0) return; 842 for (var key in sglCache) { 843 var eff = sglCache[key]; 844 eff.duration && (eff.currentTime = eff.duration); 845 } 846 waitings.length = 0; 847 pauseds.length = 0; 848 for (var key in audioPool) {//reset audios in pool to be ended 849 var list = audioPool[key]; 850 for(var i = 0, li = list.length; i < li; i++){ 851 var eff = list[i]; 852 eff.loop = false; 853 eff.duration && (eff.currentTime = eff.duration); 854 } 855 } 856 if(currEffect) self._stopAudio(currEffect); 857 }, 858 _effectPauseCb : function(){ 859 var self = this; 860 if(self._isHiddenMode) return;//in this mode, return 861 var currEffect = self._getWaitingEffToPlay();//get eff to play 862 if(currEffect){ 863 if(currEffect._isToPlay) { 864 delete currEffect._isToPlay; 865 currEffect.play(); 866 } 867 else self._resumeAudio(currEffect); 868 }else if(self._needToResumeMusic) { 869 var currMusic = self._currMusic; 870 if(currMusic.duration){//calculate current time 871 var temp = currMusic.currentTime + self._expendTime4Music; 872 temp = temp - currMusic.duration * ((temp/currMusic.duration)|0); 873 currMusic.currentTime = temp; 874 } 875 self._expendTime4Music = 0; 876 self._resumeAudio(currMusic); 877 self._musicPlayState = 2; 878 self._needToResumeMusic = false; 879 }; 880 }, 881 _getWaitingEffToPlay : function(){ 882 var self = this, waitings = self._waitingEffIds, effects = self._effects, 883 currEffect = self._currEffect; 884 885 var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime||0) : 0; 886 self._expendTime4Music += expendTime; 887 888 while(true){//get a audio to play 889 if(waitings.length == 0) break; 890 var effId = waitings.pop(); 891 var eff = effects[effId]; 892 if(!eff) continue; 893 if(eff._isToPlay || eff.loop || (eff.duration && eff.currentTime + expendTime < eff.duration)) { 894 self._currEffectId = effId; 895 self._currEffect = eff; 896 if(!eff._isToPlay && eff.duration){ 897 var temp = eff.currentTime + expendTime; 898 temp = temp - eff.duration * ((temp/eff.duration)|0); 899 eff.currentTime = temp; 900 } 901 eff._isToPlay = false; 902 return eff; 903 }else{ 904 eff.duration && (eff.currentTime = eff.duration); 905 } 906 } 907 self._currEffectId = null; 908 self._currEffect = null; 909 return null; 910 }, 911 912 _pausePlaying : function(){//in this function, do not change any status of audios 913 var self = this, currEffect = self._currEffect; 914 self._isHiddenMode = true; 915 var audio = self._musicPlayState == 2 ? self._currMusic : currEffect; 916 if(audio){ 917 self._playings.push(audio); 918 audio.pause(); 919 } 920 921 }, 922 _resumePlaying : function(){//in this function, do not change any status of audios 923 var self = this, playings = self._playings; 924 self._isHiddenMode = false; 925 if(playings.length > 0){ 926 self._resumeAudio(playings[0]); 927 playings.length = 0; 928 } 929 } 930 931 }); 932 } 933 934 /** 935 * Resource loader for audio. 936 */ 937 cc._audioLoader = { 938 _supportedAudioTypes : null, 939 getBasePath : function(){ 940 return cc.loader.audioPath; 941 }, 942 _load : function(realUrl, url, res, count, tryArr, audio, cb){ 943 var self = this, locLoader = cc.loader, path = cc.path; 944 var types = this._supportedAudioTypes; 945 var extname = ""; 946 if(types.length == 0) return cb("can not support audio!"); 947 if(count == -1){ 948 extname = (path.extname(realUrl) || "").toLowerCase(); 949 if(!self.audioTypeSupported(extname)) { 950 extname = types[0]; 951 count = 0; 952 } 953 }else if(count < types.length){ 954 extname = types[count]; 955 }else{ 956 return cb("can not found the resource of audio! Last match url is : " + realUrl); 957 } 958 if(tryArr.indexOf(extname) >= 0) return self._load(realUrl, url, res, count+1, tryArr, audio, cb); 959 realUrl = path.changeExtname(realUrl, extname); 960 tryArr.push(extname); 961 audio = self._loadAudio(realUrl, audio, function(err){ 962 if(err) return self._load(realUrl, url, res, count+1, tryArr, audio, cb);//can not found 963 cb(null, audio); 964 }); 965 locLoader.cache[url] = audio; 966 }, 967 968 audioTypeSupported : function(type){ 969 if(!type) return false; 970 return this._supportedAudioTypes.indexOf(type.toLowerCase()) >= 0; 971 }, 972 _loadAudio : function(url, audio, cb){ 973 var _Audio = cc.WebAudio || Audio; 974 if(arguments.length == 2){ 975 cb = audio, audio = new _Audio(); 976 }else if(arguments.length == 3 && !audio){ 977 audio = new _Audio(); 978 } 979 audio.src = url; 980 audio.preload = "auto"; 981 982 var ua = navigator.userAgent; 983 if(/Mobile/.test(ua) && (/iPhone OS/.test(ua)||/iPad/.test(ua)||/Firefox/.test(ua)) || /MSIE/.test(ua)){ 984 audio.load(); 985 cb(null, audio); 986 }else{ 987 var canplaythrough = "canplaythrough", error = "error"; 988 audio.addEventListener(canplaythrough, function(){ 989 cb(null, audio); 990 this.removeEventListener(canplaythrough, arguments.callee, false); 991 this.removeEventListener(error, arguments.callee, false); 992 }, false); 993 audio.addEventListener(error, function(){ 994 cb("load " + url + " failed") 995 this.removeEventListener(canplaythrough, arguments.callee, false); 996 this.removeEventListener(error, arguments.callee, false); 997 }, false); 998 audio.load(); 999 } 1000 return audio; 1001 }, 1002 load : function(realUrl, url, res, cb){ 1003 var tryArr = []; 1004 this._load(realUrl, url, res, -1, tryArr, null, cb); 1005 } 1006 }; 1007 cc._audioLoader._supportedAudioTypes = function() { 1008 var au = document.createElement('audio'), arr = [];; 1009 if (au.canPlayType) { 1010 // <audio> tag is supported, go on 1011 var _check = function(typeStr) { 1012 var result = au.canPlayType(typeStr); 1013 return result != "no" && result != ""; 1014 }; 1015 if(_check('audio/ogg; codecs="vorbis"')) arr.push(".ogg"); 1016 if(_check("audio/mpeg")) arr.push(".mp3"); 1017 if(_check('audio/wav; codecs="1"')) arr.push(".wav"); 1018 if(_check("audio/mp4")) arr.push(".mp4"); 1019 if(_check("audio/x-m4a") || _check("audio/aac")) arr.push(".m4a"); 1020 } 1021 return arr; 1022 }(); 1023 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], cc._audioLoader); 1024 1025 // Initialize Audio engine singleton 1026 cc.audioEngine = cc.AudioEngineForSingle ? new cc.AudioEngineForSingle() : new cc.AudioEngine(); 1027 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function(){ 1028 cc.audioEngine._pausePlaying(); 1029 }); 1030 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function(){ 1031 cc.audioEngine._resumePlaying(); 1032 }); 1033