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