var SoundStage = ( function ( $ ) {

	var extraArgs = function ( args ) {
		var named = args.callee.length;
		var given = args.length;
		if ( given <= named ) return [];
		return Array.prototype.slice.call ( args, named );
	}

	var merge = function ( ob1, ob2 ) {
		// non-destructive
		var out = {}
		for ( var i = 0; i < arguments.length; i ++ ) {
			var obj = arguments[i];
			if ( isObject ( obj ) ) {
				for ( var property in obj ) {
					if ( obj.hasOwnProperty(property) && typeof out[property] == 'undefined') {
						out[property] = obj[property];
					}
				}
			}
		}
		return out;
	}

	var callback = function ( fn, context, persistent ) {
		var cb = "__" + (""+Math.random()).substr(2);
		SoundStage[cb] = function ( ) {
			delete SoundStage[cb];
			fn.apply ( context, Array.prototype.slice.call ( arguments, 0 ) );
			return persistent ? callback ( fn, context, persistent ) : null
		}
		return cb;
	}

	var _this = this;

	var SSError = function ( defaultError ) {
		return function ( error ) {
			return new Error ( error || defaultError );
		}
	}

	var is = function ( type, fn ) {
		type = type.toLowerCase();
		var match = "[object " + type.charAt(0).toUpperCase() + type.substr(1) + "]";
		if ( typeof fn != "function" || fn.length == 1 ) {
			var _fn = function ( obj ) {
				return ( Object.prototype.toString.call ( obj ) == match );
			}
			if ( typeof fn == "function" )
				_fn = fn;
			fn = function ( ) {
				var valid = !!arguments.length;
				for ( var i = 0; i < arguments.length; i ++ )
					valid = valid && _fn ( arguments[i] );
				return valid;
			}
		}
		arguments.callee[type] = fn;
		return fn;
	};
	var isNumber = is("number");
	var isNumberBetween = is("numberBetween", function ( n, a, b ) {
		if ( !isNumber ( n, a, b ) ) return false;
		return ( n >= a ) && ( n <= b );
	} );
	var isObject = is("object");
	var isArray = is("array");
	var isFunction = is("function");
	var isString = is("string");
	var isSound = is("sound", function ( obj ) {
		return ( obj instanceof Sound );
	} );

	var Events = ( function ( ) {
		var stack = { "*" : {} }
		var c = function ( str ) {
			return str.charAt(0).toUpperCase() + str.substr(1);
		}
		var fire = function ( fnStack, scope, args, raw ) {
			var loop;
			if ( raw ) {
				loop = function ( i ) {
					fnStack[i].fn.apply ( scope, isArray ( args ) ? args : [ args ] );
					return true;
				}
			} else {
				var e = {
					arguments : Array.prototype.slice.call ( args, 0 ),
					cancel : false,
					halt : false
				}
				loop = function ( i ) {
					e.originalArguments = Array.prototype.slice.call ( args, 0 );
					e.order = i;
					fnStack[i].fn.call ( scope, e );
					return !e.halt;
				}
			}
			var cleanup = [];
			for ( var i = 0; i < fnStack.length; i ++ ) {
				//debugger;
				var br = !loop ( i );
				if ( isNumber ( fnStack[i].count ) ) fnStack[i].count -= 1;
				if ( fnStack[i].count === 0 ) cleanup.push ( i );
				if ( br ) break;
			}
			return { response : raw ? true : e, cleanup : cleanup };
		}
		var add = function ( obj, modifier, property ) {
			var fn = modifier.toLowerCase() + c(property);
			if ( obj[fn] ) return;
			stack[obj._watchId] = stack[obj._watchId] || {};
			stack[obj._watchId][property] = stack[obj._watchId][property] || {};
			stack[obj._watchId][property][modifier] = stack[obj._watchId][property][modifier] || [];
			obj[fn] = function ( fn, count ) {
				if ( isFunction ( fn ) )
					stack[obj._watchId][property][modifier].push ( { fn : fn, count : isNumber(count) && (count > 0) ? count : null } );
			}
		}
		var init = function ( obj, property ) {
			if ( !property ) return;
			add ( obj, "before", property.toString() );
			add ( obj, "on", property.toString() );
			var o = obj[property];
			obj[property] = function ( ) {
				var response;
				var args = Array.prototype.slice.call ( arguments );
				//console.log ( obj._watchId, property, stack[obj._watchId][property]["before"] );
				var rsp = fire ( stack[obj._watchId][property]["before"], this, args );
				for ( var i = rsp.cleanup.length; i > 0; i -- ) {
					stack[obj._watchId][property]["before"].splice(rsp.cleanup[i-1],1);
				}
				try {
					var rsp = fire ( (stack["*"][property]||{})["before"] || [], this, args );
					for ( var i = rsp.cleanup.length; i > 0; i -- ) {
						stack["*"][property]["before"].splice(rsp.cleanup[i-1],1);
					}
				} catch ( ex ) {
					console.log ( "Error", ex );
				};
				var e = rsp.response;
				if ( !e.cancel ) {
					response = o.apply ( this, e.arguments );
					rsp = fire ( stack[obj._watchId][property]["on"], this, e.arguments );
					//console.log ( obj._watchId + " '" + property + "' cleanup: ", rsp, stack[obj._watchId][property]["on"] );
					for ( var i = rsp.cleanup.length; i > 0; i -- ) {
						stack[obj._watchId][property]["on"].splice(rsp.cleanup[i-1],1);
					}
					try {
						rsp = fire ( (stack["*"][property]||{})["on"] || [], this, e.arguments );
						for ( var i = rsp.cleanup.length; i > 0; i -- ) {
							stack["*"][property]["on"].splice(rsp.cleanup[i-1],1);
						}
					} catch ( ex ) {
						console.log ( "Error", ex );
					};
				}
				return response;
			}
			obj[property].length = o.length;
		}
		return {
			watch : function ( obj, properties ) {
				if ( !properties && obj.constructor ) properties = obj.constructor.prototype;
				if ( !properties ) return false;
				obj._watchId = obj._watchId || "w:" + (""+Math.random()).substr(2);
				if ( isArray ( properties ) ) {
					for ( var i = 0; i < properties.length; i ++ )
						init ( obj, properties[i] );
				} else if ( isObject ( properties ) ) {
					for ( i in properties )
						if ( isFunction ( obj[i] ) ) init ( obj, i );
				} else if ( properties ) {
					init ( obj, properties );
				}
				return true;
			},
			add : function ( obj, handle, args ) {
				obj._watchId = obj._watchId || "w:" + (""+Math.random()).substr(2);
				var event = function ( args, scope ) {
					var rsp = fire ( stack[obj._watchId][handle], scope || obj, args, true );
					for ( var i = rsp.cleanup.length; i > 0; i -- ) {
						stack[obj._watchId][handle].splice(rsp.cleanup[i-1],1);
					}
					try {
						rsp = fire ( stack["*"][handle] || [], scope || obj, args, true );
						for ( var i = rsp.cleanup.length; i > 0; i -- ) {
							stack["*"][handle].splice(rsp.cleanup[i-1],1);
						}
					} catch ( ex ) {
						console.log ( "Error", ex );
					};
				}
				stack[obj._watchId] = stack[obj._watchId] || {};
				stack[obj._watchId][handle] = stack[obj._watchId][handle] || [];
				if ( obj[handle] === undefined ) {
					obj[handle] = function ( fn, count ) {
						if ( isFunction ( fn ) ) {
							stack[obj._watchId][handle].push ( { fn : fn, count : isNumber(count) && (count > 0) ? count : null } );
							if ( event.immediate )
								fn.apply ( obj, args || [] );
						}
					}
				}
				var triggers = extraArgs ( arguments );
				for ( var i = 0; i < triggers.length; i ++ ) {
					if ( isFunction ( obj["on"+c(triggers[i])] ) )
						obj["on"+c(triggers[i])]( function () {
							fire ( stack[obj._watchId][handle], obj, args, true );
						} )
				}
				return event;
			},
			watcher : function ( key ) {
				if ( !isArray ( key ) ) key = [ key ];
				key.unshift ( "*" );
				return function ( fn, count ) {
					if ( !isFunction ( fn ) ) return false;
					var root = stack;
					while ( key.length ) {
						var p = key.shift();
						if ( key.length ) {
							root[p] = root[p] || {};
						} else {
							root[p] = [];
						}
						root = root[p];
					}
					root.push ( { fn : fn, count : isNumber(count) && (count > 0) ? count : null } );
				}
			}
		}
	} )();

	var Library = ( function ( ) {
		var library = {};
		return {
			add : function ( sound ) {
				if ( !isSound ( sound ) ) throw new SoundStage.Sound.InvalidError();
				library[sound.id] = sound;
			},
			remove : function ( sound ) {
				if ( !isSound ( sound ) ) throw new SoundStage.Sound.InvalidError();
				delete library[sound.id];
			},
			get : function ( id ) {
				if ( isSound ( id ) )
					return library[id.id];
				return library[id];
			},
			each : function ( fn, context ) {
				if ( isFunction ( fn ) ) {
					for ( var id in library ) {
						fn.call ( context || library[id], library[id], id );
					}
				}
			},

			ExistsError : SSError ( "A sound with this ID already exists" )
		}
	} )();

	var Jukebox = ( function ( ) {
		var remote;
		var jb_api;
		var buildStubJukebox = function ( ) {
			jb_api = {
				load : function ( sound, s, f ) {
					SoundStage[s]();
				},
				play : function ( sound ) {
					var s = Library.get(sound.id);
					if ( SoundStage[s.onStart] )
						s.onStart = SoundStage[s.onStart]();
					s.__playTimer = setTimeout ( function ( ) {
						if ( SoundStage[s.onComplete] )
							s.onComplete = SoundStage[s.onComplete]();
					}, s.getLength() );
				},
				stop : function ( sound ) {
					var s = Library.get(sound.id);
					clearTimeout ( s.__playTimer );
					if ( s.__subtitleId )
						hideSubtitle ( s.__subtitleId );
					delete s.__subtitleId;
				},
				getLength : function ( sound ) {
					var s = Library.get(sound.id);
					if ( s.length ) return s.length * 1000; // if given a length, use it - assuming seconds
					if ( s.value ) return s.value.length * 100; // need to work on this algorithm!
					return 5000; // default to 5 seconds
				}
			};
		}
		var buildJukebox = function ( api ) {
			var callback = function ( meta ) {
				var fn = function ( ) {
					return remote.call ( meta.reference, Array.prototype.slice.call ( arguments, 0 ) );
				}
				fn.length = meta.arguments || 0;
				return fn;
			}
			jb_api = {}
			for ( var fn in api ) {
				if ( api.hasOwnProperty ( fn ) ) {
					jb_api[fn] = callback ( api[fn] );
				}
			}
		}
		return {
			init : function ( callback ) {
				var containerStyle = {
					position: 'fixed',
					width: '8px',
					height: '8px',
					bottom: '0px',
					right: '0px'
				}
				var placeholder = document.createElement("div");
				placeholder.setAttribute( 'id', 'SoundStageJukebox' );

				var container = document.createElement("div");
				for ( var attr in containerStyle ) {
					if ( containerStyle.hasOwnProperty ( attr ) ) {
						container.style[attr] = containerStyle[attr];
					}
				}
				container.appendChild ( placeholder );
				document.body.appendChild ( container );

				swfobject.embedSWF( "/flash/SoundStage.swf", "SoundStageJukebox", "100%", "100%", "9.0.0", null, null, null, null, function ( rsp ) {
					if ( !rsp.success ) {
						buildStubJukebox();
						if ( typeof callback == "function" ) callback ( false );
					} else {
						var interval = setInterval ( function ( ) {
							try {
								remote = swfobject.getObjectById( "SoundStageJukebox" );
								var api = remote.init ( "SoundStage" );
								clearInterval ( interval );
								buildJukebox ( api );
								if ( typeof callback == "function" ) callback ( true );
							} catch ( e ) {
								arguments.callee.count = ( arguments.callee.count || 0 ) + 1;
								if ( arguments.callee.count > 20 ) {
									clearInterval ( interval );
									buildStubJukebox();
									if ( typeof callback == "function" ) callback ( false );
								}
							}
						}, 5 );
					}
				} );

				/*
				var interval = setInterval ( function ( ) {
					try {
						remote = swfobject.getObjectById( "SoundStageJukebox" );
						var api = remote.init ( "SoundStage" );
						clearInterval ( interval );
						buildJukebox ( api );
						if ( typeof callback == "function" ) callback ( true );
					} catch ( e ) {
						arguments.callee.count = ( arguments.callee.count || 0 ) + 1;
						if ( arguments.callee.count > 20 ) {
							clearInterval ( interval );
							buildStubJukebox();
							if ( typeof callback == "function" ) callback ( false );
							// need to handle a lack of flash...
						}
					}
				}, 5 );
				*/
			},
			call : function ( fn ) {
				var args = Array.prototype.slice.call ( arguments, 1 );
				if ( jb_api && ( typeof jb_api[fn] == "function" ) )
					return jb_api[fn].apply ( this, args );
				return null;
			}
		}
	} )();

	var metaCollector = function ( type, properties ) {
		return function ( obj ) {
			var meta = {};
			for ( var property in properties ) {
				var value = obj[property];
				if ( typeof value != properties[property] )
					switch ( properties[property] ) {
						case "string": value = ""; break;
						case "number": value = 0; break;
						case "boolean": value = false; break;
					}
				meta[property] = value;
			}
			return meta;
		}
	}

	var Sound = ( function ( ) {
		var meta = metaCollector ( "sound", { id:"string", src:"string", stream:"boolean", onStart:"string", onComplete:"string" } );
		var o = function ( src, options ) {
			if ( !src ) throw new SoundStage.Sound.InvalidError("Sounds must have a source");
			if ( !SoundStage.ready() ) throw new SoundStage.NotReadyError();
			options = isObject ( options ) ? options : {};

			Events.watch ( this );

			this.id = options.id || src;
			this.src = src;

			if ( Library.get ( this.id ) )
				throw new Library.ExistsError();

			var loadEvent = Events.add ( this, "onLoad" );
			this.onLoad ( function ( ) {
				this.setVolume ( SS.getVolume() );
				if ( SS.muted() ) this.mute();
			} );
			this.loaded = function ( ) {
				return !!loadEvent.immediate;
			}
			var failEvent = Events.add ( this, "onFail" );
			this.failed = function ( ) {
				return !!failEvent.immediate;
			}

			this.onStart = callback ( Events.add ( this, "onBegin" ), null, true );
			this.onComplete = callback ( Events.add ( this, "onEnd" ), null, true );

			options = options || {};
			for ( var i in options ) {
				if ( options.hasOwnProperty ( i ) ) {
					if ( isFunction ( this["set"+i.substr(0,1).toUpperCase()+i.substr(1).toLowerCase()] ) ) {
						try {
							( function ( i ) {
								this.onPlay ( function ( ) { this["set"+i.substr(0,1).toUpperCase()+i.substr(1).toLowerCase()](options[i]); }, 1 );
							} ).call ( this, i );
						} catch ( e ) {
							console.log ( e );
						}
					} else if ( !this[i] ) {
						this[i] = options[i];
					} else if ( isFunction ( this[i], options[i] ) && ( i.indexOf("on") == 0 || i.indexOf("before") == 0 ) ) {
						this[i] ( options[i] );
					}
				}
			}

			delete this.autoPlay;
			if ( options.autoPlay )
				this.onLoad ( function ( ) {
					this.play();
				} );

			this.onEnd ( function ( e ) {
				if ( this.repeat ) {
					//e.halt = true;
					//e.cancel = true;
					this.play();
				}
			} )

			if ( !options.noSubtitles ) {
				this.onBegin ( function ( ) {
					if ( window.showSubtitle )
						this.__subtitleId = showSubtitle ( this );
				} );
				this.onEnd ( function ( ) {
					if ( window.hideSubtitle )
						hideSubtitle ( this.__subtitleId );
					delete this.__subtitleId;
				} );
			}

			this.lazy = ( function ( l ) {
				return function ( ) { return l; }
			} )(!!options.lazy);

			this.clone = function ( id ) {
				var m = merge ( options );
				m.repeat = this.repeat;
				m.id = id;
				return new SoundStage.Sound ( this.src, m );
			}

			Library.add ( this );

			this.initialize ( function ( ) {
				loadEvent.immediate = true;
				loadEvent();
			}, function ( ) {
				failEvent.immediate = true;
				failEvent();
			}, this.lazy() );
		}
		o.InvalidError = SSError("Not a valid sound");
		o.VolumeError = SSError("Volume must be an integer between 0 and 100");
		o.PanError = SSError("Pan must be an integer between -100 and 100");
		o.LoadError = SSError("Sound not loaded. Wait for onLoad event");
		o.OutOfBoundsError = SSError("Progress must be within length of sound");
		o.prototype.initialize = ( function ( ) {
			var initialized = [];
			var initializing = [];
			var stack = [];
			var finish = function ( stack, failed ) {
				while ( stack.length ) {
					var cb = stack.shift();
					if ( failed ) {
						if ( isFunction ( cb.error ) ) cb.error.call(cb.context);
					} else if ( isFunction ( cb.success ) ) cb.success.call(cb.context);
				}
			}
			return function ( success, error, lazy ) {
				var id = this.id;
				stack[id] = stack[id] || [];
				stack[id].push ( { context : this, success : success, error : error } );
				if ( lazy ) return;
				if ( !initializing[id] && !initialized[id] ) {
					initializing[id] = true;
					Jukebox.call ( "load", meta ( this ), callback ( function ( sound ) {
						initialized[id] = true;
						initializing[id] = false;
						finish(stack[id],false);
					}, this ), callback ( function ( sound ) {
						initialized[id] = true;
						initializing[id] = false;
						finish(stack[id],true);
					}, this ) );
				} else if ( initialized[id] ) {
					finish(stack[id],this.failed());
				}
			}
		} )();
		o.prototype.play = function ( ) {
			if ( !this.lazy() && !this.loaded() ) throw new SoundStage.Sound.LoadError();
			this.initialize ( function ( ) {
				Jukebox.call ( "play", meta ( this ) );
			} );
		}
		o.prototype.pause = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			Jukebox.call ( "pause", meta ( this ) );
		}
		o.prototype.stop = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			Jukebox.call ( "stop", meta ( this ) );
		}
		o.prototype.restart = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			Jukebox.call ( "restart", meta ( this ) );
		}
		o.prototype.mute = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			Jukebox.call ( "mute", meta ( this ) );
		}
		o.prototype.unmute = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			Jukebox.call ( "unmute", meta ( this ) );
		}
		o.prototype.setPan = function ( pan ) {
			//if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			if ( !isNumberBetween ( pan, -100, 100 ) ) throw new SoundStage.Sound.PanError();
			Jukebox.call ( "setPan", pan, meta ( this ) );
		}
		o.prototype.setVolume = function ( volume ) {
			//if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			if ( !isNumberBetween ( volume, 0, 100 ) ) throw new SoundStage.Sound.VolumeError();
			Jukebox.call ( "setVolume", volume, meta ( this ) );
		}
		o.prototype.getVolume = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			return Jukebox.call ( "getVolume", meta ( this ) );
		}
		o.prototype.getSize = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			return Jukebox.call ( "getSize", meta ( this ) );
		},
		o.prototype.getLength = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			return Jukebox.call ( "getLength", meta ( this ) );
		}
		o.prototype.getProgress = function ( ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			return Jukebox.call ( "getProgress", meta ( this ) );
		}
		o.prototype.setProgress = function ( progress ) {
			if ( !this.loaded() ) throw new SoundStage.Sound.LoadError();
			if ( !isNumberBetween ( progress, 0, this.getLength() ) ) throw new SoundStage.Sound.OutOfBoundsError();
			return Jukebox.call ( "setProgress", progress, meta ( this ) );
		}
		o.prototype.status = function ( ) {
			return "stopped";
		}
		o.prototype.toString = function ( ) {
			return this.value || "[object Sound]";
		}
		return o;
	} )();

	var Playlist = ( function ( ) {
		var meta = metaCollector ( "playlist", { id:"string" } );
		var playlists = {};
		var o = function ( id, options ) {
			Events.watch ( this );

			playlists[id] = {
				sounds : [],
				currentIndex : 0
			};
			this.id = id;
			var sounds = Array.prototype.slice.call ( arguments, 2 );
			if ( isSound ( options ) ) {
				sounds.unshift ( options );
				options = null;
			}
			if ( !isObject ( options ) )
				options = {}
			playlists[id].options = options;
			/*
			for ( var i = sounds.length - 1; i >= 0; i-- ) {
				if ( !isSound ( sounds[i] ) )
					sounds.splice ( i, 1 );
			}
			*/
			this.add.apply ( this, sounds );

			this._end = Events.add ( this, "onEnd" );
		}
		o.prototype.add = function ( ) {
			for ( var i = 0; i < arguments.length; i ++ ) {
				if ( isSound ( arguments[i] ) ) {
					var playlist = this;
					playlists[this.id].sounds.push ( arguments[i].id );
					arguments[i].onStop ( function ( ) {
						playlists[playlist.id].currentIndex = 0;
					} );
				}
			}
		}
		o.prototype.remove = function ( ) { }
		o.prototype.play = function ( ) {
			var playlist = this;
			var currentIndex = playlists[this.id].currentIndex;
			var sound = Library.get(playlists[this.id].sounds[currentIndex]);

			if ( !sound && playlists[this.id].sounds.length && (playlists[this.id].options||{}).repeat ) {
				playlists[this.id].currentIndex = 0;
				if ( isNumber ( playlists[this.id].options.repeat ) )
					playlists[this.id].options.repeat -= 1;
				sound = Library.get(playlists[this.id].sounds[0]);
			}

			if ( sound ) {
				sound.onBegin ( function ( ) {
					playlists[playlist.id].currentIndex += 1;
					//console.log ( "Playing <" + this.id + "> (" + playlists[playlist.id].currentIndex + ")" );
				}, 1 );
				sound.onEnd ( function ( ) {
					var delay = parseInt ( (playlists[playlist.id].options||{}).delay );
					setTimeout ( function ( ) {
						playlist.play();
					}, !isNaN ( delay ) ? delay : 1 );
				}, 1 );
				sound.onStop ( function ( ) {
					playlists[playlist.id].currentIndex = 0;
				}, 1 );
				sound.onLoad ( function ( ) {
					sound.play();
				}, 1 );
			} else {
				this._end();
			}
		}
		o.prototype.pause = function ( ) { }
		o.prototype.stop = function ( ) {
			
		}
		o.prototype.remove = function ( ) { }
		o.prototype.next = function ( ) { }
		o.prototype.previous = function ( ) {
			return null;
		}
		o.prototype.current = function ( ) { }
		o.prototype.status = function ( ) {
			if ( this.current() )
				return this.current.status();
			return "stopped";
		}

		o.prototype.each = function ( fn ) {
			if ( isFunction ( fn ) ) {
				var sounds = playlists[this.id].sounds;
				for ( var i = 0, l = sounds.length; i < l; i ++ ) {
					fn ( Library.get(sounds[i]) );
				}
			}
		}
		return o;
	} )();

	var readyEvent;

	var SS = ( function ( ) {
		var global_volume = 100;
		var muted = false;
		return {
			log : function ( msg ) {
				//console.info ( msg );
			},
			ready : function ( ) {
				return !!readyEvent.immediate;
			},
			stop : function ( ) {
				Library.each ( function ( ) {
					try { this.stop(); } catch ( e ) { }
				} );
			},
	
			setPan : function ( pan ) {
				Library.each ( function ( ) {
					this.setPan ( pan );
				} );
			},
			setVolume : function ( volume ) {
				Library.each ( function ( ) {
					//console.log ( this, volume, this.getVolume() );
					this.setVolume ( volume );
				} );
				global_volume = volume;
			},
			getVolume : function ( ) {
				return global_volume;
			},
			muted : function ( ) {
				return muted;
			},
			mute : function ( ) {
				muted = true;
				Library.each ( function ( ) {
					this.mute();
				} );
			},
			unmute : function ( ) {
				muted = false;
				Library.each ( function ( ) {
					this.unmute();
				} );
			},
	
			onBegin : Events.watcher ( "onBegin", function ( ) {
					
			} ),
			onEnd : Events.watcher ( "onEnd", function ( ) {
					
			} ),
	
			NotReadyError : SSError ( "SoundStage is not loaded. Wait for whenReady event" ),
	
			Sound : Sound,
			Playlist : Playlist
		}
	} )();

	readyEvent = Events.add ( SS, "whenReady" );
	// initialise SoundStage
	$( function ( ) {
		Jukebox.init ( function ( success ) {
			readyEvent.immediate = true;
			readyEvent();
			if ( success )
				$(document.body).removeClass ( "no-audio" );
			else
				$(document.body).addClass ( "no-audio" );
		} );
	} );

	return SS;

} )( jQuery );

