CCAudio.js 39 KB

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