UNPKG

buzz

Version:

Buzz, a Javascript HTML5 Audio library

971 lines (738 loc) 27.1 kB
// ---------------------------------------------------------------------------- // 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; });