buzz
Version:
Buzz, a Javascript HTML5 Audio library
971 lines (738 loc) • 27.1 kB
JavaScript
// ----------------------------------------------------------------------------
// Buzz, a Javascript HTML5 Audio library
// Licensed under the MIT license.
// http://buzz.jaysalvat.com/
// ----------------------------------------------------------------------------
// Copyright (C) Jay Salvat
// http://jaysalvat.com/
// ----------------------------------------------------------------------------
/* jshint browser: true, node: true */
/* global define */
(function (context, factory) {
"use strict";
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define([], factory);
} else {
context.buzz = factory();
}
})(this, function () {
"use strict";
var AudioContext = window.AudioContext || window.webkitAudioContext;
var buzz = {
defaults: {
autoplay: false,
crossOrigin: null,
duration: 5000,
formats: [],
loop: false,
placeholder: '--',
preload: 'metadata',
volume: 80,
webAudioApi: false,
document: window.document // iframe support
},
types: {
'mp3': 'audio/mpeg',
'ogg': 'audio/ogg',
'wav': 'audio/wav',
'aac': 'audio/aac',
'm4a': 'audio/x-m4a'
},
sounds: [],
el: document.createElement('audio'),
getAudioContext: function() {
if (this.audioCtx === undefined) {
try {
this.audioCtx = AudioContext ? new AudioContext() : null;
} catch (e) {
// There is a limit to how many contexts you can have, so fall back in case of errors constructing it
this.audioCtx = null;
}
}
return this.audioCtx;
},
sound: function (src, options) {
options = options || {};
var doc = options.document || buzz.defaults.document;
var pid = 0,
events = [],
eventsOnce = {},
supported = buzz.isSupported();
// publics
this.load = function () {
if (!supported) {
return this;
}
this.sound.load();
return this;
};
this.play = function () {
if (!supported) {
return this;
}
this.sound.play().catch(function () {});
return this;
};
this.togglePlay = function () {
if (!supported) {
return this;
}
if (this.sound.paused) {
this.sound.play().catch(function () {});
} else {
this.sound.pause();
}
return this;
};
this.pause = function () {
if (!supported) {
return this;
}
this.sound.pause();
return this;
};
this.isPaused = function () {
if (!supported) {
return null;
}
return this.sound.paused;
};
this.stop = function () {
if (!supported ) {
return this;
}
this.sound.pause();
this.setTime(0);
return this;
};
this.isEnded = function () {
if (!supported) {
return null;
}
return this.sound.ended;
};
this.loop = function () {
if (!supported) {
return this;
}
this.sound.loop = 'loop';
this.bind('ended.buzzloop', function () {
this.currentTime = 0;
this.play();
});
return this;
};
this.unloop = function () {
if (!supported) {
return this;
}
this.sound.removeAttribute('loop');
this.unbind('ended.buzzloop');
return this;
};
this.mute = function () {
if (!supported) {
return this;
}
this.sound.muted = true;
return this;
};
this.unmute = function () {
if (!supported) {
return this;
}
this.sound.muted = false;
return this;
};
this.toggleMute = function () {
if (!supported) {
return this;
}
this.sound.muted = !this.sound.muted;
return this;
};
this.isMuted = function () {
if (!supported) {
return null;
}
return this.sound.muted;
};
this.setVolume = function (volume) {
if (!supported) {
return this;
}
if (volume < 0) {
volume = 0;
}
if (volume > 100) {
volume = 100;
}
this.volume = volume;
this.sound.volume = volume / 100;
return this;
};
this.getVolume = function () {
if (!supported) {
return this;
}
return this.volume;
};
this.increaseVolume = function (value) {
return this.setVolume(this.volume + (value || 1));
};
this.decreaseVolume = function (value) {
return this.setVolume(this.volume - (value || 1));
};
this.setTime = function (time) {
if (!supported) {
return this;
}
var set = true;
this.whenReady(function () {
if (set === true) {
set = false;
this.sound.currentTime = time;
}
});
return this;
};
this.getTime = function () {
if (!supported) {
return null;
}
var time = Math.round(this.sound.currentTime * 100) / 100;
return isNaN(time) ? buzz.defaults.placeholder : time;
};
this.setPercent = function (percent) {
if (!supported) {
return this;
}
return this.setTime(buzz.fromPercent(percent, this.sound.duration));
};
this.getPercent = function () {
if (!supported) {
return null;
}
var percent = Math.round(buzz.toPercent(this.sound.currentTime, this.sound.duration));
return isNaN(percent) ? buzz.defaults.placeholder : percent;
};
this.setSpeed = function (duration) {
if (!supported) {
return this;
}
this.sound.playbackRate = duration;
return this;
};
this.getSpeed = function () {
if (!supported) {
return null;
}
return this.sound.playbackRate;
};
this.getDuration = function () {
if (!supported) {
return null;
}
var duration = Math.round(this.sound.duration * 100) / 100;
return isNaN(duration) ? buzz.defaults.placeholder : duration;
};
this.getPlayed = function () {
if (!supported) {
return null;
}
return timerangeToArray(this.sound.played);
};
this.getBuffered = function () {
if (!supported) {
return null;
}
return timerangeToArray(this.sound.buffered);
};
this.getSeekable = function () {
if (!supported) {
return null;
}
return timerangeToArray(this.sound.seekable);
};
this.getErrorCode = function () {
if (supported && this.sound.error) {
return this.sound.error.code;
}
return 0;
};
this.getErrorMessage = function () {
if (!supported) {
return null;
}
switch(this.getErrorCode()) {
case 1:
return 'MEDIA_ERR_ABORTED';
case 2:
return 'MEDIA_ERR_NETWORK';
case 3:
return 'MEDIA_ERR_DECODE';
case 4:
return 'MEDIA_ERR_SRC_NOT_SUPPORTED';
default:
return null;
}
};
this.getStateCode = function () {
if (!supported) {
return null;
}
return this.sound.readyState;
};
this.getStateMessage = function () {
if (!supported) {
return null;
}
switch(this.getStateCode()) {
case 0:
return 'HAVE_NOTHING';
case 1:
return 'HAVE_METADATA';
case 2:
return 'HAVE_CURRENT_DATA';
case 3:
return 'HAVE_FUTURE_DATA';
case 4:
return 'HAVE_ENOUGH_DATA';
default:
return null;
}
};
this.getNetworkStateCode = function () {
if (!supported) {
return null;
}
return this.sound.networkState;
};
this.getNetworkStateMessage = function () {
if (!supported) {
return null;
}
switch(this.getNetworkStateCode()) {
case 0:
return 'NETWORK_EMPTY';
case 1:
return 'NETWORK_IDLE';
case 2:
return 'NETWORK_LOADING';
case 3:
return 'NETWORK_NO_SOURCE';
default:
return null;
}
};
this.set = function (key, value) {
if (!supported) {
return this;
}
this.sound[key] = value;
return this;
};
this.get = function (key) {
if (!supported) {
return null;
}
return key ? this.sound[key] : this.sound;
};
this.bind = function (types, func) {
if (!supported) {
return this;
}
types = types.split(' ');
var self = this,
efunc = function (e) { func.call(self, e); };
for (var t = 0; t < types.length; t++) {
var type = types[t],
idx = type;
type = idx.split('.')[0];
events.push({ idx: idx, func: efunc });
this.sound.addEventListener(type, efunc, true);
}
return this;
};
this.unbind = function (types) {
if (!supported) {
return this;
}
types = types.split(' ');
for (var t = 0; t < types.length; t++) {
var idx = types[t],
type = idx.split('.')[0];
for (var i = 0; i < events.length; i++) {
var namespace = events[i].idx.split('.');
if (events[i].idx === idx || (namespace[1] && namespace[1] === idx.replace('.', ''))) {
this.sound.removeEventListener(type, events[i].func, true);
// remove event
events.splice(i, 1);
}
}
}
return this;
};
this.bindOnce = function (type, func) {
if (!supported) {
return this;
}
var self = this;
eventsOnce[pid++] = false;
this.bind(type + '.' + pid, function () {
if (!eventsOnce[pid]) {
eventsOnce[pid] = true;
func.call(self);
}
self.unbind(type + '.' + pid);
});
return this;
};
this.trigger = function (types, detail) {
if (!supported) {
return this;
}
types = types.split(' ');
for (var t = 0; t < types.length; t++) {
var idx = types[t];
for (var i = 0; i < events.length; i++) {
var eventType = events[i].idx.split('.');
if (events[i].idx === idx || (eventType[0] && eventType[0] === idx.replace('.', ''))) {
var evt = doc.createEvent('HTMLEvents');
evt.initEvent(eventType[0], false, true);
evt.originalEvent = detail;
this.sound.dispatchEvent(evt);
}
}
}
return this;
};
this.fadeTo = function (to, duration, callback) {
if (!supported) {
return this;
}
if (duration instanceof Function) {
callback = duration;
duration = buzz.defaults.duration;
} else {
duration = duration || buzz.defaults.duration;
}
var from = this.volume,
delay = duration / Math.abs(from - to),
self = this,
fadeToTimeout;
this.play();
function doFade() {
clearTimeout(fadeToTimeout);
fadeToTimeout = setTimeout(function () {
if (from < to && self.volume < to) {
self.setVolume(self.volume += 1);
doFade();
} else if (from > to && self.volume > to) {
self.setVolume(self.volume -= 1);
doFade();
} else if (callback instanceof Function) {
callback.apply(self);
}
}, delay);
}
this.whenReady(function () {
doFade();
});
return this;
};
this.fadeIn = function (duration, callback) {
if (!supported) {
return this;
}
return this.setVolume(0).fadeTo(100, duration, callback);
};
this.fadeOut = function (duration, callback) {
if (!supported) {
return this;
}
return this.fadeTo(0, duration, callback);
};
this.fadeWith = function (sound, duration) {
if (!supported) {
return this;
}
this.fadeOut(duration, function () {
this.stop();
});
sound.play().fadeIn(duration);
return this;
};
this.whenReady = function (func) {
if (!supported) {
return null;
}
var self = this;
if (this.sound.readyState === 0) {
this.bind('canplay.buzzwhenready', function () {
func.call(self);
});
} else {
func.call(self);
}
};
this.addSource = function (src) {
var self = this,
source = doc.createElement('source');
source.src = src;
if (buzz.types[getExt(src)]) {
source.type = buzz.types[getExt(src)];
}
this.sound.appendChild(source);
source.addEventListener('error', function (e) {
self.trigger('sourceerror', e);
});
return source;
};
// privates
function timerangeToArray(timeRange) {
var array = [],
length = timeRange.length - 1;
for (var i = 0; i <= length; i++) {
array.push({
start: timeRange.start(i),
end: timeRange.end(i)
});
}
return array;
}
function getExt(filename) {
return filename.split('.').pop();
}
// init
if (supported && src) {
for (var i in buzz.defaults) {
if (buzz.defaults.hasOwnProperty(i)) {
if (options[i] === undefined) {
options[i] = buzz.defaults[i];
}
}
}
this.sound = doc.createElement('audio');
// Shoud we set crossOrigin?
if (options.crossOrigin !== null) {
this.sound.crossOrigin = options.crossOrigin;
}
// Use web audio if possible to improve performance.
if (options.webAudioApi) {
var audioCtx = buzz.getAudioContext();
if (audioCtx) {
this.source = audioCtx.createMediaElementSource(this.sound);
this.source.connect(audioCtx.destination);
}
}
if (src instanceof Array) {
for (var j in src) {
if (src.hasOwnProperty(j)) {
this.addSource(src[j]);
}
}
} else if (options.formats.length) {
for (var k in options.formats) {
if (options.formats.hasOwnProperty(k)) {
this.addSource(src + '.' + options.formats[k]);
}
}
} else {
this.addSource(src);
}
if (options.loop) {
this.loop();
}
if (options.autoplay) {
this.sound.autoplay = 'autoplay';
}
if (options.preload === true) {
this.sound.preload = 'auto';
} else if (options.preload === false) {
this.sound.preload = 'none';
} else {
this.sound.preload = options.preload;
}
this.setVolume(options.volume);
buzz.sounds.push(this);
}
},
group: function (sounds) {
sounds = argsToArray(sounds, arguments);
// publics
this.getSounds = function () {
return sounds;
};
this.add = function (soundArray) {
soundArray = argsToArray(soundArray, arguments);
for (var a = 0; a < soundArray.length; a++) {
sounds.push(soundArray[a]);
}
};
this.remove = function (soundArray) {
soundArray = argsToArray(soundArray, arguments);
for (var a = 0; a < soundArray.length; a++) {
for (var i = 0; i < sounds.length; i++) {
if (sounds[i] === soundArray[a]) {
sounds.splice(i, 1);
break;
}
}
}
};
this.load = function () {
fn('load');
return this;
};
this.play = function () {
fn('play');
return this;
};
this.togglePlay = function () {
fn('togglePlay');
return this;
};
this.pause = function (time) {
fn('pause', time);
return this;
};
this.stop = function () {
fn('stop');
return this;
};
this.mute = function () {
fn('mute');
return this;
};
this.unmute = function () {
fn('unmute');
return this;
};
this.toggleMute = function () {
fn('toggleMute');
return this;
};
this.setVolume = function (volume) {
fn('setVolume', volume);
return this;
};
this.increaseVolume = function (value) {
fn('increaseVolume', value);
return this;
};
this.decreaseVolume = function (value) {
fn('decreaseVolume', value);
return this;
};
this.loop = function () {
fn('loop');
return this;
};
this.unloop = function () {
fn('unloop');
return this;
};
this.setSpeed = function (speed) {
fn('setSpeed', speed);
return this;
};
this.setTime = function (time) {
fn('setTime', time);
return this;
};
this.set = function (key, value) {
fn('set', key, value);
return this;
};
this.bind = function (type, func) {
fn('bind', type, func);
return this;
};
this.unbind = function (type) {
fn('unbind', type);
return this;
};
this.bindOnce = function (type, func) {
fn('bindOnce', type, func);
return this;
};
this.trigger = function (type) {
fn('trigger', type);
return this;
};
this.fade = function (from, to, duration, callback) {
fn('fade', from, to, duration, callback);
return this;
};
this.fadeIn = function (duration, callback) {
fn('fadeIn', duration, callback);
return this;
};
this.fadeOut = function (duration, callback) {
fn('fadeOut', duration, callback);
return this;
};
// privates
function fn() {
var args = argsToArray(null, arguments),
func = args.shift();
for (var i = 0; i < sounds.length; i++) {
sounds[i][func].apply(sounds[i], args);
}
}
function argsToArray(array, args) {
return (array instanceof Array) ? array : Array.prototype.slice.call(args);
}
},
all: function () {
return new buzz.group(buzz.sounds);
},
isSupported: function () {
return !!buzz.el.canPlayType;
},
isOGGSupported: function () {
return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/ogg; codecs="vorbis"');
},
isWAVSupported: function () {
return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/wav; codecs="1"');
},
isMP3Supported: function () {
return !!buzz.el.canPlayType && buzz.el.canPlayType('audio/mpeg;');
},
isAACSupported: function () {
return !!buzz.el.canPlayType && (buzz.el.canPlayType('audio/x-m4a;') || buzz.el.canPlayType('audio/aac;'));
},
toTimer: function (time, withHours) {
var h, m, s;
h = Math.floor(time / 3600);
h = isNaN(h) ? '--' : (h >= 10) ? h : '0' + h;
m = withHours ? Math.floor(time / 60 % 60) : Math.floor(time / 60);
m = isNaN(m) ? '--' : (m >= 10) ? m : '0' + m;
s = Math.floor(time % 60);
s = isNaN(s) ? '--' : (s >= 10) ? s : '0' + s;
return withHours ? h + ':' + m + ':' + s : m + ':' + s;
},
fromTimer: function (time) {
var splits = time.toString().split(':');
if (splits && splits.length === 3) {
time = (parseInt(splits[0], 10) * 3600) + (parseInt(splits[1], 10) * 60) + parseInt(splits[2], 10);
}
if (splits && splits.length === 2) {
time = (parseInt(splits[0], 10) * 60) + parseInt(splits[1], 10);
}
return time;
},
toPercent: function (value, total, decimal) {
var r = Math.pow(10, decimal || 0);
return Math.round(((value * 100) / total) * r) / r;
},
fromPercent: function (percent, total, decimal) {
var r = Math.pow(10, decimal || 0);
return Math.round(((total / 100) * percent) * r) / r;
}
};
return buzz;
});