src/Waud.hx
import haxe.Timer;
import js.html.HTMLDocument;
import js.html.AudioElement;
import js.Browser;
/**
* Web Audio Library with HTML5 audio fallback.
*
* @class Waud
*/
@:expose @:keep class Waud {
static inline var PROBABLY:String = "probably";
static inline var MAYBE:String = "maybe";
/**
* Version number.
*
* @property version
* @static
* @type {String}
*/
public static var version:String = "1.0.3";
/**
* Tells whether to use web audio api or not.
*
* You can use this to enable/disable web audio globally for all sounds.
*
* Note that you can also enable/disable web audio individually for each sound instance.
*
* @property useWebAudio
* @static
* @type {Bool}
* @default true
* @example
* Waud.useWebAudio = false;
*/
public static var useWebAudio:Bool = true;
/**
* Tells whether web audio api is supported or not.
*
* @property isWebAudioSupported
* @static
* @type {Bool}
* @readOnly
* @example
* Waud.isWebAudioSupported;
*/
public static var isWebAudioSupported(default, null):Bool;
/**
* Tells whether html5 audio is supported or not.
*
* @property isHTML5AudioSupported
* @static
* @type {Bool}
* @readOnly
* @example
* Waud.isHTML5AudioSupported;
*/
public static var isHTML5AudioSupported(default, null):Bool;
/**
* Defaults properties used on sound.
*
* @property defaults
* @static
* @type {WaudSoundOptions}
* @default { autoplay: false, loop: false, preload: true, webaudio: true, volume: 1 }
* @example
* Waud.defaults = { volume: 0.5, autoplay: true, preload: false };
*/
public static var defaults:WaudSoundOptions = {
autoplay: false,
autostop: true,
loop: false,
preload: true,
webaudio: true,
volume: 1,
playbackRate: 1
};
/**
* Holds all the sounds that are loaded.
*
* @property sounds
* @static
* @type {Map<String, IWaudSound>}
* @readOnly
* @example
* Waud.sounds.get("url");
*/
public static var sounds(default, null):Map<String, IWaudSound>;
/**
* Preferred sample rate used when creating buffer on audio context.
*
* It is recommended to use audio files with same sample rate and set the value used here.
*
* @property preferredSampleRate
* @static
* @type {Int}
* @default 44100
* @example
* Waud.preferredSampleRate = 22050;
*/
public static var preferredSampleRate:Int = 44100;
/**
* Audio Manager instance.
*
* @property audioManager
* @static
* @type {AudioManager}
* @readOnly
*/
public static var audioManager(default, null):AudioManager;
/**
* Audio Context reference.
*
* @property audioContext
* @static
* @type {AudioContext}
* @readOnly
*/
public static var audioContext:Dynamic;
/**
* Document dom element used for appending sounds and touch events.
*
* @property dom
* @static
* @type {document}
*/
public static var dom(default, null):HTMLDocument;
/**
* State of audio, muted or not.
*
* @property isMuted
* @static
* @type {Bool}
* @readOnly
* @default false
* @example
* Waud.isMuted;
*/
public static var isMuted(default, null):Bool = false;
/**
* Touch unlock callback reference.
*
* @property __touchUnlockCallback
* @static
* @protected
* @type {Function}
*/
public static var __touchUnlockCallback:Void -> Void;
/**
* Audio element used to check audio support.
*
* @property __audioElement
* @static
* @private
* @type {AudioElement}
* @readOnly
*/
static var __audioElement:AudioElement;
/**
* Global playback rate.
*
* @property _playbackRate
* @static
* @private
* @type {Float}
*/
public static var _playbackRate:Float = 1;
/**
* Focus Manager reference used for `autoMute` functionality.
*
* @property _focusManager
* @static
* @private
* @type {WaudFocusManager}
* @readOnly
*/
static var _focusManager:WaudFocusManager;
/**
* Current global volume.
*
* @property _volume
* @static
* @private
* @type {Float}
* @readOnly
*/
static var _volume:Float;
/**
* To initialise the library, make sure you call this first.
*
* You can also pass an optional parent DOM element to it where all the HTML5 sounds will be appended
* and also used for touch events to unlock audio on iOS devices.
*
* @static
* @method init
* @param {HTMLDocument} [d = document]
* @example
* Waud.init();
*/
public static function init(?d:HTMLDocument) {
if (__audioElement == null) {
if (d == null) d = Browser.document;
dom = d;
__audioElement = dom.createAudioElement();
if (Waud.audioManager == null) Waud.audioManager = new AudioManager();
isWebAudioSupported = Waud.audioManager.checkWebAudioAPISupport();
isHTML5AudioSupported = (Reflect.field(Browser.window, "Audio") != null);
if (isWebAudioSupported) audioContext = Waud.audioManager.createAudioContext();
sounds = new Map();
_volume = 1;
_sayHello();
}
}
static inline function _sayHello() {
var support = isWebAudioSupported ? "Web Audio" : "HTML5 Audio";
if (Browser.navigator.userAgent.toLowerCase().indexOf("chrome") > 1) {
var e = [
"\n %c %c %c WAUD%c.%cJS%c v" + version + " - " + support + " %c %c http://www.waudjs.com %c %c %c 📢 \n\n",
"background: #32BEA6; padding:5px 0;",
"background: #32BEA6; padding:5px 0;",
"color: #E70000; background: #29162B; padding:5px 0;",
"color: #F3B607; background: #29162B; padding:5px 0;",
"color: #32BEA6; background: #29162B; padding:5px 0;",
"color: #999999; background: #29162B; padding:5px 0;",
"background: #32BEA6; padding:5px 0;",
"background: #B8FCEF; padding:5px 0;",
"background: #32BEA6; padding:5px 0;",
"color: #E70000; background: #32BEA6; padding:5px 0;",
"color: #FF2424; background: #FFFFFF; padding:5px 0;"
];
untyped __js__("window.console.log").apply(Browser.window.console, e);
}
else Browser.window.console.log("WAUD.JS v" + version + " - " + support + " - http://www.waudjs.com");
}
/**
* Helper function to automatically mute audio when the browser window is not in focus.
*
* Will un-mute when the window gains focus.
*
* @static
* @method autoMute
* @example
* Waud.autoMute();
*/
public static function autoMute() {
_focusManager = new WaudFocusManager(dom);
_focusManager.focus = function() {
mute(false);
audioManager.resumeContext();
}
_focusManager.blur = function() {
mute(true);
audioManager.suspendContext();
}
}
/**
* Helper function to unlock audio on iOS devices.
*
* You can pass an optional callback which will be triggered after unlocking audio.
*
* @static
* @method enableTouchUnlock
* @param {Function} [callback] - Optional callback that triggers after unlocking audio.
* @example
* Waud.enableTouchUnlock(callback);
*/
public static function enableTouchUnlock(?callback:Void -> Void) {
__touchUnlockCallback = callback;
dom.ontouchend = Waud.audioManager.unlockAudio;
}
/**
* Function to set global volume.
*
* @static
* @method setVolume
* @param {Float} val - Should be between 0 and 1.
* @example
* Waud.setVolume(0.5);
*/
public static function setVolume(val:Float) {
if ((Std.is(val, Int) || Std.is(val, Float)) && val >= 0 && val <= 1) {
_volume = val;
if (sounds != null) for (sound in sounds) sound.setVolume(val);
}
else Browser.console.warn("Volume should be a number between 0 and 1. Received: " + val);
}
/**
* Function to get global volume.
*
* @static
* @method getVolume
* @return {Float} between 0 and 1
* @example
* Waud.getVolume();
*/
public static function getVolume():Float {
return _volume;
}
/**
* Helper function to mute all the sounds.
*
* @static
* @method mute
* @param {Bool} [val = true]
* @example
* Waud.mute();
* Waud.mute(true);
* Waud.mute(false);
*/
public static function mute(?val:Bool = true) {
isMuted = val;
if (sounds != null) for (sound in sounds) sound.mute(val);
if (val) audioManager.suspendContext();
else audioManager.resumeContext();
}
/**
* Helper function to set playback rate of all the sounds.
*
* @static
* @method playbackRate
* @param {Float} [val]
* @return {Float} current playback rate.
* @example
* Waud.playbackRate();
* Waud.playbackRate(1.25);
*/
public static function playbackRate(?val:Float):Float {
if (val == null) return _playbackRate;
else if (sounds != null) for (sound in sounds) sound.playbackRate(val);
return _playbackRate = val;
}
/**
* Helper function to stop all the sounds.
*
* @static
* @method stop
* @example
* Waud.stop();
*/
public static function stop() {
if (sounds != null) for (sound in sounds) sound.stop();
}
/**
* Helper function to pause all the sounds.
*
* @static
* @method pause
* @example
* Waud.pause();
*/
public static function pause() {
if (sounds != null) for (sound in sounds) sound.pause();
}
/**
* Helper function to play sounds sequentially. This function assumes sounds are already initialised and loaded.
*
* @static
* @method playSequence
* @param {Array<String>} snds - Array of sounds to play sequentially
* @param {Function} [onComplete] - Optional callback that triggers after playing all sounds in the sequence.
* @param {Function} [onSoundComplete] - Optional callback that triggers after playing each sounds in the sequence.
* @param {Int} [interval] - Optional interval in milliseconds to play sequence. Use this to play sounds in
* set interval instead of waiting for the previous sound to finish.
* @example
* Waud.playSequence(["snd1", "snd2", "snd3"]);
* Waud.playSequence(["snd1", "snd2", "snd3"], onComplete);
* Waud.playSequence(["snd1", "snd2", "snd3"], onComplete, onSoundComplete);
* Waud.playSequence(["snd1", "snd2", "snd3"], 500);
*/
public static function playSequence(snds:Array<String>, ?onComplete:Void -> Void, ?onSoundComplete:String -> Void, ?interval:Int = -1) {
if (snds == null || snds.length == 0) return;
for (i in 0 ... snds.length) {
if (sounds.get(snds[i]) == null) {
trace("Unable to find \"" + snds[i] + "\" in the sequence, skipping it");
snds.splice(i, 1);
}
}
var playSound:Void -> Void = null;
playSound = function() {
if (snds.length > 0) {
var sndStr = snds.shift();
var sndToPlay:IWaudSound = sounds.get(sndStr);
sndToPlay.play();
if (interval > 0) {
Timer.delay(function() {
if (onSoundComplete != null) onSoundComplete(sndStr);
playSound();
},
interval);
}
else {
sndToPlay.onEnd(function(snd:IWaudSound) {
if (onSoundComplete != null) onSoundComplete(sndStr);
playSound();
});
}
}
else {
if (onComplete != null) onComplete();
}
}
playSound();
}
/**
* Returns a string with all the format support information.
*
* @static
* @method getFormatSupportString
* @return {String} support string `OGG: probably, WAV: probably, MP3: probably, AAC: probably, M4A: maybe` (example)
* @example
* Waud.getFormatSupportString();
*/
public static function getFormatSupportString():String {
var support:String = "OGG: " + __audioElement.canPlayType('audio/ogg; codecs="vorbis"');
support += ", WAV: " + __audioElement.canPlayType('audio/wav; codecs="1"');
support += ", MP3: " + __audioElement.canPlayType('audio/mpeg;');
support += ", AAC: " + __audioElement.canPlayType('audio/aac;');
support += ", M4A: " + __audioElement.canPlayType('audio/x-m4a;');
return support;
}
/**
* Function to check whether audio is supported or not.
*
* @static
* @method isSupported
* @return {Bool} true or false
* @example
* Waud.isSupported();
*/
public static function isSupported():Bool {
if (isWebAudioSupported == null || isHTML5AudioSupported == null) {
isWebAudioSupported = Waud.audioManager.checkWebAudioAPISupport();
isHTML5AudioSupported = (Reflect.field(Browser.window, "Audio") != null);
}
return (isWebAudioSupported || isHTML5AudioSupported);
}
/**
* Function to check `ogg` format support.
*
* @static
* @method isOGGSupported
* @return {Bool} true or false
* @example
* Waud.isOGGSupported();
*/
public static function isOGGSupported():Bool {
var canPlay = __audioElement.canPlayType('audio/ogg; codecs="vorbis"');
return (isHTML5AudioSupported && canPlay != null && (canPlay == PROBABLY || canPlay == MAYBE));
}
/**
* Function to check `wav` format support.
*
* @static
* @method isWAVSupported
* @return {Bool} true or false
* @example
* Waud.isWAVSupported();
*/
public static function isWAVSupported():Bool {
var canPlay = __audioElement.canPlayType('audio/wav; codecs="1"');
return (isHTML5AudioSupported && canPlay != null && (canPlay == PROBABLY || canPlay == MAYBE));
}
/**
* Function to check `mp3` format support.
*
* @static
* @method isMP3Supported
* @return {Bool} true or false
* @example
* Waud.isMP3Supported();
*/
public static function isMP3Supported():Bool {
var canPlay = __audioElement.canPlayType('audio/mpeg;');
return (isHTML5AudioSupported && canPlay != null && (canPlay == PROBABLY || canPlay == MAYBE));
}
/**
* Function to check `aac` format support.
*
* @static
* @method isAACSupported
* @return {Bool} true or false
* @example
* Waud.isAACSupported();
*/
public static function isAACSupported():Bool {
var canPlay = __audioElement.canPlayType('audio/aac;');
return (isHTML5AudioSupported && canPlay != null && (canPlay == PROBABLY || canPlay == MAYBE));
}
/**
* Function to check `m4a` format support.
*
* @static
* @method isM4ASupported
* @return {Bool} true or false
* @example
* Waud.isM4ASupported();
*/
public static function isM4ASupported():Bool {
var canPlay = __audioElement.canPlayType('audio/x-m4a;');
return (isHTML5AudioSupported && canPlay != null && (canPlay == PROBABLY || canPlay == MAYBE));
}
/**
* Function to get current sample rate of audio context.
*
* @static
* @method getSampleRate
* @return {Float} sample rate
* @example
* Waud.getSampleRate();
*/
public static function getSampleRate():Float {
return audioContext != null ? audioContext.sampleRate : 0;
}
/**
* Function to destroy audio context.
*
* @static
* @method destroy
* @example
* Waud.destroy();
*/
public static function destroy() {
if (sounds != null) for (sound in sounds) sound.destroy();
sounds = null;
if (Waud.audioManager != null) Waud.audioManager.destroy();
Waud.audioManager = null;
Waud.audioContext = null;
__audioElement = null;
if (_focusManager != null) {
_focusManager.clearEvents();
_focusManager.blur = null;
_focusManager.focus = null;
_focusManager = null;
}
}
}