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