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