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 777 if (!cc.sys._supportWebAudio && cc.sys._supportMultipleAudio < 0) { 778 779 cc.AudioEngineForSingle = cc.AudioEngine.extend({ 780 _waitingEffIds: [], 781 _pausedEffIds: [], 782 _currEffect: null, 783 _maxAudioInstance: 2, 784 _effectCache4Single: {},//{url:audio}, 785 _needToResumeMusic: false, 786 _expendTime4Music: 0, 787 788 _isHiddenMode: false, 789 790 _playMusic: function (audio) { 791 this._stopAllEffects(); 792 this._super(audio); 793 }, 794 795 resumeMusic: function () { 796 var self = this; 797 if (self._musicPlayState == 1) { 798 self._stopAllEffects(); 799 self._needToResumeMusic = false; 800 self._expendTime4Music = 0; 801 self._super(); 802 } 803 }, 804 805 playEffect: function (url, loop) { 806 var self = this, currEffect = self._currEffect; 807 var audio = loop ? self._getEffect(url) : self._getSingleEffect(url); 808 if (!audio) return null; 809 audio.loop = loop || false; 810 var audioId = self._audioID++; 811 self._effects[audioId] = audio; 812 813 if (self.isMusicPlaying()) { 814 self.pauseMusic(); 815 self._needToResumeMusic = true; 816 } 817 if (currEffect) { 818 if (currEffect != audio) self._waitingEffIds.push(self._currEffectId); 819 self._waitingEffIds.push(audioId); 820 currEffect.pause(); 821 } else { 822 self._currEffect = audio; 823 self._currEffectId = audioId; 824 audio.play(); 825 } 826 return audioId; 827 }, 828 829 pauseEffect: function (effectId) { 830 cc.log("pauseEffect not supported in single audio mode!"); 831 }, 832 833 pauseAllEffects: function () { 834 var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds, currEffect = self._currEffect; 835 if (!currEffect) return; 836 for (var i = 0, li = waitings.length; i < li; i++) { 837 pauseds.push(waitings[i]); 838 } 839 waitings.length = 0;//clear 840 pauseds.push(self._currEffectId); 841 currEffect.pause(); 842 }, 843 844 resumeEffect: function (effectId) { 845 cc.log("resumeEffect not supported in single audio mode!"); 846 }, 847 848 resumeAllEffects: function () { 849 var self = this, waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 850 851 if (self.isMusicPlaying()) {//if music is playing, pause it first 852 self.pauseMusic(); 853 self._needToResumeMusic = true; 854 } 855 856 for (var i = 0, li = pauseds.length; i < li; i++) {//move pauseds to waitings 857 waitings.push(pauseds[i]); 858 } 859 pauseds.length = 0;//clear 860 if (!self._currEffect && waitings.length >= 0) {//is none currEff, resume the newest effect in waitings 861 var effId = waitings.pop(); 862 var eff = self._effects[effId]; 863 if (eff) { 864 self._currEffectId = effId; 865 self._currEffect = eff; 866 self._resumeAudio(eff); 867 } 868 } 869 }, 870 871 stopEffect: function (effectId) { 872 var self = this, currEffect = self._currEffect, waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 873 if (currEffect && this._currEffectId == effectId) {//if the eff to be stopped is currEff 874 this._stopAudio(currEffect); 875 } else {//delete from waitings or pauseds 876 var index = waitings.indexOf(effectId); 877 if (index >= 0) { 878 waitings.splice(index, 1); 879 } else { 880 index = pauseds.indexOf(effectId); 881 if (index >= 0) pauseds.splice(index, 1); 882 } 883 } 884 }, 885 886 stopAllEffects: function () { 887 var self = this; 888 self._stopAllEffects(); 889 if (!self._currEffect && self._needToResumeMusic) {//need to resume music 890 self._resumeAudio(self._currMusic); 891 self._musicPlayState = 2; 892 self._needToResumeMusic = false; 893 self._expendTime4Music = 0; 894 } 895 }, 896 897 unloadEffect: function (url) { 898 var self = this, locLoader = cc.loader, locEffects = self._effects, effCache = self._effectCache4Single, 899 effectList = self._getEffectList(url), currEffect = self._currEffect; 900 locLoader.release(url);//release the resource in cc.loader first. 901 if (effectList.length == 0 && !effCache[url]) return; 902 var realUrl = effectList.length > 0 ? effectList[0].src : effCache[url].src; 903 delete self._audioPool[url]; 904 delete effCache[url]; 905 for (var key in locEffects) { 906 if (locEffects[key].src == realUrl) { 907 delete locEffects[key]; 908 } 909 } 910 if (currEffect && currEffect.src == realUrl) self._stopAudio(currEffect);//need to stop currEff 911 }, 912 913 //When `loop == false`, one url one audio. 914 _getSingleEffect: function (url) { 915 var self = this, audio = self._effectCache4Single[url], locLoader = cc.loader, 916 waitings = self._waitingEffIds, pauseds = self._pausedEffIds, effects = self._effects; 917 if (audio) { 918 if (audio.readyState > 2) 919 audio.currentTime = 0; //reset current time 920 } else { 921 audio = self._getAudioByUrl(url); 922 if (!audio) return null; 923 audio = audio.cloneNode(true); 924 if (self._effectPauseCb) 925 cc._addEventListener(audio, "pause", self._effectPauseCb); 926 audio.volume = self._effectsVolume; 927 self._effectCache4Single[url] = audio; 928 } 929 for (var i = 0, li = waitings.length; i < li;) {//reset waitings 930 if (effects[waitings[i]] == audio) { 931 waitings.splice(i, 1); 932 } else 933 i++; 934 } 935 for (var i = 0, li = pauseds.length; i < li;) {//reset pauseds 936 if (effects[pauseds[i]] == audio) { 937 pauseds.splice(i, 1); 938 } else 939 i++; 940 } 941 audio._isToPlay = true;//custom flag 942 return audio; 943 }, 944 945 _stopAllEffects: function () { 946 var self = this, currEffect = self._currEffect, audioPool = self._audioPool, sglCache = self._effectCache4Single, 947 waitings = self._waitingEffIds, pauseds = self._pausedEffIds; 948 if (!currEffect && waitings.length == 0 && pauseds.length == 0) 949 return; 950 for (var key in sglCache) { 951 var eff = sglCache[key]; 952 if (eff.readyState > 2 && eff.duration && eff.duration != Infinity) 953 eff.currentTime = eff.duration; 954 } 955 waitings.length = 0; 956 pauseds.length = 0; 957 for (var key in audioPool) {//reset audios in pool to be ended 958 var list = audioPool[key]; 959 for (var i = 0, li = list.length; i < li; i++) { 960 var eff = list[i]; 961 eff.loop = false; 962 if (eff.readyState > 2 && eff.duration && eff.duration != Infinity) 963 eff.currentTime = eff.duration; 964 } 965 } 966 if (currEffect) self._stopAudio(currEffect); 967 }, 968 969 _effectPauseCb: function () { 970 var self = this; 971 if (self._isHiddenMode) return;//in this mode, return 972 var currEffect = self._getWaitingEffToPlay();//get eff to play 973 if (currEffect) { 974 if (currEffect._isToPlay) { 975 delete currEffect._isToPlay; 976 currEffect.play(); 977 } 978 else self._resumeAudio(currEffect); 979 } else if (self._needToResumeMusic) { 980 var currMusic = self._currMusic; 981 if (currMusic.readyState > 2 && currMusic.duration && currMusic.duration != Infinity) {//calculate current time 982 var temp = currMusic.currentTime + self._expendTime4Music; 983 temp = temp - currMusic.duration * ((temp / currMusic.duration) | 0); 984 currMusic.currentTime = temp; 985 } 986 self._expendTime4Music = 0; 987 self._resumeAudio(currMusic); 988 self._musicPlayState = 2; 989 self._needToResumeMusic = false; 990 } 991 }, 992 993 _getWaitingEffToPlay: function () { 994 var self = this, waitings = self._waitingEffIds, effects = self._effects, 995 currEffect = self._currEffect; 996 997 var expendTime = currEffect ? currEffect.currentTime - (currEffect.startTime || 0) : 0; 998 self._expendTime4Music += expendTime; 999 1000 while (true) {//get a audio to play 1001 if (waitings.length == 0) 1002 break; 1003 var effId = waitings.pop(); 1004 var eff = effects[effId]; 1005 if (!eff) 1006 continue; 1007 if (eff._isToPlay || eff.loop || (eff.duration && eff.currentTime + expendTime < eff.duration)) { 1008 self._currEffectId = effId; 1009 self._currEffect = eff; 1010 if (!eff._isToPlay && eff.readyState > 2 && eff.duration && eff.duration != Infinity) { 1011 var temp = eff.currentTime + expendTime; 1012 temp = temp - eff.duration * ((temp / eff.duration) | 0); 1013 eff.currentTime = temp; 1014 } 1015 eff._isToPlay = false; 1016 return eff; 1017 } else { 1018 if (eff.readyState > 2 && eff.duration && eff.duration != Infinity) 1019 eff.currentTime = eff.duration; 1020 } 1021 } 1022 self._currEffectId = null; 1023 self._currEffect = null; 1024 return null; 1025 }, 1026 1027 _pausePlaying: function () {//in this function, do not change any status of audios 1028 var self = this, currEffect = self._currEffect; 1029 self._isHiddenMode = true; 1030 var audio = self._musicPlayState == 2 ? self._currMusic : currEffect; 1031 if (audio) { 1032 self._playings.push(audio); 1033 audio.pause(); 1034 } 1035 1036 }, 1037 _resumePlaying: function () {//in this function, do not change any status of audios 1038 var self = this, playings = self._playings; 1039 self._isHiddenMode = false; 1040 if (playings.length > 0) { 1041 self._resumeAudio(playings[0]); 1042 playings.length = 0; 1043 } 1044 } 1045 1046 }); 1047 } 1048 1049 cc._audioLoader = { 1050 _supportedAudioTypes: null, 1051 1052 // Get audio default path. 1053 getBasePath: function () { 1054 return cc.loader.audioPath; 1055 }, 1056 1057 // pre-load the audio. <br/> 1058 // 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/> 1059 // 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. 1060 _load: function (realUrl, url, res, count, tryArr, audio, cb) { 1061 var self = this, locLoader = cc.loader, path = cc.path; 1062 var types = this._supportedAudioTypes; 1063 var extname = ""; 1064 if (types.length == 0) 1065 return cb("can not support audio!"); 1066 if (count == -1) { 1067 extname = (path.extname(realUrl) || "").toLowerCase(); 1068 if (!self.audioTypeSupported(extname)) { 1069 extname = types[0]; 1070 count = 0; 1071 } 1072 } else if (count < types.length) { 1073 extname = types[count]; 1074 } else { 1075 return cb("can not found the resource of audio! Last match url is : " + realUrl); 1076 } 1077 if (tryArr.indexOf(extname) >= 0) 1078 return self._load(realUrl, url, res, count + 1, tryArr, audio, cb); 1079 realUrl = path.changeExtname(realUrl, extname); 1080 tryArr.push(extname); 1081 var delFlag = (count == types.length -1); 1082 audio = self._loadAudio(realUrl, audio, function (err) { 1083 if (err) 1084 return self._load(realUrl, url, res, count + 1, tryArr, audio, cb);//can not found 1085 cb(null, audio); 1086 }, delFlag); 1087 locLoader.cache[url] = audio; 1088 }, 1089 1090 //Check whether to support this type of file 1091 audioTypeSupported: function (type) { 1092 if (!type) return false; 1093 return this._supportedAudioTypes.indexOf(type.toLowerCase()) >= 0; 1094 }, 1095 1096 _loadAudio: function (url, audio, cb, delFlag) { 1097 var _Audio; 1098 if (typeof(window["cc"]) != "object" && cc.sys.browserType == "firefox") 1099 _Audio = Audio; //The WebAudio of FireFox doesn't work after google closure compiler compiled with advanced mode 1100 else 1101 _Audio = (location.origin == "file://") ? Audio : (cc.WebAudio || Audio); 1102 if (arguments.length == 2) { 1103 cb = audio; 1104 audio = new _Audio(); 1105 } else if ((arguments.length > 3 ) && !audio) { 1106 audio = new _Audio(); 1107 } 1108 audio.src = url; 1109 audio.preload = "auto"; 1110 1111 var ua = navigator.userAgent; 1112 if (/Mobile/.test(ua) && (/iPhone OS/.test(ua) || /iPad/.test(ua) || /Firefox/.test(ua)) || /MSIE/.test(ua)) { 1113 audio.load(); 1114 cb(null, audio); 1115 } else { 1116 var canplaythrough = "canplaythrough", error = "error"; 1117 cc._addEventListener(audio, canplaythrough, function () { 1118 cb(null, audio); 1119 this.removeEventListener(canplaythrough, arguments.callee, false); 1120 this.removeEventListener(error, arguments.callee, false); 1121 }, false); 1122 cc._addEventListener(audio, error, function () { 1123 cb("load " + url + " failed"); 1124 if(delFlag){ 1125 this.removeEventListener(canplaythrough, arguments.callee, false); 1126 this.removeEventListener(error, arguments.callee, false); 1127 } 1128 }, false); 1129 audio.load(); 1130 } 1131 return audio; 1132 }, 1133 1134 // Load this audio. 1135 load: function (realUrl, url, res, cb) { 1136 var tryArr = []; 1137 this._load(realUrl, url, res, -1, tryArr, null, cb); 1138 } 1139 }; 1140 1141 cc._audioLoader._supportedAudioTypes = function () { 1142 var au = cc.newElement('audio'), arr = []; 1143 if (au.canPlayType) { 1144 // <audio> tag is supported, go on 1145 var _check = function (typeStr) { 1146 var result = au.canPlayType(typeStr); 1147 return result != "no" && result != ""; 1148 }; 1149 if (_check('audio/ogg; codecs="vorbis"')) arr.push(".ogg"); 1150 if (_check("audio/mpeg")) arr.push(".mp3"); 1151 if (_check('audio/wav; codecs="1"')) arr.push(".wav"); 1152 if (_check("audio/mp4")) arr.push(".mp4"); 1153 if (_check("audio/x-m4a") || _check("audio/aac")) arr.push(".m4a"); 1154 } 1155 return arr; 1156 }(); 1157 1158 cc.loader.register(["mp3", "ogg", "wav", "mp4", "m4a"], cc._audioLoader); 1159 1160 // Initialize Audio engine singleton 1161 cc.audioEngine = cc.AudioEngineForSingle ? new cc.AudioEngineForSingle() : new cc.AudioEngine(); 1162 cc.eventManager.addCustomListener(cc.game.EVENT_HIDE, function () { 1163 cc.audioEngine._pausePlaying(); 1164 }); 1165 cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () { 1166 cc.audioEngine._resumePlaying(); 1167 }); 1168