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 var _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 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 null; 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) cc._addEventListener(audio, "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) 822 cc._addEventListener(audio, "pause", self._effectPauseCb); 823 audio.volume = self._effectsVolume; 824 self._effectCache4Single[url] = audio; 825 } 826 for (var i = 0, li = waitings.length; i < li;) {//reset waitings 827 if (effects[waitings[i]] == audio) { 828 waitings.splice(i, 1); 829 } else i++; 830 } 831 for (var i = 0, li = pauseds.length; i < li;) {//reset pauseds 832 if (effects[pauseds[i]] == audio) { 833 pauseds.splice(i, 1); 834 } else i++; 835 } 836 audio._isToPlay = true;//custom flag 837 return audio; 838 }, 839 _stopAllEffects: function () { 840 var self = this, currEffect = self._currEffect, audioPool = self._audioPool, sglCache = self._effectCache4Single, 841 waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 842 if (!currEffect && waitings.length == 0 && pauseds.length == 0) return; 843 for (var key in sglCache) { 844 var eff = sglCache[key]; 845 eff.duration && (eff.currentTime = eff.duration); 846 } 847 waitings.length = 0; 848 pauseds.length = 0; 849 for (var key in audioPool) {//reset audios in pool to be ended 850 var list = audioPool[key]; 851 for (var i = 0, li = list.length; i < li; i++) { 852 var eff = list[i]; 853 eff.loop = false; 854 eff.duration && (eff.currentTime = eff.duration); 855 } 856 } 857 if (currEffect) self._stopAudio(currEffect); 858 }, 859 _effectPauseCb: function () { 860 var self = this; 861 if (self._isHiddenMode) return;//in this mode, return 862 var currEffect = self._getWaitingEffToPlay();//get eff to play 863 if (currEffect) { 864 if (currEffect._isToPlay) { 865 delete currEffect._isToPlay; 866 currEffect.play(); 867 } 868 else self._resumeAudio(currEffect); 869 } else if (self._needToResumeMusic) { 870 var currMusic = self._currMusic; 871 if (currMusic.duration) {//calculate current time 872 var temp = currMusic.currentTime + self._expendTime4Music; 873 temp = temp - currMusic.duration * ((temp / currMusic.duration) | 0); 874 currMusic.currentTime = temp; 875 } 876 self._expendTime4Music = 0; 877 self._resumeAudio(currMusic); 878 self._musicPlayState = 2; 879 self._needToResumeMusic = false; 880 } 881 ; 882 }, 883 _getWaitingEffToPlay: function () { 884 var self = this, waitings = self._waitingEffIds, effects = self._effects, 885 currEffect = self._currEffect; 886 887 var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0; 888 self._expendTime4Music += expendTime; 889 890 while (true) {//get a audio to play 891 if (waitings.length == 0) break; 892 var effId = waitings.pop(); 893 var eff = effects[effId]; 894 if (!eff) continue; 895 if (eff._isToPlay || eff.loop || (eff.duration && eff.currentTime + expendTime < eff.duration)) { 896 self._currEffectId = effId; 897 self._currEffect = eff; 898 if (!eff._isToPlay && eff.duration) { 899 var temp = eff.currentTime + expendTime; 900 temp = temp - eff.duration * ((temp / eff.duration) | 0); 901 eff.currentTime = temp; 902 } 903 eff._isToPlay = false; 904 return eff; 905 } else { 906 eff.duration && (eff.currentTime = eff.duration); 907 } 908 } 909 self._currEffectId = null; 910 self._currEffect = null; 911 return null; 912 }, 913 914 _pausePlaying: function () {//in this function, do not change any status of audios 915 var self = this, currEffect = self._currEffect; 916 self._isHiddenMode = true; 917 var audio = self._musicPlayState == 2 ? self._currMusic : currEffect; 918 if (audio) { 919 self._playings.push(audio); 920 audio.pause(); 921 } 922 923 }, 924 _resumePlaying: function () {//in this function, do not change any status of audios 925 var self = this, playings = self._playings; 926 self._isHiddenMode = false; 927 if (playings.length > 0) { 928 self._resumeAudio(playings[0]); 929 playings.length = 0; 930 } 931 } 932 933 }); 934 } 935 936 /** 937 * Resource loader for audio. 938 */ 939 cc._audioLoader = { 940 _supportedAudioTypes: null, 941 getBasePath: function () { 942 return cc.loader.audioPath; 943 }, 944 _load: function (realUrl, url, res, count, tryArr, audio, cb) { 945 var self = this, locLoader = cc.loader, path = cc.path; 946 var types = this._supportedAudioTypes; 947 var extname = ""; 948 if (types.length == 0) return cb("can not support audio!"); 949 if (count == -1) { 950 extname = (path.extname(realUrl) || "").toLowerCase(); 951 if (!self.audioTypeSupported(extname)) { 952 extname = types[0]; 953 count = 0; 954 } 955 } else if (count < types.length) { 956 extname = types[count]; 957 } else { 958 return cb("can not found the resource of audio! Last match url is : " + realUrl); 959 } 960 if (tryArr.indexOf(extname) >= 0) return self._load(realUrl, url, res, count + 1, tryArr, audio, cb); 961 realUrl = path.changeExtname(realUrl, extname); 962 tryArr.push(extname); 963 audio = self._loadAudio(realUrl, audio, function (err) { 964 if (err) return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);//can not found 965 cb(null, audio); 966 }); 967 locLoader.cache[url] = audio; 968 }, 969 970 audioTypeSupported: function (type) { 971 if (!type) return false; 972 return this._supportedAudioTypes.indexOf(type.toLowerCase()) >= 0; 973 }, 974 _loadAudio: function (url, audio, cb) { 975 var _Audio = cc.WebAudio || Audio; 976 if (arguments.length == 2) { 977 cb = audio, audio = new _Audio(); 978 } else if (arguments.length == 3 && !audio) { 979 audio = new _Audio(); 980 } 981 audio.src = url; 982 audio.preload = "auto"; 983 984 var ua = navigator.userAgent; 985 if (/Mobile/.test(ua) && (/iPhone OS/.test(ua) || /iPad/.test(ua) || /Firefox/.test(ua)) || /MSIE/.test(ua)) { 986 audio.load(); 987 cb(null, audio); 988 } else { 989 var canplaythrough = "canplaythrough", error = "error"; 990 cc._addEventListener(audio, canplaythrough, function () { 991 cb(null, audio); 992 this.removeEventListener(canplaythrough, arguments.callee, false); 993 this.removeEventListener(error, arguments.callee, false); 994 }, false); 995 cc._addEventListener(audio, error, function () { 996 cb("load " + url + " failed") 997 this.removeEventListener(canplaythrough, arguments.callee, false); 998 this.removeEventListener(error, arguments.callee, false); 999 }, false); 1000 audio.load(); 1001 } 1002 return audio; 1003 }, 1004 load: function (realUrl, url, res, cb) { 1005 var tryArr = []; 1006 this._load(realUrl, url, res, -1, tryArr, null, cb); 1007 } 1008 }; 1009 cc._audioLoader._supportedAudioTypes = function () { 1010 var au = cc.newElement('audio'), arr = []; 1011 ; 1012 if (au.canPlayType) { 1013 // <audio> tag is supported, go on 1014 var _check = function (typeStr) { 1015 var result = au.canPlayType(typeStr); 1016 return result != "no" && result != ""; 1017 }; 1018 if (_check('audio/ogg; codecs="vorbis"')) arr.push(".ogg"); 1019 if (_check("audio/mpeg")) arr.push(".mp3"); 1020 if (_check('audio/wav; codecs="1"')) arr.push(".wav"); 1021 if (_check("audio/mp4")) arr.push(".mp4"); 1022 if (_check("audio/x-m4a") || _check("audio/aac")) arr.push(".m4a"); 1023 } 1024 return arr; 1025 }(); 1026 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], cc._audioLoader); 1027 1028 // Initialize Audio engine singleton 1029 cc.audioEngine = cc.AudioEngineForSingle ? new cc.AudioEngineForSingle() : new cc.AudioEngine(); 1030 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () { 1031 cc.audioEngine._pausePlaying(); 1032 }); 1033 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () { 1034 cc.audioEngine._resumePlaying(); 1035 }); 1036