buzz.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. // ----------------------------------------------------------------------------
  2. // Buzz, a Javascript HTML5 Audio library
  3. // v 1.0.4 beta
  4. // Licensed under the MIT license.
  5. // http://buzz.jaysalvat.com/
  6. // ----------------------------------------------------------------------------
  7. // Copyright (C) 2011 Jay Salvat
  8. // http://jaysalvat.com/
  9. // ----------------------------------------------------------------------------
  10. // Permission is hereby granted, free of charge, to any person obtaining a copy
  11. // of this software and associated documentation files ( the "Software" ), to deal
  12. // in the Software without restriction, including without limitation the rights
  13. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. // copies of the Software, and to permit persons to whom the Software is
  15. // furnished to do so, subject to the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be included in
  18. // all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  26. // THE SOFTWARE.
  27. // ----------------------------------------------------------------------------
  28. var buzz = {
  29. defaults: {
  30. autoplay: false,
  31. duration: 5000,
  32. formats: [],
  33. loop: false,
  34. placeholder: '--',
  35. preload: 'metadata',
  36. volume: 80
  37. },
  38. types: {
  39. 'mp3': 'audio/mpeg',
  40. 'ogg': 'audio/ogg',
  41. 'wav': 'audio/wav',
  42. 'aac': 'audio/aac',
  43. 'm4a': 'audio/x-m4a'
  44. },
  45. sounds: [],
  46. el: document.createElement( 'audio' ),
  47. sound: function( src, options ) {
  48. var options = options || {},
  49. pid = 0,
  50. events = [],
  51. eventsOnce = {},
  52. supported = buzz.isSupported();
  53. // publics
  54. this.load = function() {
  55. if ( !supported ) return this;
  56. this.sound.load();
  57. return this;
  58. }
  59. this.play = function() {
  60. if ( !supported ) return this;
  61. this.sound.play();
  62. return this;
  63. }
  64. this.togglePlay = function() {
  65. if ( !supported ) return this;
  66. if ( this.sound.paused ) {
  67. this.sound.play();
  68. } else {
  69. this.sound.pause();
  70. }
  71. return this;
  72. }
  73. this.pause = function() {
  74. if ( !supported ) return this;
  75. this.sound.pause();
  76. return this;
  77. }
  78. this.isPaused = function() {
  79. if ( !supported ) return null;
  80. return this.sound.paused;
  81. }
  82. this.stop = function() {
  83. if ( !supported ) return this;
  84. this.setTime( this.getDuration() );
  85. this.sound.pause();
  86. return this;
  87. }
  88. this.isEnded = function() {
  89. if ( !supported ) return null;
  90. return this.sound.ended;
  91. }
  92. this.loop = function() {
  93. if ( !supported ) return this;
  94. this.sound.loop = 'loop';
  95. this.bind( 'ended.buzzloop', function() {
  96. this.currentTime = 0;
  97. this.play();
  98. });
  99. return this;
  100. }
  101. this.unloop = function() {
  102. if ( !supported ) return this;
  103. this.sound.removeAttribute( 'loop' );
  104. this.unbind( 'ended.buzzloop' );
  105. return this;
  106. }
  107. this.mute = function() {
  108. if ( !supported ) return this;
  109. this.sound.muted = true;
  110. return this;
  111. }
  112. this.unmute = function() {
  113. if ( !supported ) return this;
  114. this.sound.muted = false;
  115. return this;
  116. }
  117. this.toggleMute = function() {
  118. if ( !supported ) return this;
  119. this.sound.muted = !this.sound.muted;
  120. return this;
  121. }
  122. this.isMuted = function() {
  123. if ( !supported ) return null;
  124. return this.sound.muted;
  125. }
  126. this.setVolume = function( volume ) {
  127. if ( !supported ) return this;
  128. if ( volume < 0 ) volume = 0;
  129. if ( volume > 100 ) volume = 100;
  130. this.volume = volume;
  131. this.sound.volume = volume / 100;
  132. return this;
  133. },
  134. this.getVolume = function() {
  135. if ( !supported ) return this;
  136. return this.volume;
  137. }
  138. this.increaseVolume = function( value ) {
  139. return this.setVolume( this.volume + ( value || 1 ) );
  140. }
  141. this.decreaseVolume = function( value ) {
  142. return this.setVolume( this.volume - ( value || 1 ) );
  143. }
  144. this.setTime = function( time ) {
  145. if ( !supported ) return this;
  146. this.whenReady( function() {
  147. this.sound.currentTime = time;
  148. });
  149. return this;
  150. }
  151. this.getTime = function() {
  152. if ( !supported ) return null;
  153. var time = Math.round( this.sound.currentTime * 100 ) / 100;
  154. return isNaN( time ) ? buzz.defaults.placeholder : time;
  155. }
  156. this.setPercent = function( percent ) {
  157. if ( !supported ) return this;
  158. return this.setTime( buzz.fromPercent( percent, this.sound.duration ) );
  159. }
  160. this.getPercent = function() {
  161. if ( !supported ) return null;
  162. var percent = Math.round( buzz.toPercent( this.sound.currentTime, this.sound.duration ) );
  163. return isNaN( percent ) ? buzz.defaults.placeholder : percent;
  164. }
  165. this.setSpeed = function( duration ) {
  166. if ( !supported ) return this;
  167. this.sound.playbackRate = duration;
  168. }
  169. this.getSpeed = function() {
  170. if ( !supported ) return null;
  171. return this.sound.playbackRate;
  172. }
  173. this.getDuration = function() {
  174. if ( !supported ) return null;
  175. var duration = Math.round( this.sound.duration * 100 ) / 100;
  176. return isNaN( duration ) ? buzz.defaults.placeholder : duration;
  177. }
  178. this.getPlayed = function() {
  179. if ( !supported ) return null;
  180. return timerangeToArray( this.sound.played );
  181. }
  182. this.getBuffered = function() {
  183. if ( !supported ) return null;
  184. return timerangeToArray( this.sound.buffered );
  185. }
  186. this.getSeekable = function() {
  187. if ( !supported ) return null;
  188. return timerangeToArray( this.sound.seekable );
  189. }
  190. this.getErrorCode = function() {
  191. if ( supported && this.sound.error ) {
  192. return this.sound.error.code;
  193. }
  194. return 0;
  195. }
  196. this.getErrorMessage = function() {
  197. if ( !supported ) return null;
  198. switch( this.getErrorCode() ) {
  199. case 1:
  200. return 'MEDIA_ERR_ABORTED';
  201. case 2:
  202. return 'MEDIA_ERR_NETWORK';
  203. case 3:
  204. return 'MEDIA_ERR_DECODE';
  205. case 4:
  206. return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
  207. default:
  208. return null;
  209. }
  210. }
  211. this.getStateCode = function() {
  212. if ( !supported ) return null;
  213. return this.sound.readyState;
  214. }
  215. this.getStateMessage = function() {
  216. if ( !supported ) return null;
  217. switch( this.getStateCode() ) {
  218. case 0:
  219. return 'HAVE_NOTHING';
  220. case 1:
  221. return 'HAVE_METADATA';
  222. case 2:
  223. return 'HAVE_CURRENT_DATA';
  224. case 3:
  225. return 'HAVE_FUTURE_DATA';
  226. case 4:
  227. return 'HAVE_ENOUGH_DATA';
  228. default:
  229. return null;
  230. }
  231. }
  232. this.getNetworkStateCode = function() {
  233. if ( !supported ) return null;
  234. return this.sound.networkState;
  235. }
  236. this.getNetworkStateMessage = function() {
  237. if ( !supported ) return null;
  238. switch( this.getNetworkStateCode() ) {
  239. case 0:
  240. return 'NETWORK_EMPTY';
  241. case 1:
  242. return 'NETWORK_IDLE';
  243. case 2:
  244. return 'NETWORK_LOADING';
  245. case 3:
  246. return 'NETWORK_NO_SOURCE';
  247. default:
  248. return null;
  249. }
  250. }
  251. this.set = function( key, value ) {
  252. if ( !supported ) return this;
  253. this.sound[ key ] = value;
  254. return this;
  255. }
  256. this.get = function( key ) {
  257. if ( !supported ) return null;
  258. return key ? this.sound[ key ] : this.sound;
  259. }
  260. this.bind = function( types, func ) {
  261. if ( !supported ) return this;
  262. var that = this,
  263. types = types.split( ' ' ),
  264. efunc = function( e ) { func.call( that, e ) };
  265. for( var t = 0; t < types.length; t++ ) {
  266. var type = types[ t ],
  267. idx = type;
  268. type = idx.split( '.' )[ 0 ];
  269. events.push( { idx: idx, func: efunc } );
  270. this.sound.addEventListener( type, efunc, true );
  271. }
  272. return this;
  273. }
  274. this.unbind = function( types ) {
  275. if ( !supported ) return this;
  276. var types = types.split( ' ' );
  277. for( var t = 0; t < types.length; t++ ) {
  278. var idx = types[ t ];
  279. type = idx.split( '.' )[ 0 ];
  280. for( var i = 0; i < events.length; i++ ) {
  281. var namespace = events[ i ].idx.split( '.' );
  282. if ( events[ i ].idx == idx || ( namespace[ 1 ] && namespace[ 1 ] == idx.replace( '.', '' ) ) ) {
  283. this.sound.removeEventListener( type, events[ i ].func, true );
  284. delete events[ i ];
  285. }
  286. }
  287. }
  288. return this;
  289. }
  290. this.bindOnce = function( type, func ) {
  291. if ( !supported ) return this;
  292. var that = this;
  293. eventsOnce[ pid++ ] = false;
  294. this.bind( pid + type, function() {
  295. if ( !eventsOnce[ pid ] ) {
  296. eventsOnce[ pid ] = true;
  297. func.call( that );
  298. }
  299. that.unbind( pid + type );
  300. });
  301. }
  302. this.trigger = function( types ) {
  303. if ( !supported ) return this;
  304. var types = types.split( ' ' );
  305. for( var t = 0; t < types.length; t++ ) {
  306. var idx = types[ t ];
  307. for( var i = 0; i < events.length; i++ ) {
  308. var eventType = events[ i ].idx.split( '.' );
  309. if ( events[ i ].idx == idx || ( eventType[ 0 ] && eventType[ 0 ] == idx.replace( '.', '' ) ) ) {
  310. var evt = document.createEvent('HTMLEvents');
  311. evt.initEvent( eventType[ 0 ], false, true );
  312. this.sound.dispatchEvent( evt );
  313. }
  314. }
  315. }
  316. return this;
  317. }
  318. this.fadeTo = function( to, duration, callback ) {
  319. if ( !supported ) return this;
  320. if ( duration instanceof Function ) {
  321. callback = duration;
  322. duration = buzz.defaults.duration;
  323. } else {
  324. duration = duration || buzz.defaults.duration;
  325. }
  326. var from = this.volume,
  327. delay = duration / Math.abs( from - to ),
  328. that = this;
  329. this.play();
  330. function doFade() {
  331. setTimeout( function() {
  332. if ( from < to && that.volume < to ) {
  333. that.setVolume( that.volume += 1 );
  334. doFade();
  335. } else if ( from > to && that.volume > to ) {
  336. that.setVolume( that.volume -= 1 );
  337. doFade();
  338. } else if ( callback instanceof Function ) {
  339. callback.apply( that );
  340. }
  341. }, delay );
  342. }
  343. this.whenReady( function() {
  344. doFade();
  345. });
  346. return this;
  347. }
  348. this.fadeIn = function( duration, callback ) {
  349. if ( !supported ) return this;
  350. return this.setVolume(0).fadeTo( 100, duration, callback );
  351. }
  352. this.fadeOut = function( duration, callback ) {
  353. if ( !supported ) return this;
  354. return this.fadeTo( 0, duration, callback );
  355. }
  356. this.fadeWith = function( sound, duration ) {
  357. if ( !supported ) return this;
  358. this.fadeOut( duration, function() {
  359. this.stop();
  360. });
  361. sound.play().fadeIn( duration );
  362. return this;
  363. }
  364. this.whenReady = function( func ) {
  365. if ( !supported ) return null;
  366. var that = this;
  367. if ( this.sound.readyState == 0 ) {
  368. this.bind( 'canplay.buzzwhenready', function() {
  369. func.call( that );
  370. });
  371. } else {
  372. func.call( that );
  373. }
  374. }
  375. // privates
  376. function timerangeToArray( timeRange ) {
  377. var array = [],
  378. length = timeRange.length - 1;
  379. for( var i = 0; i <= length; i++ ) {
  380. array.push({
  381. start: timeRange.start( length ),
  382. end: timeRange.end( length )
  383. });
  384. }
  385. return array;
  386. }
  387. function getExt( filename ) {
  388. return filename.split('.').pop();
  389. }
  390. function addSource( sound, src ) {
  391. var source = document.createElement( 'source' );
  392. source.src = src;
  393. if ( buzz.types[ getExt( src ) ] ) {
  394. source.type = buzz.types[ getExt( src ) ];
  395. }
  396. sound.appendChild( source );
  397. }
  398. // init
  399. if ( supported ) {
  400. for( i in buzz.defaults ) {
  401. options[ i ] = options[ i ] || buzz.defaults[ i ];
  402. }
  403. this.sound = document.createElement( 'audio' );
  404. if ( src instanceof Array ) {
  405. for( var i in src ) {
  406. addSource( this.sound, src[ i ] );
  407. }
  408. } else if ( options.formats.length ) {
  409. for( var i in options.formats ) {
  410. addSource( this.sound, src + '.' + options.formats[ i ] );
  411. }
  412. } else {
  413. addSource( this.sound, src );
  414. }
  415. if ( options.loop ) {
  416. this.loop();
  417. }
  418. if ( options.autoplay ) {
  419. this.sound.autoplay = 'autoplay';
  420. }
  421. if ( options.preload === true ) {
  422. this.sound.preload = 'auto';
  423. } else if ( options.preload === false ) {
  424. this.sound.preload = 'none';
  425. } else {
  426. this.sound.preload = options.preload;
  427. }
  428. this.setVolume( options.volume );
  429. buzz.sounds.push( this );
  430. }
  431. },
  432. group: function( sounds ) {
  433. var sounds = argsToArray( sounds, arguments );
  434. // publics
  435. this.getSounds = function() {
  436. return sounds;
  437. }
  438. this.add = function( soundArray ) {
  439. var soundArray = argsToArray( soundArray, arguments );
  440. for( var a = 0; a < soundArray.length; a++ ) {
  441. for( var i = 0; i < sounds.length; i++ ) {
  442. sounds.push( soundArray[ a ] );
  443. }
  444. }
  445. }
  446. this.remove = function( soundArray ) {
  447. var soundArray = argsToArray( soundArray, arguments );
  448. for( var a = 0; a < soundArray.length; a++ ) {
  449. for( var i = 0; i < sounds.length; i++ ) {
  450. if ( sounds[ i ] == soundArray[ a ] ) {
  451. delete sounds[ i ];
  452. break;
  453. }
  454. }
  455. }
  456. }
  457. this.load = function() {
  458. fn( 'load' );
  459. return this;
  460. }
  461. this.play = function() {
  462. fn( 'play' );
  463. return this;
  464. }
  465. this.togglePlay = function( ) {
  466. fn( 'togglePlay' );
  467. return this;
  468. }
  469. this.pause = function( time ) {
  470. fn( 'pause', time );
  471. return this;
  472. }
  473. this.stop = function() {
  474. fn( 'stop' );
  475. return this;
  476. }
  477. this.mute = function() {
  478. fn( 'mute' );
  479. return this;
  480. }
  481. this.unmute = function() {
  482. fn( 'unmute' );
  483. return this;
  484. }
  485. this.toggleMute = function() {
  486. fn( 'toggleMute' );
  487. return this;
  488. }
  489. this.setVolume = function( volume ) {
  490. fn( 'setVolume', volume );
  491. return this;
  492. }
  493. this.increaseVolume = function( value ) {
  494. fn( 'increaseVolume', value );
  495. return this;
  496. }
  497. this.decreaseVolume = function( value ) {
  498. fn( 'decreaseVolume', value );
  499. return this;
  500. }
  501. this.loop = function() {
  502. fn( 'loop' );
  503. return this;
  504. }
  505. this.unloop = function() {
  506. fn( 'unloop' );
  507. return this;
  508. }
  509. this.setTime = function( time ) {
  510. fn( 'setTime', time );
  511. return this;
  512. }
  513. this.setduration = function( duration ) {
  514. fn( 'setduration', duration );
  515. return this;
  516. }
  517. this.set = function( key, value ) {
  518. fn( 'set', key, value );
  519. return this;
  520. }
  521. this.bind = function( type, func ) {
  522. fn( 'bind', type, func );
  523. return this;
  524. }
  525. this.unbind = function( type ) {
  526. fn( 'unbind', type );
  527. return this;
  528. }
  529. this.bindOnce = function( type, func ) {
  530. fn( 'bindOnce', type, func );
  531. return this;
  532. }
  533. this.trigger = function( type ) {
  534. fn( 'trigger', type );
  535. return this;
  536. }
  537. this.fade = function( from, to, duration, callback ) {
  538. fn( 'fade', from, to, duration, callback );
  539. return this;
  540. }
  541. this.fadeIn = function( duration, callback ) {
  542. fn( 'fadeIn', duration, callback );
  543. return this;
  544. }
  545. this.fadeOut = function( duration, callback ) {
  546. fn( 'fadeOut', duration, callback );
  547. return this;
  548. }
  549. // privates
  550. function fn() {
  551. var args = argsToArray( null, arguments ),
  552. func = args.shift();
  553. for( var i = 0; i < sounds.length; i++ ) {
  554. sounds[ i ][ func ].apply( sounds[ i ], args );
  555. }
  556. }
  557. function argsToArray( array, args ) {
  558. return ( array instanceof Array ) ? array : Array.prototype.slice.call( args );
  559. }
  560. },
  561. all: function() {
  562. return new buzz.group( buzz.sounds );
  563. },
  564. isSupported: function() {
  565. return !!this.el.canPlayType;
  566. },
  567. isOGGSupported: function() {
  568. return !!this.el.canPlayType && this.el.canPlayType( 'audio/ogg; codecs="vorbis"' );
  569. },
  570. isWAVSupported: function() {
  571. return !!this.el.canPlayType && this.el.canPlayType( 'audio/wav; codecs="1"' );
  572. },
  573. isMP3Supported: function() {
  574. return !!this.el.canPlayType && this.el.canPlayType( 'audio/mpeg;' );
  575. },
  576. isAACSupported: function() {
  577. return !!this.el.canPlayType && ( this.el.canPlayType( 'audio/x-m4a;' ) || this.el.canPlayType( 'audio/aac;' ) );
  578. },
  579. toTimer: function( time, withHours ) {
  580. h = Math.floor( time / 3600 );
  581. h = isNaN( h ) ? '--' : ( h >= 10 ) ? h : '0' + h;
  582. m = withHours ? Math.floor( time / 60 % 60 ) : Math.floor( time / 60 );
  583. m = isNaN( m ) ? '--' : ( m >= 10 ) ? m : '0' + m;
  584. s = Math.floor( time % 60 );
  585. s = isNaN( s ) ? '--' : ( s >= 10 ) ? s : '0' + s;
  586. return withHours ? h + ':' + m + ':' + s : m + ':' + s;
  587. },
  588. fromTimer: function( time ) {
  589. var splits = time.toString().split( ':' );
  590. if ( splits && splits.length == 3 ) {
  591. time = ( parseInt( splits[ 0 ] ) * 3600 ) + ( parseInt(splits[ 1 ] ) * 60 ) + parseInt( splits[ 2 ] );
  592. }
  593. if ( splits && splits.length == 2 ) {
  594. time = ( parseInt( splits[ 0 ] ) * 60 ) + parseInt( splits[ 1 ] );
  595. }
  596. return time;
  597. },
  598. toPercent: function( value, total, decimal ) {
  599. var r = Math.pow( 10, decimal || 0 );
  600. return Math.round( ( ( value * 100 ) / total ) * r ) / r;
  601. },
  602. fromPercent: function( percent, total, decimal ) {
  603. var r = Math.pow( 10, decimal || 0 );
  604. return Math.round( ( ( total / 100 ) * percent ) * r ) / r;
  605. }
  606. }