howler.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. (function() {
  2. // setup
  3. var cache = {};
  4. // setup the audio context
  5. var ctx = null,
  6. usingWebAudio = true,
  7. noAudio = false;
  8. if (typeof AudioContext !== 'undefined') {
  9. ctx = new AudioContext();
  10. } else if (typeof webkitAudioContext !== 'undefined') {
  11. ctx = new webkitAudioContext();
  12. } else if (typeof Audio !== 'undefined') {
  13. usingWebAudio = false;
  14. try {
  15. new Audio();
  16. } catch(e) {
  17. noAudio = true;
  18. }
  19. } else {
  20. usingWebAudio = false;
  21. noAudio = true;
  22. }
  23. // create a master gain node
  24. if (usingWebAudio) {
  25. var masterGain = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain();
  26. masterGain.gain.value = 1;
  27. masterGain.connect(ctx.destination);
  28. }
  29. // create global controller
  30. var HowlerGlobal = function() {
  31. this._volume = 1;
  32. this._muted = false;
  33. this.usingWebAudio = usingWebAudio;
  34. this._howls = [];
  35. };
  36. HowlerGlobal.prototype = {
  37. volume: function(vol) {
  38. var self = this;
  39. // make sure volume is a number
  40. vol = parseFloat(vol);
  41. if (vol && vol >= 0 && vol <= 1) {
  42. self._volume = vol;
  43. if (usingWebAudio) {
  44. masterGain.gain.value = vol;
  45. }
  46. // loop through cache and change volume of all nodes that are using HTML5 Audio
  47. for (var key in self._howls) {
  48. if (self._howls.hasOwnProperty(key) && self._howls[key]._webAudio === false) {
  49. // loop through the audio nodes
  50. for (var i=0; i<self._howls[key]._audioNode.length; i++) {
  51. self._howls[key]._audioNode[i].volume = self._howls[key]._volume * self._volume;
  52. }
  53. }
  54. }
  55. return self;
  56. }
  57. // return the current global volume
  58. return (usingWebAudio) ? masterGain.gain.value : self._volume;
  59. },
  60. mute: function() {
  61. this._setMuted(true);
  62. return this;
  63. },
  64. unmute: function() {
  65. this._setMuted(false);
  66. return this;
  67. },
  68. _setMuted: function(muted) {
  69. var self = this;
  70. self._muted = muted;
  71. if (usingWebAudio) {
  72. masterGain.gain.value = muted ? 0 : self._volume;
  73. }
  74. for (var key in self._howls) {
  75. if (self._howls.hasOwnProperty(key) && self._howls[key]._webAudio === false) {
  76. // loop through the audio nodes
  77. for (var i=0; i<self._howls[key]._audioNode.length; i++) {
  78. self._howls[key]._audioNode[i].muted = muted;
  79. }
  80. }
  81. }
  82. }
  83. };
  84. var Howler = new HowlerGlobal();
  85. // check for browser codec support
  86. var audioTest = null;
  87. if (!noAudio) {
  88. audioTest = new Audio();
  89. var codecs = {
  90. mp3: !!audioTest.canPlayType('audio/mpeg;').replace(/^no$/,''),
  91. opus: !!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,''),
  92. ogg: !!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''),
  93. wav: !!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/,''),
  94. m4a: !!(audioTest.canPlayType('audio/x-m4a;') || audioTest.canPlayType('audio/aac;')).replace(/^no$/,''),
  95. webm: !!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,'')
  96. };
  97. }
  98. // setup the audio object
  99. var Howl = function(o) {
  100. var self = this;
  101. // setup the defaults
  102. self._autoplay = o.autoplay || false;
  103. self._buffer = o.buffer || false;
  104. self._duration = o.duration || 0;
  105. self._format = o.format || null;
  106. self._loop = o.loop || false;
  107. self._loaded = false;
  108. self._sprite = o.sprite || {};
  109. self._src = o.src || '';
  110. self._pos3d = o.pos3d || [0, 0, -0.5];
  111. self._volume = o.volume || 1;
  112. self._urls = o.urls || [];
  113. self._rate = o.rate || 1;
  114. // setup event functions
  115. self._onload = [o.onload || function() {}];
  116. self._onloaderror = [o.onloaderror || function() {}];
  117. self._onend = [o.onend || function() {}];
  118. self._onpause = [o.onpause || function() {}];
  119. self._onplay = [o.onplay || function() {}];
  120. self._onendTimer = [];
  121. // Web Audio or HTML5 Audio?
  122. self._webAudio = usingWebAudio && !self._buffer;
  123. // check if we need to fall back to HTML5 Audio
  124. self._audioNode = [];
  125. if (self._webAudio) {
  126. self._setupAudioNode();
  127. }
  128. // add this to an array of Howl's to allow global control
  129. Howler._howls.push(self);
  130. // load the track
  131. self.load();
  132. };
  133. // setup all of the methods
  134. Howl.prototype = {
  135. load: function() {
  136. var self = this,
  137. url = null;
  138. // if no audio is available, quit immediately
  139. if (noAudio) {
  140. self.on('loaderror');
  141. return;
  142. }
  143. var canPlay = {
  144. mp3: codecs.mp3,
  145. opus: codecs.opus,
  146. ogg: codecs.ogg,
  147. wav: codecs.wav,
  148. m4a: codecs.m4a,
  149. weba: codecs.webm
  150. };
  151. // loop through source URLs and pick the first one that is compatible
  152. for (var i=0; i<self._urls.length; i++) {
  153. var ext;
  154. if (self._format) {
  155. // use specified audio format if available
  156. ext = self._format;
  157. } else {
  158. // figure out the filetype (whether an extension or base64 data)
  159. ext = self._urls[i].toLowerCase().match(/.+\.([^?]+)(\?|$)/);
  160. ext = (ext && ext.length >= 2) ? ext[1] : self._urls[i].toLowerCase().match(/data\:audio\/([^?]+);/)[1];
  161. }
  162. if (canPlay[ext]) {
  163. url = self._urls[i];
  164. break;
  165. }
  166. }
  167. if (!url) {
  168. self.on('loaderror');
  169. return;
  170. }
  171. self._src = url;
  172. if (self._webAudio) {
  173. loadBuffer(self, url);
  174. } else {
  175. var newNode = new Audio();
  176. self._audioNode.push(newNode);
  177. // setup the new audio node
  178. newNode.src = url;
  179. newNode._pos = 0;
  180. newNode.preload = 'auto';
  181. newNode.volume = (Howler._muted) ? 0 : self._volume * Howler.volume();
  182. // add this sound to the cache
  183. cache[url] = self;
  184. // setup the event listener to start playing the sound
  185. // as soon as it has buffered enough
  186. var listener = function() {
  187. self._duration = newNode.duration;
  188. // setup a sprite if none is defined
  189. if (Object.getOwnPropertyNames(self._sprite).length === 0) {
  190. self._sprite = {_default: [0, self._duration * 1000]};
  191. }
  192. if (!self._loaded) {
  193. self._loaded = true;
  194. self.on('load');
  195. }
  196. if (self._autoplay) {
  197. self.play();
  198. }
  199. // clear the event listener
  200. newNode.removeEventListener('canplaythrough', listener, false);
  201. };
  202. newNode.addEventListener('canplaythrough', listener, false);
  203. newNode.load();
  204. }
  205. return self;
  206. },
  207. urls: function(urls) {
  208. var self = this;
  209. if (urls) {
  210. self.stop();
  211. self._urls = (typeof urls === 'string') ? [urls] : urls;
  212. self._loaded = false;
  213. self.load();
  214. return self;
  215. } else {
  216. return self._urls;
  217. }
  218. },
  219. play: function(sprite, callback) {
  220. var self = this;
  221. // if no sprite was passed but a callback was, update the variables
  222. if (typeof sprite === 'function') {
  223. callback = sprite;
  224. }
  225. // use the default sprite if none is passed
  226. if (!sprite || typeof sprite === 'function') {
  227. sprite = '_default';
  228. }
  229. // if the sound hasn't been loaded, add it to the event queue
  230. if (!self._loaded) {
  231. self.on('load', function() {
  232. self.play(sprite, callback);
  233. });
  234. return self;
  235. }
  236. // if the sprite doesn't exist, play nothing
  237. if (!self._sprite[sprite]) {
  238. if (typeof callback === 'function') callback();
  239. return self;
  240. }
  241. // get the node to playback
  242. self._inactiveNode(function(node) {
  243. // persist the sprite being played
  244. node._sprite = sprite;
  245. // determine where to start playing from
  246. var pos = (node._pos > 0) ? node._pos : self._sprite[sprite][0] / 1000,
  247. duration = self._sprite[sprite][1] / 1000 - node._pos;
  248. // determine if this sound should be looped
  249. var loop = !!(self._loop || self._sprite[sprite][2]);
  250. // set timer to fire the 'onend' event
  251. var soundId = (typeof callback === 'string') ? callback : Math.round(Date.now() * Math.random()) + '',
  252. timerId;
  253. (function() {
  254. var data = {
  255. id: soundId,
  256. sprite: sprite,
  257. loop: loop
  258. };
  259. timerId = setTimeout(function() {
  260. // if looping, restart the track
  261. if (!self._webAudio && loop) {
  262. self.stop(data.id, data.timer).play(sprite, data.id);
  263. }
  264. // set web audio node to paused at end
  265. if (self._webAudio && !loop) {
  266. self._nodeById(data.id).paused = true;
  267. }
  268. // end the track if it is HTML audio and a sprite
  269. if (!self._webAudio && !loop) {
  270. self.stop(data.id, data.timer);
  271. }
  272. // fire ended event
  273. self.on('end', soundId);
  274. }, duration * 1000);
  275. // store the reference to the timer
  276. self._onendTimer.push(timerId);
  277. // remember which timer to cancel
  278. data.timer = self._onendTimer[self._onendTimer.length - 1];
  279. })();
  280. if (self._webAudio) {
  281. var loopStart = self._sprite[sprite][0] / 1000,
  282. loopEnd = self._sprite[sprite][1] / 1000;
  283. // set the play id to this node and load into context
  284. node.id = soundId;
  285. node.paused = false;
  286. refreshBuffer(self, [loop, loopStart, loopEnd], soundId);
  287. self._playStart = ctx.currentTime;
  288. node.gain.value = self._volume;
  289. if (typeof node.bufferSource.start === 'undefined') {
  290. node.bufferSource.noteGrainOn(0, pos, duration);
  291. } else {
  292. node.bufferSource.start(0, pos, duration);
  293. }
  294. } else {
  295. if (node.readyState === 4) {
  296. node.id = soundId;
  297. node.currentTime = pos;
  298. node.muted = Howler._muted;
  299. node.volume = self._volume * Howler.volume();
  300. setTimeout(function() { node.play(); }, 0);
  301. } else {
  302. self._clearEndTimer(timerId);
  303. (function(){
  304. var sound = self,
  305. playSprite = sprite,
  306. fn = callback,
  307. newNode = node;
  308. var listener = function() {
  309. sound.play(playSprite, fn);
  310. // clear the event listener
  311. newNode.removeEventListener('canplaythrough', listener, false);
  312. };
  313. newNode.addEventListener('canplaythrough', listener, false);
  314. })();
  315. return self;
  316. }
  317. }
  318. // fire the play event and send the soundId back in the callback
  319. self.on('play');
  320. if (typeof callback === 'function') callback(soundId);
  321. return self;
  322. });
  323. return self;
  324. },
  325. pause: function(id, timerId) {
  326. var self = this;
  327. // if the sound hasn't been loaded, add it to the event queue
  328. if (!self._loaded) {
  329. self.on('play', function() {
  330. self.pause(id);
  331. });
  332. return self;
  333. }
  334. // clear 'onend' timer
  335. self._clearEndTimer(timerId || 0);
  336. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  337. if (activeNode) {
  338. activeNode._pos = self.pos(null, id);
  339. if (self._webAudio) {
  340. // make sure the sound has been created
  341. if (!activeNode.bufferSource) {
  342. return self;
  343. }
  344. activeNode.paused = true;
  345. if (typeof activeNode.bufferSource.stop === 'undefined') {
  346. activeNode.bufferSource.noteOff(0);
  347. } else {
  348. activeNode.bufferSource.stop(0);
  349. }
  350. } else {
  351. activeNode.pause();
  352. }
  353. }
  354. self.on('pause');
  355. return self;
  356. },
  357. stop: function(id, timerId) {
  358. var self = this;
  359. // if the sound hasn't been loaded, add it to the event queue
  360. if (!self._loaded) {
  361. self.on('play', function() {
  362. self.stop(id);
  363. });
  364. return self;
  365. }
  366. // clear 'onend' timer
  367. self._clearEndTimer(timerId || 0);
  368. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  369. if (activeNode) {
  370. activeNode._pos = 0;
  371. if (self._webAudio) {
  372. // make sure the sound has been created
  373. if (!activeNode.bufferSource) {
  374. return self;
  375. }
  376. activeNode.paused = true;
  377. if (typeof activeNode.bufferSource.stop === 'undefined') {
  378. activeNode.bufferSource.noteOff(0);
  379. } else {
  380. activeNode.bufferSource.stop(0);
  381. }
  382. } else {
  383. activeNode.pause();
  384. activeNode.currentTime = 0;
  385. }
  386. }
  387. return self;
  388. },
  389. mute: function(id) {
  390. var self = this;
  391. // if the sound hasn't been loaded, add it to the event queue
  392. if (!self._loaded) {
  393. self.on('play', function() {
  394. self.mute(id);
  395. });
  396. return self;
  397. }
  398. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  399. if (activeNode) {
  400. if (self._webAudio) {
  401. activeNode.gain.value = 0;
  402. } else {
  403. activeNode.volume = 0;
  404. }
  405. }
  406. return self;
  407. },
  408. unmute: function(id) {
  409. var self = this;
  410. // if the sound hasn't been loaded, add it to the event queue
  411. if (!self._loaded) {
  412. self.on('play', function() {
  413. self.unmute(id);
  414. });
  415. return self;
  416. }
  417. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  418. if (activeNode) {
  419. if (self._webAudio) {
  420. activeNode.gain.value = self._volume;
  421. } else {
  422. activeNode.volume = self._volume;
  423. }
  424. }
  425. return self;
  426. },
  427. volume: function(vol, id) {
  428. var self = this;
  429. // make sure volume is a number
  430. vol = parseFloat(vol);
  431. if (vol >= 0 && vol <= 1) {
  432. self._volume = vol;
  433. // if the sound hasn't been loaded, add it to the event queue
  434. if (!self._loaded) {
  435. self.on('play', function() {
  436. self.volume(vol, id);
  437. });
  438. return self;
  439. }
  440. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  441. if (activeNode) {
  442. if (self._webAudio) {
  443. activeNode.gain.value = vol;
  444. } else {
  445. activeNode.volume = vol * Howler.volume();
  446. }
  447. }
  448. return self;
  449. } else {
  450. return self._volume;
  451. }
  452. },
  453. loop: function(loop) {
  454. var self = this;
  455. if (typeof loop === 'boolean') {
  456. self._loop = loop;
  457. return self;
  458. } else {
  459. return self._loop;
  460. }
  461. },
  462. sprite: function(sprite) {
  463. var self = this;
  464. if (typeof sprite === 'object') {
  465. self._sprite = sprite;
  466. return self;
  467. } else {
  468. return self._sprite;
  469. }
  470. },
  471. pos: function(pos, id) {
  472. var self = this;
  473. // if the sound hasn't been loaded, add it to the event queue
  474. if (!self._loaded) {
  475. self.on('load', function() {
  476. self.pos(pos);
  477. });
  478. return typeof pos === 'number' ? self : self._pos || 0;
  479. }
  480. // make sure we are dealing with a number for pos
  481. pos = parseFloat(pos);
  482. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  483. if (activeNode) {
  484. if (self._webAudio) {
  485. if (pos >= 0) {
  486. activeNode._pos = pos;
  487. self.pause(id).play(activeNode._sprite, id);
  488. return self;
  489. } else {
  490. return activeNode._pos + (ctx.currentTime - self._playStart);
  491. }
  492. } else {
  493. if (pos >= 0) {
  494. activeNode.currentTime = pos;
  495. return self;
  496. } else {
  497. return activeNode.currentTime;
  498. }
  499. }
  500. } else if (pos >= 0) {
  501. return self;
  502. } else {
  503. // find the first inactive node to return the pos for
  504. for (var i=0; i<self._audioNode.length; i++) {
  505. if (self._audioNode[i].paused && self._audioNode[i].readyState === 4) {
  506. return (self._webAudio) ? self._audioNode[i]._pos : self._audioNode[i].currentTime;
  507. }
  508. }
  509. }
  510. },
  511. pos3d: function(x, y, z, id) {
  512. var self = this;
  513. // set a default for the optional 'y' & 'z'
  514. y = (typeof y === 'undefined' || !y) ? 0 : y;
  515. z = (typeof z === 'undefined' || !z) ? -0.5 : z;
  516. // if the sound hasn't been loaded, add it to the event queue
  517. if (!self._loaded) {
  518. self.on('play', function() {
  519. self.pos3d(x, y, z, id);
  520. });
  521. return self;
  522. }
  523. if (x >= 0 || x < 0) {
  524. if (self._webAudio) {
  525. var activeNode = (id) ? self._nodeById(id) : self._activeNode();
  526. if (activeNode) {
  527. self._pos3d = [x, y, z];
  528. activeNode.panner.setPosition(x, y, z);
  529. }
  530. }
  531. } else {
  532. return self._pos3d;
  533. }
  534. return self;
  535. },
  536. fade: function(from, to, len, callback, id) {
  537. var self = this,
  538. diff = Math.abs(from - to),
  539. dir = from > to ? 'down' : 'up',
  540. steps = diff / 0.01,
  541. stepTime = len / steps;
  542. // if the sound hasn't been loaded, add it to the event queue
  543. if (!self._loaded) {
  544. self.on('load', function() {
  545. self.fade(from, to, len, callback, id);
  546. });
  547. return self;
  548. }
  549. // set the volume to the start position
  550. self.volume(from, id);
  551. for (var i=1; i<=steps; i++) {
  552. (function() {
  553. var change = self._volume + (dir === 'up' ? 0.01 : -0.01) * i,
  554. vol = Math.round(1000 * change) / 1000,
  555. toVol = to;
  556. setTimeout(function() {
  557. self.volume(vol, id);
  558. if (vol === toVol) {
  559. if (callback) callback();
  560. }
  561. }, stepTime * i);
  562. })();
  563. }
  564. },
  565. fadeIn: function(to, len, callback) {
  566. return this.volume(0).play().fade(0, to, len, callback);
  567. },
  568. fadeOut: function(to, len, callback, id) {
  569. var self = this;
  570. return self.fade(self._volume, to, len, function() {
  571. if (callback) callback();
  572. self.pause(id);
  573. // fire ended event
  574. self.on('end');
  575. }, id);
  576. },
  577. _nodeById: function(id) {
  578. var self = this,
  579. node = self._audioNode[0];
  580. // find the node with this ID
  581. for (var i=0; i<self._audioNode.length; i++) {
  582. if (self._audioNode[i].id === id) {
  583. node = self._audioNode[i];
  584. break;
  585. }
  586. }
  587. return node;
  588. },
  589. _activeNode: function() {
  590. var self = this,
  591. node = null;
  592. // find the first playing node
  593. for (var i=0; i<self._audioNode.length; i++) {
  594. if (!self._audioNode[i].paused) {
  595. node = self._audioNode[i];
  596. break;
  597. }
  598. }
  599. // remove excess inactive nodes
  600. self._drainPool();
  601. return node;
  602. },
  603. _inactiveNode: function(callback) {
  604. var self = this,
  605. node = null;
  606. // find first inactive node to recycle
  607. for (var i=0; i<self._audioNode.length; i++) {
  608. if (self._audioNode[i].paused && self._audioNode[i].readyState === 4) {
  609. callback(self._audioNode[i]);
  610. node = true;
  611. break;
  612. }
  613. }
  614. // remove excess inactive nodes
  615. self._drainPool();
  616. if (node) {
  617. return;
  618. }
  619. // create new node if there are no inactives
  620. var newNode;
  621. if (self._webAudio) {
  622. newNode = self._setupAudioNode();
  623. callback(newNode);
  624. } else {
  625. self.load();
  626. newNode = self._audioNode[self._audioNode.length - 1];
  627. newNode.addEventListener('loadedmetadata', function() {
  628. callback(newNode);
  629. });
  630. }
  631. },
  632. /**
  633. * If there are more than 5 inactive audio nodes in the pool, clear out the rest.
  634. */
  635. _drainPool: function() {
  636. var self = this,
  637. inactive = 0,
  638. i;
  639. // count the number of inactive nodes
  640. for (i=0; i<self._audioNode.length; i++) {
  641. if (self._audioNode[i].paused) {
  642. inactive++;
  643. }
  644. }
  645. // remove excess inactive nodes
  646. for (i=self._audioNode.length-1; i>=0; i--) {
  647. if (inactive <= 5) {
  648. break;
  649. }
  650. if (self._audioNode[i].paused) {
  651. // disconnect the audio source if using Web Audio
  652. if (self._webAudio) {
  653. self._audioNode[i].disconnect(0);
  654. }
  655. inactive--;
  656. self._audioNode.splice(i, 1);
  657. }
  658. }
  659. },
  660. _clearEndTimer: function(timerId) {
  661. var self = this,
  662. timer = self._onendTimer.indexOf(timerId);
  663. // make sure the timer gets cleared
  664. timer = timer >= 0 ? timer : 0;
  665. if (self._onendTimer[timer]) {
  666. clearTimeout(self._onendTimer[timer]);
  667. self._onendTimer.splice(timer, 1);
  668. }
  669. },
  670. _setupAudioNode: function() {
  671. var self = this,
  672. node = self._audioNode,
  673. index = self._audioNode.length;
  674. // create gain node
  675. node[index] = (typeof ctx.createGain === 'undefined') ? ctx.createGainNode() : ctx.createGain();
  676. node[index].gain.value = self._volume;
  677. node[index].paused = true;
  678. node[index]._pos = 0;
  679. node[index].readyState = 4;
  680. node[index].connect(masterGain);
  681. // create the panner
  682. node[index].panner = ctx.createPanner();
  683. node[index].panner.setPosition(self._pos3d[0], self._pos3d[1], self._pos3d[2]);
  684. node[index].panner.connect(node[index]);
  685. return node[index];
  686. },
  687. on: function(event, fn) {
  688. var self = this,
  689. events = self['_on' + event];
  690. if (typeof fn === "function") {
  691. events.push(fn);
  692. } else {
  693. for (var i=0; i<events.length; i++) {
  694. if (fn) {
  695. events[i].call(self, fn);
  696. } else {
  697. events[i].call(self);
  698. }
  699. }
  700. }
  701. return self;
  702. },
  703. off: function(event, fn) {
  704. var self = this,
  705. events = self['_on' + event],
  706. fnString = fn.toString();
  707. // loop through functions in the event for comparison
  708. for (var i=0; i<events.length; i++) {
  709. if (fnString === events[i].toString()) {
  710. events.splice(i, 1);
  711. break;
  712. }
  713. }
  714. return self;
  715. },
  716. unload: function() {
  717. var self = this;
  718. // stop playing any active nodes
  719. var nodes = self._audioNode;
  720. for (var i=0; i<self._audioNode.length; i++) {
  721. self.stop(nodes[i].id);
  722. if (!self._webAudio) {
  723. // remove the source if using HTML5 Audio
  724. nodes[i].src = '';
  725. } else {
  726. // disconnect the output from the master gain
  727. nodes[i].disconnect(0);
  728. }
  729. }
  730. // remove the reference in the global Howler object
  731. var index = Howler._howls.indexOf(self);
  732. if (index) {
  733. Howler._howls.splice(index, 1);
  734. }
  735. // delete this sound from the cache
  736. delete cache[self._src];
  737. self = null;
  738. }
  739. };
  740. // only define these functions when using WebAudio
  741. if (usingWebAudio) {
  742. var loadBuffer = function(obj, url) {
  743. // check if the buffer has already been cached
  744. if (url in cache) {
  745. // set the duration from the cache
  746. obj._duration = cache[url].duration;
  747. // load the sound into this object
  748. loadSound(obj);
  749. } else {
  750. // load the buffer from the URL
  751. var xhr = new XMLHttpRequest();
  752. xhr.open('GET', url, true);
  753. xhr.responseType = 'arraybuffer';
  754. xhr.onload = function() {
  755. // decode the buffer into an audio source
  756. ctx.decodeAudioData(xhr.response, function(buffer) {
  757. if (buffer) {
  758. cache[url] = buffer;
  759. loadSound(obj, buffer);
  760. }
  761. });
  762. };
  763. xhr.onerror = function() {
  764. // if there is an error, switch the sound to HTML Audio
  765. if (obj._webAudio) {
  766. obj._buffer = true;
  767. obj._webAudio = false;
  768. obj._audioNode = [];
  769. delete obj._gainNode;
  770. obj.load();
  771. }
  772. };
  773. try {
  774. xhr.send();
  775. } catch (e) {
  776. xhr.onerror();
  777. }
  778. }
  779. };
  780. var loadSound = function(obj, buffer) {
  781. // set the duration
  782. obj._duration = (buffer) ? buffer.duration : obj._duration;
  783. // setup a sprite if none is defined
  784. if (Object.getOwnPropertyNames(obj._sprite).length === 0) {
  785. obj._sprite = {_default: [0, obj._duration * 1000]};
  786. }
  787. // fire the loaded event
  788. if (!obj._loaded) {
  789. obj._loaded = true;
  790. obj.on('load');
  791. }
  792. if (obj._autoplay) {
  793. obj.play();
  794. }
  795. };
  796. var refreshBuffer = function(obj, loop, id) {
  797. // determine which node to connect to
  798. var node = obj._nodeById(id);
  799. // setup the buffer source for playback
  800. node.bufferSource = ctx.createBufferSource();
  801. node.bufferSource.buffer = cache[obj._src];
  802. node.bufferSource.connect(node.panner);
  803. node.bufferSource.loop = loop[0];
  804. if (loop[0]) {
  805. node.bufferSource.loopStart = loop[1];
  806. node.bufferSource.loopEnd = loop[1] + loop[2];
  807. }
  808. node.bufferSource.playbackRate.value = obj._rate;
  809. };
  810. }
  811. /**
  812. * Add support for AMD (Asynchronous Module Definition) libraries such as require.js.
  813. */
  814. if (typeof define === 'function' && define.amd) {
  815. define(function() {
  816. return {
  817. Howler: Howler,
  818. Howl: Howl
  819. };
  820. });
  821. }
  822. // define globally in case AMD is not available or available but not used
  823. window.Howler = Howler;
  824. window.Howl = Howl;
  825. })();