API Docs for: 0.3.0
Show:

File: modules/Audio/Sound.js

/**
 * @module Audio
 * @namespace Audio
 */

var TW = TW || {};
define(['../Utils/Polyfills'], function() {

	TW.Audio = TW.Audio || {};


	TW.Audio.PLAY_SUCCEEDED = "playSucceeded";
	TW.Audio.PLAY_FINISHED = "playFinished";
	TW.Audio.PLAY_FAILED = "playFailed";

	TW.Audio.AUDIO_READY = "canplaythrough";
	TW.Audio.AUDIO_ENDED = "ended";
	TW.Audio.AUDIO_PLAYED = "play";

	/**
	 * Sound class is object represent html5 sound tag.
	 *
	 * @class Sound
	 * @constructor
	 * @param {String|String[]} src The sound source url. If an array is passed, the first supported source is used,
	 *  so provide the same sound in many formats is recommended.
	 */
	function Sound(src) {

		/**
		 * Audio play state.
		 *
		 * @property {String} playState
		 * @readonly
		 * @default null
		 */
		this.playState = null;


		/**
		 * Audio offset.
		 *
		 * @property {Number} offset
		 * @default 0
		 * @private
		 */
		this._offset = 0;

		/**
		 * Audio volume, between 0.0 and 1.0
		 *
		 * @property {Number} _volume
		 * @default 1.0
		 * @private
		 */
		this._volume = 1;

		/**
		 * Number of loop remaining to play.
		 *
		 * @property {Number} remainingLoops
		 * @default 0
		 */
		this.remainingLoops = 0;

		/**
		 * Mute state.
		 *
		 * @property {Boolean} _muted
		 * @default false
		 * @private
		 */
		this._muted = false;

		/**
		 * Pause state.
		 *
		 * @property {Boolean} paused
		 * @readonly
		 * @default false
		 */
		this.paused = false;

		/**
		 * Callback function when sound play is complete.
		 *
		 * @property {Function} onComplete
		 * @default null
		 */
		this.onComplete = null;

		/**
		 * Callback called when sound play restart loop.
		 *
		 * @property {Function} onLoop
		 * @default null
		 */
		this.onLoop = null;

		/**
		 * Callback called when sound is ready to play.
		 *
		 * @property {Function} onReady
		 * @default null
		 */
		this.onReady = null;

		/**
		 * Html5 tag audio.
		 *
		 * @property {Audio} audio
		 * @type Object
		 */
		this.audio = document.createElement("audio");

		/**
		 * Html5 tag audio capabilities.
		 * Indicates for each format if it is supported by the navigator.
		 *
		 * Supported format are mp3, ogg and wav.
		 *
		 * @property {Object} audio
		 * @readonly
		 * @example
		 *
		 *     { mp3: true, ogg: false, wav: true }
		 *
		 */
		this.capabilities = {
			mp3: ( this.audio.canPlayType("audio/mp3") !== "no" && this.audio.canPlayType("audio/mp3") !== "" ),
			ogg: ( this.audio.canPlayType("audio/ogg") !== "no" && this.audio.canPlayType("audio/ogg") !== "" ),
			wav: ( this.audio.canPlayType("audio/wav") !== "no" && this.audio.canPlayType("audio/wav") !== "" )
		};

		/**
		 * Audio source file, used by the audio element.
		 *
		 * Even if many paths are passed to constructor, `src` contain only that which is currently used.
		 *
		 * @property {String} src
		 * @readonly
		 */
		this.src = this._choosePath(src);
		if (src === null) {
			throw new Error("Unable to load sound: no supported sources.");
		}

		this.audio.src = this.src;
		this.endedHandler = this.handleSoundComplete.bind(this);
		this.readyHandler = this.handleSoundReady.bind(this);
	}

	/**
	 * choose a compatible source for audio tag.
	 *
	 * @method _choosePath
	 * @param {String|String[]} sources url or array of urls for source audio file.
	 * @return {String} the first copmpatible url if any; `null` otherwise.
	 * @private
	 */
	Sound.prototype._choosePath = function(sources) {
		if (!(sources instanceof Array)) {
			sources = [sources];
		}

		var length = sources.length;
		for (var i = 0; i < length; i++) {
			var point = sources[i].lastIndexOf(".");
			var ext = sources[i].substr(point + 1).toLowerCase();
			if (this.capabilities[ext] === true) {
				return sources[i];
			}
		}
		return null;
	};

	/**
	 * Called when an error occurs for cleaning audio tag.
	 *
	 * @method _playFailed
	 * @private
	 */
	Sound.prototype._playFailed = function() {
		this.playState = TW.Audio.PLAY_FAILED;
		this.audio.pause();
		this.audio.currentTime = 0;
		this.audio.removeEventListener(TW.Audio.AUDIO_ENDED, this.endedHandler, false);
		this.audio.removeEventListener(TW.Audio.AUDIO_READY, this.readyHandler, false);
		this.audio = null;
	};

	/**
	 * Load sound and call `onReady` callback when load finish.
	 *
	 * @method load
	 * @param {Number} offset The offset where sound start.
	 * @param {Number} loop The number of loop where sound played.
	 * @param {Number} volume The volume of sound.
	 * @return {Boolean} true if sound begin the loading or false if the sound loading is impossible.
	 */
	Sound.prototype.load = function(offset, loop, volume) {

		if (this.audio === null) {
			this._playFailed();
			return false;
		}

		this.audio.addEventListener(TW.Audio.AUDIO_ENDED, this.endedHandler, false);

		this._offset = offset;
		this._volume = volume;
		this._updateVolume();
		this.remainingLoops = loop;

		if (this.audio.readyState !== 4) {
			this.audio.addEventListener(TW.Audio.AUDIO_READY, this.readyHandler, false);
			this.audio.load();
		} else {
			this.handleSoundReady();
		}

		return true;
	};

	Sound.prototype.handleSoundReady = function() {
		this.playState = TW.Audio.PLAY_SUCCEEDED;
		this.paused = false;
		this.audio.removeEventListener(TW.Audio.AUDIO_READY, this.readyHandler, false);

		if (this._offset >= this.getDuration()) {
			this._playFailed();
			return;
		}

		this.audio.currentTime = this._offset;

		if (this.onReady !== null) {
			this.onReady(this);
		}
	};

	Sound.prototype.handleSoundComplete = function() {
		if (this.remainingLoops !== 0) {
			this.remainingLoops--;
			this.audio.currentTime = 0;
			this.audio.play();
			if (this.onLoop !== null) {
				this.onLoop(this);
			}
			return;
		}
		this.playState = TW.Audio.PLAY_FINISHED;

		if (this.onComplete !== null) {
			this.onComplete(this);
		}
	};

	/**
	 * Start play sound.
	 *
	 * @method play
	 */
	Sound.prototype.play = function() {
		this.audio.play();
		this.playState = TW.Audio.AUDIO_PLAYED;
	};

	/**
	 * Pause sound.
	 *
	 * @method pause
	 */
	Sound.prototype.pause = function() {
		this.paused = true;
		this.audio.pause();
	};

	/**
	 * Resume sound.
	 *
	 * @method resume
	 */
	Sound.prototype.resume = function() {
		this.paused = false;
		this.audio.play();
	};

	/**
	 * Stop sound.
	 *
	 * @method stop
	 */
	Sound.prototype.stop = function() {
		this.pause();
		this.playState = TW.Audio.PLAY_FINISHED;
		this.audio.currentTime = 0;
	};

	/**
	 * Set current sound volume.
	 *
	 * @method mute
	 * @param {Number} value sound volume, between 0.0 and 1.0.
	 */
	Sound.prototype.setVolume = function(value) {
		value = (value > 1.0) ? 1.0 : value;
		value = (value < 0.0) ? 0.0 : value;
		this._volume = value;
		this._updateVolume();
	};

	/**
	 * Applies all volume modifications.
	 *
	 * @method _updateVolume
	 * @private
	 */
	Sound.prototype._updateVolume = function() {
		this.audio.volume = this._muted ? 0 : this._volume;
	};

	/**
	 * Get current sound volume.
	 *
	 * @method getVolume
	 * @return {Number} A current sound volume.
	 */
	Sound.prototype.getVolume = function() {
		return this._volume;
	};

	/**
	 * Mute or Unmute all sound in this channel.
	 *
	 * @method mute
	 * @param {Boolean} isMuted True for mute or false for unmute.
	 */
	Sound.prototype.mute = function(isMuted) {
		this._muted = isMuted;
		this._updateVolume();
	};

	/**
	 * Get current sound offset.
	 *
	 * @method getPosition
	 * @return {Number} A current sound offset.
	 */
	Sound.prototype.getPosition = function() {
		return this.audio.currentTime;
	};

	/**
	 * Set current sound offset.
	 *
	 * @method setPosition
	 * @param {Number} value The value of offset.
	 */
	Sound.prototype.setPosition = function(value) {
		this.audio.currentTime = value;
	};

	/**
	 * Get current sound duration.
	 *
	 * @method getDuration
	 * @return {Number} A current sound duration.
	 */
	Sound.prototype.getDuration = function() {
		return this.audio.duration;
	};

	TW.Audio.Sound = Sound;
	return Sound;
});