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