/**
 * Copyright movideo Pty Ltd
 * @version 2.3 
 * @requires <a href="http://jquery.com/" target="_blank">jQuery 1.4.2</a> or Later
 * @requires <a href="http://www.fyneworks.com/jquery/xml-to-json/" target="_blank">jQuery XML to JSON Plugin v1.0</a> or Later
 * @description The movideo JavaScript SDK enables you to access features of the Media API via JavaScript, and it provides a rich set of client-side functionality. 
 * <br/><p>All functions in the JavaScript media SDK require an appAlias and apiKey. You can get this information in your Administration Console.</p>
 * @namespace
 */
var MOVIDEO = (function ($) {

	var api = 'http://api.movideo.com/rest/';
	var token = null;
	var loc = null;
	var ip = null;
	var clientAlias = null;
	var clientId = null;
	var apiKey = null;
	var appAlias = null;
	var application = null;
	var authenticated = false;
	var authenticating = false;
	var maxRequestAttempts = 3;
	
	/** @private */
	function hasSessionInCookie(){
		var sessionToken = MOVIDEO.utils.cookie(cookieSessionToken());
		if (sessionToken) {
			return true;
		}
		return false;
	}
	
	function setSessionFromCookie() {
		token = MOVIDEO.utils.cookie(cookieSessionToken());
		clientId = MOVIDEO.utils.cookie(cookieClientId());
		clientAlias = MOVIDEO.utils.cookie(cookieClientAlias());
	}
	
	/** @private */
	function addSessionToCookie() {
		var options = { expires: 2 };
		MOVIDEO.utils.cookie(cookieSessionToken(),	token,			options);
		MOVIDEO.utils.cookie(cookieClientAlias(),	clientAlias,	options);
		MOVIDEO.utils.cookie(cookieClientId(),		clientId,		options);
		MOVIDEO.utils.cookie(cookieLocation(),		loc,			options);
		MOVIDEO.utils.cookie(cookieIP(),			ip,				options);
	}

	/** @private */
	function removeSessionFromCookie(){
		MOVIDEO.utils.cookie(cookieSessionToken(), null);
		MOVIDEO.utils.cookie(cookieClientAlias(), null);
		MOVIDEO.utils.cookie(cookieClientId(), null);
		MOVIDEO.utils.cookie(cookieLocation(), null);
		MOVIDEO.utils.cookie(cookieIP(), null);
		token = null;
		clientId = null
		clientAlias = null;
		authenticated = false;
		authenticating = false;
	}
	
	function authPrefix() {
		return appAlias + '_' + apiKey;
	}

	function cookieSessionToken() {
	    return appAlias + '_' + apiKey + '_token';
	}
	
	function cookieLocation() {
	    return appAlias + '_' + apiKey + '_loc';
	}
	
	function cookieIP() {
	    return appAlias + '_' + apiKey + '_ip';
	}
	
	function cookieDeliveryProfiles() {
	    return appAlias + '_' + apiKey + '_dp';
	}
	
	function cookieClientAlias() {
	    return appAlias + '_' + apiKey + '_client';
	}
	
	function cookieClientId() {
	    return appAlias + '_' + apiKey + '_id';
	}
	
	var authHandlersByAuthPrefix = {};
	
	function getAuthHandlersFor(authPrefix) {
		var handlers = authHandlersByAuthPrefix[authPrefix];
		if (!handlers) {
			handlers = setAuthHandlersFor(authPrefix, []);
		}
		return handlers;
	}
	
	function setAuthHandlersFor(authPrefix, handlers) {
		handlers = handlers || [];
		authHandlersByAuthPrefix[authPrefix] = handlers;
		return handlers;
	}
	
	function addAuthHandlerFor(authPrefix, authHandler) {
		getAuthHandlersFor(authPrefix).push(authHandler);
	}
	
	function callAuthHandlersFor(authPrefix, context, args) {
		var handlers = getAuthHandlersFor(authPrefix);
		setAuthHandlersFor(authPrefix, []);
		while (handlers[0]) {
			handlers.shift().apply(context, args);
		}
	}
	
	function addAuthHandler(handler) {
		addAuthHandlerFor(authPrefix(), handler);
	}
	
	function callAuthHandlers(args) {
		callAuthHandlersFor(authPrefix(), null, args);
	}
	
	var session = { clear: function() { removeSessionFromCookie() } };
	var devices = (function() {
	
	var browsers = {
		MSIE:		"MSIE",
		Chrome:		"Chrome",
		Firefox:	"Firefox",
		Safari:		"Safari",
		Opera:		"Opera",
		Unknown:	"Unknown"
	};
	
	var types = {
		Android:	"Android",
		iPhone:		"iPhone",
		iPad:		"iPad",
		iPod: 		"iPod", 
		BlackBerry: "BlackBerry",
		Windows:	"Windows",
		Mac:		"Mac",
		Unknown:	"Unknown"
	};
	
	var browserDetectors = [
		[/Chrome/i,	 browsers.Chrome],
		[/Firefox/i, browsers.Firefox],
		[/Safari/i,	 browsers.Safari],
		[/MSIE/i,	 browsers.MSIE],
		[/Opera/i,	browsers.Opera],
		];	
		
	var typeDetectors = [
		[/Android/i, 	types.Android], 
		[/iPhone/i,		types.iPhone],
		[/iPad/i,		types.iPad],
		[/iPod/i,		types.iPod],
		[/Blackberry/i, types.Blackberry],
		[/Windows/i,	types.Windows], 
		[/Mac OS X/i,	types.Mac]
		];
		
	var iosTypes = [ types.iPhone, types.iPad, types.iPod ];
	
	function _detectDevice() {
		var device = {
			agent:	 navigator.userAgent,
			browser: _detectBrowser(),
			type:	 _detectType(),
			version: _detectVersion()
		};
		
		device.isIOS = (jQuery.inArray(device.type, iosTypes) != -1);
		
		return device;
	}
	
	function _detectBrowser() {
		var userAgent = navigator.userAgent;
			
		for (var i = 0, n = browserDetectors.length; i < n; i++) {
			var browserDetector = browserDetectors[i]
			if (browserDetector[0].test(userAgent)) {
				return browserDetector[1];
			}
		}
		
		return browsers.Unknown;
	}
	
	function _detectType() {
		var userAgent = navigator.userAgent;
					   
		for (var i = 0, n = typeDetectors.length; i < n; i++) {
			var typeDetector = typeDetectors[i]
			if (typeDetector[0].test(userAgent)) {
				return typeDetector[1];
			}
		}
		
		return types.Unknown;
	}
	
	function _detectVersion() {
		return $.browser.version;
	}
	
	return {
		browsers: browsers,
		types: types,
		detect: function() {
			return _detectDevice();
		}
	};
}
)();
	var media = (function() {
		
	var omitFields = 'creationDate,lastModifiedDate,copyright,cuePointsExist,isAdvertisement,ratio,creator,tagProfileId,imageFilename,mediaFileExists,syndicated,mediaSchedules,displayStatus,syndicatedPartners,length,tags,filename,status,defaultImage';
	
	function convertToQueryJSONArray(values){
		if (values && values.length > 0) {
			return '["' + values.join('","') + '"]';
		}
		return null;
	}
	
	//
	// 
	//

	var _Deferred = function() {
		var callbacks = [], 
			fired,
			firing,
			cancelled,
			deferred = {
				// done(f1, f2)
				done: function() {
					if (!cancelled) {
						var args = arguments, 
							i, 
							length, 
							elem, 
							type, 
							_fired;
						if (fired) {
							_fired = fired;
							fired = 0;
						}
						for (i = 0, length = args.length; i < length; i++) {
							elem = args[i]
							if (jQuery.isArray(elem)) {
								deferred.done.apply(deferred, elem);
							}
							else if (jQuery.isFunction(elem)) {
								callbacks.push(elem);
							}
						}
						if (_fired) {
							deferred.resolveWith(_fired[0], _fired[1]);
						}
					}
					return this;
				},
				
				resolveWith: function(context, args) {
					if (!cancelled && !fired && !firing) {
						args = args || [];
						firing = 1;
						try {
							while (callbacks[0]) {
								callbacks.shift().apply(context, args);
							}
						}
						finally {
							fired = [context, args];
							firing = 0;
						}
					}
				},
				
				resolve: function() {
					deferred.resolveWith(this, arguments);
					return this;
				},
				
				isResolved: function() {
					return !!(firing || fired);
				},
				
				cancel: function() {
					cancelled = 1;
					callbacks = [];
					return this;
				}
			};
		return deferred;
	}
	
	var promiseMethods = "done fail isResolved isRejected promise then always".split(" ");
	
	var Deferred = function(func) {
		var deferred = _Deferred(), 
			failDeferred = _Deferred();
		jQuery.extend(deferred, {
			
			then: function(doneCallbacks, failCallbacks) {
				deferred.done(doneCallbacks).fail(failCallbacks);
				return this;
			},
			
			always: function() {
				return deferred.done.apply(deferred, arguments).fail.apply(this, arguments);
			},
			
			fail: failDeferred.done, 
			
			rejectWith: failDeferred.resolveWith, 
			
			reject: failDeferred.resolve, 
			
			isRejected: failDeferred.isResolved, 
			
			promise: function(obj) {
				if (obj == null) {
					if (promise) {
						return promise;
					}
					promise = obj = {};
					var i = promiseMethods.length;
					while (i--) {
						obj[promiseMethods[i]] = deferred[promiseMethods[i]]
					}
					return obj;
				}
			}
		});
		
		// make sure only one callback list is used
		deferred.done(failDeferred.cancel).fail(deferred.cancel);
		
		// unexpose cancel
		delete deferred.cancel;
		
		if (func) {
			func.call(deferred, deferred);
		}
		
		return deferred;
	}
	
	//
	//
	//

	var Request = function() {
		this._baseURL = "";
		this._endpoint = "";
		this._options = {};
		this._parameters = new Parameters();
		this._deferred = new Deferred();
		this._completeDeferred = new Deferred();
		this._attempts = 0;
	}
	
	Request.prototype = {
		baseURL: function(value) {
			if (value === undefined) {
				return this._baseURL;
			}
			this._baseURL = value;
			return this;
		},
		
		endpoint: function(value) {
			if (value === undefined) {
				return this._endpoint;
			}
			this._endpoint = value;
			return this;
		},
		
		options: function(value) {
			if (value === undefined) {
				return this._options;
			}
			this._options = value;
			return this;
		},
		
		attempts: function() {
			return this._attempts;
		},
		
		/**
		 * @param {Array} [parameters] Array of Parameters to replace the current parameters with.
		 * @return {Request} This Request if a value is given. 
		 * @return {Array} Array of Parameters if value is undefined
		 */
		parameters: function(value) {
			if (value === undefined) {
				return this._parameters.toArray();
			}
			this._parameters.addAll(value);
			return this;
		},
		/**
		 * @param {String} name					Name of the Parameter to add
		 * @param {*} value						Value of the Parmater to add
		 * @param {Function} [valueConvertor]	
		 */
		addParameter: function(name, value, valueConvertor) {
			this._parameters.add(name, value, valueConvertor)
			return this;
		},
		/**
		 * @param {Array} parameters Array of { name: _, value: _ }
		 */
		addParameters: function(parameters) {
			if (parameters) {
				this._parameters.addAll(parameters);
			}
			return this;
		},
		
		setParameter: function(name, value, valueConvertor) {
			this._parameters.set(name, value, valueConvertor);
			return this;
		},
		
		call:  function() {
			this._attempts++;
			this.setParameter('output', 'json');
			
			var request = this;
			
			// cache the current deferred for the success, error closures. 
			var deferred = this._deferred;
			var completeDeferred = this._completeDeferred;
			
			// reset deferreds in case we have to retry this request
			this._deferred = new Deferred();
			this._completeDeferred = new Deferred();
			
			// make the call
			var url = this.toURL();
			$.jsonp({
				url: url,
				callbackParameter: "callback",
				success: function(result) {
					// ;
					deferred.resolveWith(request, [ result, url, request.options().extras, request ]);
					completeDeferred.resolveWith(request, [ request, "success" ]);
				}, 
				error: function(error) {
					// ;
					deferred.rejectWith(request, [ error, request ]);
					completeDeferred.resolveWith(request, [ request, "error" ]);
				} 
			});
		},
		
		success: function(callbacks) {
			this._deferred.done(callbacks)
			return this;
		},
		
		error: function(callbacks) {
			this._deferred.fail(callbacks);
			return this;
		},
		
		complete: function(callbacks) {
			this._completeDeferred.done(callbacks);
			return this;
		},
		
		toURL: function() {
			var url = this.baseURL() + this.endpoint();
			url += (url.indexOf('?') == -1 ? '?' : '&');
			url += $.param(this.parameters());
			return url;
		},
		
		toObject: function() {
			return {
				request:		this,
				options:		this._options,
				endpoint:		this._endpoint,
				params:			this._parameters.toArray(), 
				handler:		this._options.handler,
				async:			this._options.async,
				extras:			this._options.extras,
				errorHandler:	this._options.errorHandler
			};
		}
	};
	
	var Parameters = function() {
		this._parameters = [];
	}
	
	Parameters.prototype = {
		create: function(name, value, valueConvertor) {
			return { name: name, value: (valueConvertor ? valueConvertor(value) : value) };
		},
		
		add: function(name, value, valueConvertor) {
			if (value !== undefined) {
				this._parameters.push(this.create(name, value, valueConvertor));
			}
			return this;
		},
		
		addAll: function(parameters) {
			if (parameters) {
				this._parameters = this._parameters.concat(parameters);
			}
			return this;
		},
		
		/** Replaces any existing parameter with the same name */
		set: function(name, value, valueConvertor) {
			var parameters = this._parameters.slice();
			for (var i = 0; i < parameters.length; i++) {
				var parameter = parameters[i];
				if (parameter.name == name) {
					parameters.splice(i, 1);
				}
			}
			
			this._parameters = parameters;
			this.add(name, value, valueConvertor);
			return this;
		},
		
		toArray: function() {
			return this._parameters.slice();
		}
	};
	
	var RequestQueue = function() {
		this._queue = [];
		this._paused = false;
	}
	
	RequestQueue.prototype = {
		
		pause: function() {
			this._paused = true;
			return this;
		},
		
		resume: function() {
			this._paused = false;
			this._callNext();
			return this;
		},
		
		isPaused: function() {
			return this._paused;
		},
		
		add: function(request, call) {
			this._queue.push([request, call]);
			this._callNext();
			return this;
		},
		
		/** @private */
		_callNext: function() {
			if (!this._paused) {
				var queued = this._queue.shift();
				if (queued) {
					this._call.apply(this, queued);
				}
			}
		}, 
		
		/** @private */
		_call: function(request, call) {
			var requestQueue = this;
			
			request.complete(function() {
				requestQueue._callNext();
			});
				
			call(request);
		}
	};
	
	var requestQueue = new RequestQueue().pause();
	
	//
	//
	//
	
	return {
		Request: Request,
		Parameters: Parameters,
		_requestQueue: requestQueue,
		
		/** @private */
		createRequest: function() {
			return new MOVIDEO.media.Request();
		},
		
		/** @private */
		createRequestFor: function(endpoint, options, page) {
			var request = MOVIDEO.media.createRequest()
				.endpoint(endpoint)
				.options(options)
				.addParameters(MOVIDEO.media.getDefaultParameters(options))
				.addParameters(page ? MOVIDEO.media.getPagingParameters(page) : null)
				.addParameters(options.params);
				
			if (options && options.handler) {
				request.success(options.handler);
			}
			
			if (options && options.errorHandler) {
				request.error(options.errorHandler);
			}
			
			return request;
		},
		
		/** @private */
		createParameters: function() {
			return new MOVIDEO.media.Parameters();
		},
		
		/** @private */
		getDefaultParameters: function(options) {
			var parameters = MOVIDEO.media.createParameters();
			if (options.omit === undefined || options.omit == true) {
				parameters.add('omitFields', omitFields);
			}
			return parameters.toArray();
		},
		
		/** @private */
		getPagingParameters: function(options) {
			var parameters = MOVIDEO.media.createParameters();
			options = $.extend({ active: true, size: 10, pos: 0 }, options);
			parameters.add('paged',		options.active);
			parameters.add('page',		options.active ? options.pos : undefined);
			parameters.add('pageSize',	options.active ? options.size : undefined);
			return parameters.toArray();
		},
		
		/** @private */
		getIncludeMediaParameters: function(options) {
			var parameters = MOVIDEO.media.createParameters();
			parameters.add('includeMedia', options.media);
			if (options.media) {
				parameters.add('mediaLimit', options.mediaLimit);
			}
			return parameters.toArray();
		},
		
		/** @private */
		callRequest: function(request) {
			// ;
			
			requestQueue.add(request, function(request) {
				// ;
				
				request
					.baseURL(api)
					.setParameter('token', token)
					.error(retryRequest)
					.call();
					
				function retryRequest() { 
					// ;
					
					if (request.attempts() < maxRequestAttempts) {
						MOVIDEO.media.getSession({ ignoreCookie: true });
						MOVIDEO.media._retryRequest(request);
					}
				}
			});
		},
		
		_retryRequest: function(request) {
			// ;
			
			var options = request.options();
			
			if (options && options.handler) {
				request.success(options.handler);
			}
			
			if (options && options.errorHandler) {
				request.error(options.errorHandler);
			}
			
			MOVIDEO.media.callRequest(request);
		},
		
		//
		//
		//
		
		/**
		 * Calls the movideo Media API 
		 *
		 * @param {Object} options Responsible for wrapping all the required field to pass to the media API.
		 * @config {Function} handler Called with the results of the API request.
		 * @config {String} endpoint The endpoint of the REST URL. e.g. media/14690
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @param {Boolean} withoutToken Indicates if the token should be included as a parameter of the request.
		 *
		 * @example
		 *	MOVIDEO.callAPI({
		 *		endpoint: 'media/14690',
		 *		handler: myHandler,
		 *		params: [{name:'omitFields',value:'ratio'}]
		 *	});
		 */
		callAPI: function (options, withoutToken) {
			// ;
			
			var endpoint = options.endpoint;
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options);
			
			requestQueue.add(request, function(request) {
				// ;
				
				if (!withoutToken) {
					request.setParameter('token', token)
				}
				
				request
					.baseURL(api)
					.error(function() { 
						// ;
						
						if (request.attempts() < maxRequestAttempts) {
							MOVIDEO.media.getSession({ ignoreCookie: true });
							MOVIDEO.media._retryRequest(request);
						}
					})
					.call();
			});
		},
		
		getSession: function(options) {
			// ;
			
			options = $.extend({ ignoreCookie: false }, options);

			var hasSessionToken = false;

			if (!options.ignoreCookie) {
				hasSessionToken = hasSessionInCookie();
			}
			
			if (hasSessionToken) {
				setSessionFromCookie();
				
				authenticated = true;
				authenticating = false;
				
				var authData = { 'cookie': true, 'clientId': clientId, 'clientAlias': clientAlias, 'token': token };
				callAuthHandlers([ authData, hasSessionToken ]);
				
				requestQueue.resume();
				return;
			}
			
			if (authenticating) {
				// ;
				return; 
			}
			
			authenticated = false;
			authenticating = true;
			
			options = $.extend({ appAlias: appAlias, apiKey: apiKey }, options);
			
			requestQueue.pause();
			
			var endpoint = "session";
			
			var request = MOVIDEO.media.createRequest();
			
			request
				.baseURL(api)
				.endpoint(endpoint)
				.options(options)
				.addParameter('output', 'json')
				.addParameter('applicationalias', options.appAlias)
				.addParameter('key', options.apiKey)
				.addParameter('includeApplication', false)
				.success(updateSession)
				.success(notifySessionSuccess)
				.error(clearSession)
				.error(notifySessionError)
				.complete(resumeRequestQueue)
				.call();
				
			function updateSession(result) {
				// ;
			
				var session = result.session;
				clientId = session.clientId;
				clientAlias = session.clientId;
				loc = session.location.countryCode + ',' + session.location.regionCode;
				ip = session.ipAddress;
				token = session.token;
				addSessionToCookie();
			}
			
			function clearSession(error) {
				// ;
				
				removeSessionFromCookie();
			}
			
			function notifySessionSuccess(result) {
				// ;
				
				authenticated = true;
				authenticating = false;
				
				callAuthHandlers([ result, true ]);
			}
			
			function notifySessionError(error) {
				// ;
				
				authenticated = false;
				authenticating = false;
				
				callAuthHandlers([ error, false ]);
			}
			
			function resumeRequestQueue() {
				requestQueue.resume();
			}
		},
		
		/**
		 * Gets a Playlist by id. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the playlist.
		 * @config {Boolean} media Indicates if Media should be returned with the Playlist
		 * @config {Integer} mediaLimit Number of Media to return with the Playlist.
		 * @config {Integer} depth Depth of child Playlists and Media to return with the Playlist.
		 * @config {Boolean} includeEmptyPlaylists Indicates if empty child Playlists should be returned with the Playlist.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getPlaylist({
		 * 		id: 123,
		 * 		media: true, 
		 * 		mediaLimit: 100,
		 * 		depth: 2, 
		 * 		includeEmptyPlaylists: false,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getPlaylist: function(options) {
			// ;
			
			var endpoint = "playlist/" + options.id;
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options)
				.addParameters(MOVIDEO.media.getIncludeMediaParameters(options))
				.addParameter('depth', options.depth)
				.addParameter('includeEmptyPlaylists', options.includeEmptyPlaylists);
				
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets the Media for a Playlist by id. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the Playlist.
		 * @config {Integer} mediaLimit Number of Media to return with the Playlist.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getPlaylistMedia({
		 * 		id: 123,
		 * 		mediaLimit: 100,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getPlaylistMedia: function(options) {
			// ;
			
			var endpoint = "playlist/" + options.id + "/media";
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options)
				.addParameter('mediaLimit', options.mediaLimit);
				
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets the root Playlist of the given Playlist id. The root playlist is determined by traversing
		 * up the parents of a child Playlist to find the first parent Playlist without its own parent. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the Playlist.
		 * @config {Integer} depth Depth of child Playlists and Media to return with the Playlist.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getFirstRootPlaylistForPlaylist({
		 * 		id: 123,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getFirstRootPlaylistForPlaylist: function(options) {
			// ;
			
			var endpoint = 'playlist/' + options.id + '/firstRootPlaylist';
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options)
				.addParameters(MOVIDEO.media.getIncludeMediaParameters(options))
				.addParameter('depth', options.depth);
				
			MOVIDEO.media.callRequest(request);
		},
	
		/**
		 * Gets the root Playlist of the given Media id. The root playlist is determined by traversing
		 * up the parents of a child Playlist to find the first parent Playlist without its own parent. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the Media.
		 * @config {Integer} depth Depth of child Playlists and Media to return with the Playlist.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getFirstRootPlaylistForPlaylist({
		 * 		id: 123,
		 * 		depth: 2,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getFirstRootPlaylistForMedia: function(options) {
			// ;
			
			var endpoint = 'playlist/firstRootPlaylist';
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options)
				.addParameters(MOVIDEO.media.getIncludeMediaParameters(options))
				.addParameter('depth', options.depth)
				.addParameter('mediaId', options.id);
				
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets a Channel by id. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the Channel.
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getChannel({
		 * 		id: 30762,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getChannel: function(options) {
			// ;
			
			var endpoint = 'channel/' + options.id;
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options)
				.addParameters(MOVIDEO.media.getIncludeMediaParameters(options));
				
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Searches for tags matching: 
		 * <ul>
		 * <li>A namespace,</li>
		 * <li>A namespace and predicate,</li>
		 * <li>A namespace, predicate and value.</li>
		 * </ul>
		 * 
		 * @param {Object} options Options for the request.
		 * @config {String} tag The machine tag to search for this can be a part of or a full machine tag. e.g. "nasa", "nasa:topic", "nasa:topic=Launch"
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @param {Object} [page] Responsible for controlling paging through result sets. Uses default values below when <code>undefined</code>. 
		 * @config {Boolean} [active=true] If false all results are returned (this is not advised as large result set will impact performace.
		 * @config {Integer} [pos=0] The page position in the result set that will be returned.
		 * @config {Integer} [size=10] The size of the page.
		 * 
		 * @see <a href="http://www.flickr.com/groups/api/discuss/72157594497877875/" target="_blank">Flickr Introduction to Machine Tags</a>.
		 * 
		 * @example
		 * 	MOVIDEO.searchTags({
		 * 		tag: 'nasa:topic',
		 * 		handler: function(result) {
		 * 			
		 * 		}
		 * 	});
		 */
		searchTags: function(options, page) {
			// ;
			
			var endpoint = "tag/search";
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options, page)
				.addParameter('tag', options.tag);
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Searches for Media matching the given criteria.
		 * 
		 * @param {Object} options Options for the request.
		 * @config {String[]} [tags] An Array of machine tags used to search media e.g.["nasa:topic=launch"] 
		 * @config {String} [keyword] Keyword used to search for against Title and/or Description. 
		 * @config {String} [title] Keyword used to search for against Title only. 
		 * @config {String} [description] Keyword used to search for against Description only. 
		 * @config {String} [orderBy] The following are valid Order By fields: title,description,creationdate
		 * @config {String} [orderDesc] If to order by Descending Order. Default is false
		 * @config {String} [mediaType] Type of media to return. Possible values: (AUDIO, VIDEO). Default is all types returned.
		 * @config {String[]} [tagProfiles] Tag Profile names to only be included in the search results. e.g. ["NASA"]
		 * @config {Boolean} [totals] If true only the totals of the results are returned. Default is false.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async] If false blocks the browser while the requests is active. Default is true.
		 * 
		 * @param {Object} [page] Responsible for controlling paging through result sets. Uses default values below when <code>undefined</code>. 
		 * @config {Boolean} [active=true] If false all results are returned (This is not advised as large result set will impact performace.)
		 * @config {Integer} [pos=0] The page position in the result set that will be returned.
		 * @config {Integer} [size=10] The size of the page.
		 *
		 * @example
		 * MOVIDEO.searchMedia({
		 *	tag: 'nasa:topic',
		 *	handler: myHandler
		 *	});
		 */
		searchMedia: function(options, page) {
			// ;
			
			var endpoint = "media/search";
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options, page)
				.addParameter('tags',				options.tags)
				.addParameter('keywords',			options.keywords, convertToQueryJSONArray)
				.addParameter('keywordOperator',	options.keywordOperator)
				.addParameter('excludeTags',		options.excludeTags)
				.addParameter('excludeOperator',	options.excludeOperator)
				.addParameter('operator',			options.operator)
				.addParameter('title',				options.title)
				.addParameter('description',		options.description)
				.addParameter('orderBy',			options.orderBy)
				.addParameter('orderDesc',			options.orderDesc)
				.addParameter('tagProfiles',		options.tagProfiles)
				.addParameter('mediaType',			options.mediaType)
				.addParameter('totals',				options.totals);
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Searches for Playlists matching the given criteria.
		 * 
		 * @param {Object} options Options for the request.
		 * @config {String[]} [tags] An Array of machine tags used to search media e.g.["nasa:topic=launch"] 
		 * @config {String} [keywords] Keywords used to search for against Title and/or Description. 
		 * @config {String} [title] Keyword used to search for against Title only.
		 * @config {String} [description] Keyword used to search for against Description only.	
		 * @config {String} [orderBy] The following are valid Order By fields: title,description,creationdate
		 * @config {Boolean} [orderDesc] If to order by Descending Order. Default is false
		 * @config {String[]} [tagProfiles] Tag Profile names to only be included in the search results. e.g. ["NASA"]
		 * @config {Boolean} [media] If true media is returned whith playlist in the results. Default is false.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully. 
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @param {Object} [page] Responsible for controlling paging through result sets. Uses default values below when <code>undefined</code>. 
		 * @config {Boolean} [active=true] If false all results are returned (This is not advised as large result set will impact performace.)
		 * @config {Integer} [pos=0] The page position in the result set that will be returned.
		 * @config {Integer} [size=10] The size of the page.
		 *
		 * @example
		 * 	MOVIDEO.searchPlaylists({
		 * 		tag: 'nasa:topic',
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		searchPlaylists: function(options, page){
			// ;
			
			var endpoint = "playlist/search/";
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options, page)
				.addParameters(MOVIDEO.media.getIncludeMediaParameters(options))
				.addParameter('tags',			options.tags)
				.addParameter('keywords',		options.keywords, convertToQueryJSONArray)
				.addParameter('title',			options.title)
				.addParameter('description',	options.description)
				.addParameter('orderBy',		options.orderBy)
				.addParameter('orderDesc',		options.orderDesc)
				.addParameter('tagProfiles',	options.tagProfiles);
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets a Media by id.
		 *
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the media item.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully.
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getMedia({
		 * 		id: 30762,
		 * 		handler: function(result) { 
		 * 			;
		 * 		}
		 * 	});
		 */
		getMedia: function(options) {
			// ;
			
			var endpoint = "media/" + options.id;
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options);
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets Media related to the given Media id.
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id The id of the media item.
		 * @config {String} [mediaType] Type of media to return. Possible values: (AUDIO, VIDEO). Default is all types returned.
		 * @config {String[]} [excludeTags] Tags that are to be excluded from related search. These can also be in the form of Namespace:Predicate only and a match will be performed on the Media to compare object to form the full tag.
		 * @config {Array[]} [params] An array of name value pair objects that are used to form the parameters of a URL Query String e.g. [{ name: 'omitFields', value:'ratio' },{ name: 'tag', value: 'nasa:topic=launch' }]
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API Call this allows you to handle it gracefully.
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @param {Object} [page] Responsible for controlling paging through result sets. If not present see defaults of fields. 
		 * @config {Boolean} [active=true] If false all results are returned (this is not advised as large result set will impact performace.
		 * @config {Integer} [pos=0] The page position in the result set that will be returned.
		 * @config {Integer} [size=10] The size of the page.
		 * 
		 * @example
		 * 	MOVIDEO.getRelatedMedia({
		 * 		id: 1234,
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getRelatedMedia: function(options, page) {
			// ;
			
			var endpoint = "media/related/" + options.id;
			
			var request = MOVIDEO.media.createRequestFor(endpoint, options, page)
				.addParameter('mediaType', options.mediaType)
				.addParameter('excludeTags', options.excludeTags);
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets SMIL data for a Media by id. 
		 * 
		 * @param {Object} options Options for the request.
		 * @config {Integer} id Media id.
		 * @config {Function} handler Called with the results of the API request.
		 * @config {Function} [errorHandler] If an error is returned from the API request this allows you to handle it gracefully.
		 * @config {Boolean} [async=true] If false blocks the browser while the requests is active.
		 * 
		 * @example
		 * 	MOVIDEO.getSMIL({
		 * 		id: 123
		 * 		handler: function(result) {
		 * 			;
		 * 		}
		 * 	});
		 */
		getSMIL: function(options) {
			// ;
			
			var endpoint = "media/" + options.id + "/smil";
			
			var request = MOVIDEO.media.createRequestFor(endpoint, $.extend(options, { omit: false }))
			
			MOVIDEO.media.callRequest(request);
		},
		
		/**
		 * Gets the field names that will be omitted from responses for calls to Media API functions with 'omit: true'.
		 * 
		 * @returns {String[]}
		 */
		getOmittedFields: function() {
			return omitFields.split(',');
		},
		
		/**
		 * Sets the field names that will be omitted from responses for calls to Media API functions with 'omit: true'.
		 * 
		 * @param {String[]} values 
		 * 
		 * @example
		 * 	MOVIDEO.media.setOmittedFields([ 'creationDate', 'lastModifiedDate', 'mediaSchedules', 'cuePoints' ]);
		 */
		setOmittedFields: function(values) {
			if (values) {
				omitFields = values.join(",");
			}
		}
	};
}
)();
	var ads = (function() {

	var adConfig;
	var proxyURL= "/ads";
	var initialArray;
	var initialIndex = 0;
	var recurringArray;
	var recurringIndex = 0;
	var companionAdLocations=[];
	var companionAdResourceHandlers = {
		// for html resources, replace the innerHTML with the resource. 
		// NOTE does not support <script> elements in the HTML resource.
		'html': function(companionAd, adLocation){ 
			adLocation.html(companionAd.resource);
		},
		// for iframe resources, simply change the IFrameElement.src to point to the new URL.
		'iframe': function(companionAd, adLocation){
			adLocation.html('<iframe src="' + companionAd.resource + '" style="width:' + companionAd.width + 'px,height:' + companionAd.height + 'px" />'); 
		},
		// for static resources, create HTML to wrap the resource, and inject to DOM. 
		'static': function(companionAd, adLocation){
			// handle images resource by creating an <a> link with an <img />
			// matches creativeTypes such as: image/jpeg, image/gif
			if (companionAd.creativeType.indexOf('image') == 0) {
				var html 
					= '<a href="' + companionAd.clickThroughUrl + '" target="_blank">' 
					+ '<img border="0" src="' + companionAd.resource + '" width="' + companionAd.width 
					+ '" height="' + companionAd.height	+ '" /></a>';
				adLocation.html(html);
			}
			// handles SWFs by embedding in the ad container
			else if (companionAd.creativeType == 'application/x-shockwave-flash') {
				//swfobject.embedSWF(companionAd.resource + '?clickTAG=' + companionAd.clickThroughUrl,adLocation.attr("id"), companionAd.width, companionAd.height, '9.0.0');
				$(adLocation).flash({
					src: companionAd.resource + '?clickTAG=' + companionAd.clickThroughUrl,
					width: companionAd.width,
					height: companionAd.height
				});
			}
		}
	};
	
	function postTrackingEvent(id,event){
		var videoAd = getVideoAd(id);
		event =  event.toLowerCase();
		if (videoAd!=undefined){
			for (var i = 0; i < videoAd.trackingEvents.length; i++){
				var eventObj = videoAd.trackingEvents[i];
				if (eventObj.name == event){
					post(eventObj.url);
					break;
				}
			}
		}
	}

	function executeVideoClickEvent(id){
		var videoAd = getVideoAd(id);
		for (var i = 0; i < videoAd.trackingUrls.length; i++) {
			post(videoAd.trackingUrls[i]);	
		}
		window.open(videoAd.clickThroughUrl);
	}
	
	function executeImpressionEvent(id){
		var videoAd = getVideoAd(id);
		for (var i = 0; i < videoAd.impressionUrls.length; i++) {
			post(videoAd.impressionUrls[i]);	
		}
	}
	
	function createVideoAdVAST1(id){
		var ad = $.data(document.body, 'movideo.ui.player.'+id+'.ad');
		var videoAd = new Object;
		videoAd.version = '1.0';
		videoAd.url = ad['Ad'][0]['InLine'][0]['Video'][0]['MediaFiles'][0]['MediaFile'][0]['URL'][0]['Text'];
		videoAd.clickThroughUrl = ad['Ad'][0]['InLine'][0]['Video'][0]['VideoClicks'][0]['ClickThrough'][0]['URL'][0]['Text']; 
		videoAd.impressionUrls = [];
		var impressionUrls = ad['Ad'][0]['InLine'][0]['Impression'][0]['URL'];
		
		for (var i = 0; i < impressionUrls.length; i++) {
			videoAd.impressionUrls.push(impressionUrls[i]['Text']);
		}

		videoAd.trackingUrls = [];
		if (ad['Ad'][0]['InLine'][0]['Video'][0]['VideoClicks'][0]['ClickTracking']!==undefined){
			var trackingUrls =  ad['Ad'][0]['InLine'][0]['Video'][0]['VideoClicks'][0]['ClickTracking'][0]['URL'] || [];
			for (var i = 0; i < trackingUrls.length; i++) {
				videoAd.trackingUrls.push(trackingUrls[i]['Text']);
			}
		}
	
		videoAd.trackingEvents = [];
		var events  = ad['Ad'][0]['InLine'][0]['TrackingEvents'][0] || [];
		for (var i = 0; i < events.length; i++){
			var eventObj =events[i];
			videoAd.trackingEvents.push(buildEvent(eventObj.event,eventObj['Text']));
		}

		videoAd.companionAds =[];
		var companion  = ad['Ad'][0]['InLine'][0]['CompanionAds'][0]['Companion'] || [];
		for (var i = 0; i < companion.length; i++){
			var companionObj = companion[i];
			var id = companionObj['id'];
			var width = companionObj['width'];
			var height = companionObj['height'];
			var creativeType = companionObj['creativeType'];
			
			if (width == undefined || width == 0) {
				width = companionObj['expandedWidth'];
			}
			
			if (height == undefined || height == 0) {
				height = companionObj['expandedHeight'];
			}
			
			var resource = companionObj['URL'][0]['Text'];
			var resourceType = companionObj['resourceType'];
			var clickThroughUrl = companionObj['CompanionClickThrough'][0]['URL'][0]['Text']; 
			var altText = '';
			var parameters = '';
			videoAd.companionAds.push(buildCompanionAds(id,width,height,resource,resourceType,creativeType,clickThroughUrl,altText,parameters));
		}
		$.data(document.body, 'movideo.ui.player.' + id + '.ad.video', videoAd);
		return videoAd;
	}
	
	function createVideoAdVAST2(id){
		var ad = $.data(document.body, 'movideo.ui.player.'+id+'.ad');
		var videoAd = new Object;
		videoAd.version = ad['version'];
		videoAd.url= ad['Ad'][0]['InLine'][0]['Creatives'][0]['Creative'][0]['Linear'][0]['MediaFiles'][0]['MediaFile'][0]['Text'];
		videoAd.clickThroughUrl = ad['Ad'][0]['InLine'][0]['Creatives'][0]['Creative'][0]['Linear'][0]['VideoClicks'][0]['ClickThrough'][0]['Text']; 

		videoAd.impressionUrls =[];
		var impressionUrls = ad['Ad'][0]['InLine'][0]['Impression'] || [];
		for ( var i = 0; i < impressionUrls.length; i++) {
			videoAd.impressionUrls.push(impressionUrls[i]['Text']);
		}
		
		videoAd.trackingUrls =[];
		var trackingUrls = ad['Ad'][0]['InLine'][0]['Creatives'][0]['Creative'][0]['Linear'][0]['VideoClicks'][0]['ClickTracking'] || []; 
		for ( var i = 0; i < trackingUrls.length; i++) {
			videoAd.trackingUrls.push(trackingUrls[i]['Text']);
		}
		
		videoAd.trackingEvents =[];
		var events  = ad['Ad'][0]['InLine'][0]['Creatives'][0]['Creative'][0]['Linear'][0]['TrackingEvents'][0]['Tracking'] || [];
		for ( var i = 0; i <events.length; i++){
			var eventObj =events[i];
			videoAd.trackingEvents.push(buildEvent(eventObj.event,eventObj['Text']));
		}

		videoAd.companionAds =[];
		var companion  = ad['Ad'][0]['InLine'][0]['Creatives'][0]['Creative'][1]['CompanionAds'][0]['Companion'] || [];
		for ( var i = 0; i <companion.length; i++){
			var companionObj =companion[i];
			var id= companionObj['id'];
			var width= companionObj['width'];
			var height=companionObj['height'];
			if(width==undefined ||width==0){
				width= companionObj['expandedWidth'];	
			}
			if(height==undefined ||height==0){
				height=companionObj['expandedHeight'];	
			}
			var resource;
			var resourceType;
			var creativeType="";
			if(companionObj['StaticResource']){
				resource=companionObj['StaticResource'][0]['Text'];
				resourceType="static";		
				creativeType=companionObj['StaticResource'][0]['creativeType'];
			}
			if(companionObj['IFrameResource']){
				resource=companionObj['IFrameResource'][0]['Text'];
				resourceType='iframe';
			}
			if(companionObj['HTMLResource']){
				resource=companionObj['HTMLResource'][0]['Text'];
				resourceType='html';
			}
			var clickThroughUrl=companionObj['CompanionClickThrough'][0]['Text'];
		
			var altText=companionObj['AltText'][0]['Text'];
			var parameters=companionObj['AdParameters'][0]['Text'];
			videoAd.companionAds.push(buildCompanionAds(id,width,height,resource,resourceType,creativeType,clickThroughUrl,altText,parameters));
		}
		$.data(document.body, 'movideo.ui.player.'+id+'.ad.video',videoAd);
		return videoAd;
	}
	
	function buildEvent(name,url){
		var event = new Object;
		event.name = name.toLowerCase();
		event.url = url;
		return event;
	}
	
	function renderCompanionAds(companionAds){
		;
		for (var i = 0, n = companionAdLocations.length; i < n; i++) {
			var ad = companionAdLocations[i];
			;
			var companionAd = findCompanionAdId(companionAds,ad.attr("id"));
			
			if (companionAd == undefined) {
				companionAd = findCompanionAdHW(companionAds, parseInt(ad.width()), parseInt(ad.height()));
			}
			
			if (companionAd) {
				var resourceHandler = companionAdResourceHandlers[companionAd.resourceType];
				if (resourceHandler) {
					resourceHandler(companionAd, ad);
				}else{
					;
				}                                               
				ad.change();
			}else{
				;
			}
		}
	}
	
	function findCompanionAdHW(companionAds, width, height) {
		for (var i in companionAds) {
			var companionAd = companionAds[i];
			if (companionAd.width == width && companionAd.height == height) {
				return companionAd;
			}
		}
		return null;
	}
	
	function findCompanionAdId(companionAds, id) {
		for (var i in companionAds) {
			var companionAd = companionAds[i];
			if (companionAd.id == id) {
				return companionAd;
			}
		}
		return null;
	}
	
	function buildCompanionAds(id,width,height,resource,resourceType,creativeType,clickThroughUrl,altText,adParams){
		var compAd = new Object;
		compAd.id= id;
		compAd.resource = resource;
		compAd.resourceType = resourceType;
		compAd.creativeType = creativeType;
		compAd.clickThroughUrl = clickThroughUrl;
		compAd.width = width;
		compAd.height = height;
	
		if (altText != undefined) {
			compAd.altText = altText;
		}
		else {
			compAd.altText = '';
		}
		
		if (adParams != undefined) {
			compAd.parameters = adParams;
		}
		else {
			compAd.parameters = '';
		}
		
		return compAd;
	}
	
	function getAdVersion(id) {
		var ad = $.data(document.body, 'movideo.ui.player.' + id + '.ad');
		var version;
		if (ad == undefined) {
			version = "missing";
		}
		else {
			version = ad['version'];
		}
		
		if (version == undefined && ad['RootName'] == "VideoAdServingTemplate") {
			version = '1.0';	
		}
	//	;
		return version;
	}
	
	function getVideoAd(id){
		var videoAd = $.data(document.body, 'movideo.ui.player.'+id+'.ad.video');
		if (videoAd == undefined && $.data(document.body, 'movideo.ui.player.'+id+'.ad') != undefined) {
			var version = getAdVersion(id);
			if (version == "2.0") {
				videoAd = createVideoAdVAST2(id);	
			}
			else if (version == "1.0") {
				videoAd = createVideoAdVAST1(id);	
			}
			else {
				;
			}
		}
		return videoAd;
	}
	
	function post(url) {
		$("#movideo.ad.bi").detach();
		$('body').append('<img id="movideo.ad.bi" src="'+url+'"/>');	
	}

 	return {
		setConfig:function(_adConfig) {
			//;
			if (_adConfig != undefined){
				adConfig = _adConfig;
				adConfig.path = adConfig.url.replace(/http:\/\/[^\/]+/i,""); 
				if (adConfig.proxyPath == null){
					adConfig.url = proxyURL+ adConfig.path;
				}
				else{
					adConfig.url = adConfig.proxyPath + adConfig.path;
				}
				
				// ;
				
				if (adConfig.advertisingPolicy.reoccurringMedia !== undefined) {
					// yes, recurring is spelt wrong in the API xml...
					recurringArray = adConfig.advertisingPolicy.reoccurringMedia.split(",");
				}
				
				if (adConfig.advertisingPolicy.initialMedia !== undefined) {
					initialArray = adConfig.advertisingPolicy.initialMedia.split(",");
				}
			}
		},
		hasAdNext: function() {
			if (adConfig == null) {
			} else if (initialArray !== undefined && initialIndex < initialArray.length) {
				// If there is an initial array, count up to its length
				return initialArray[initialIndex++] == "a";
			} else if (recurringArray !== undefined && recurringArray.length > 0) {
				// If there is a recurring array, loop back to 0 when length is reached
				recurringIndex %= recurringArray.length;
				//;
				return recurringArray[recurringIndex++] == "a";
			}
			return false;
		},
		start: function(id) {
			postTrackingEvent(id,"start");
		},
		midpoint: function(id) {
			postTrackingEvent(id,"midpoint");
		},
		firstQuartile: function(id) {
			postTrackingEvent(id,"firstQuartile");
		},
		thirdQuartile: function(id) {
			postTrackingEvent(id,"thirdQuartile");
		},
		unmute: function(id) {
			postTrackingEvent(id,"unmute");
		},
		complete: function(id) {
			postTrackingEvent(id,"complete");
		},
		mute: function(id) {
			postTrackingEvent(id,"mute");
		},
		pause: function(id) {
			postTrackingEvent(id,"pause");
		},
		click: function(id) {
			executeVideoClickEvent(id);
		},
		impression: function(id) {
			executeImpressionEvent(id);
		},
		fullscreen: function(id) {
			postTrackingEvent(id,"fullscreen");
		},
		getVideoAd: function(id) {
			return getVideoAd(id);
		},
		addCompanionAdLocation: function(id) {
			companionAdLocations.push($("#"+id));
		},
		setCompanionAdLocations: function(ads) {
			;
			for ( var i = 0; i < ads.length; i++) {
				companionAdLocations.push($("#"+ads[i]));
			}
		},
		updateCompanionAds: function(companionAds) {
			;
			renderCompanionAds(companionAds);
		},
		callAd: function(id, adURLPreProcesser, handler) {
			var strURL;
			if (adURLPreProcesser == undefined || adURLPreProcesser == null) {
				strURL = adConfig.url;
			}
			else {
				strURL = adURLPreProcesser(adConfig.url);
			}
			;
			
			if (strURL == null) {
				if (handler != null) {
					handler(null);
				}
				else {
					return null;
				}
			}
			
			$.ajax({
				async:true,
				url: strURL,
				complete: function(xml) {
					var dom = $.textToXML(xml.responseText);
					var json = $.xmlToJSON(dom);
					;
					$.data(document.body, 'movideo.ui.player.' + id + '.ad', json);
					var videoAd = getVideoAd(id);
					if (videoAd) {
						if (handler != null) {
							handler(videoAd);
						}
						renderCompanionAds(videoAd.companionAds);
					}
					else {
						;
						if (handler != null) {
							handler(null);
						}
					}
				},
				dataType: 'xml'
			});
		}
	}
}
)();
	var bi = (function() {
	var dataCaptureDomain = "http://capture.camify.com";
	var dataCapturePath = "/dc?";
	var dataCaptureExitPath = "/x?";
	var viewerKey;
	var eventHandlers=[];
	var last_post_node;
	var last_poll_node;
	var data=[];
	var previousContent=[];
	
	/** @private */
	function getViewerKey(){
		if(viewerKey!=null){
			return 	viewerKey;
		}
		else{
			viewerKey = MOVIDEO.utils.cookie(cookieViewerKey());
		}
		if(viewerKey==null){
			addViewerKey();
			viewerKey = MOVIDEO.utils.cookie(cookieViewerKey());
			// ;
		}
		return viewerKey;
	}
	
	function addViewerKey(){
		return MOVIDEO.utils.cookie(cookieViewerKey(),MOVIDEO.utils.generateGuid(),{expires:365});
	}

	/** @private */
	function removeViewerKey(){
		 MOVIDEO.utils.cookie(cookieViewerKey(),null);
	}
	
	/** @private */
	function cookiePreviousAppKey(){
		return 'previous_applicationid';
	}
	
	/** @private */
	function cookiePreviousContentTypeKey(){
		return 'previous_content_type';
	}
	
	/** @private */
	function cookieViewerKey(){
		return 'camify';
	}
	
	function poll(){
		var sc = document.createElement('script');
		var headId ="movideo.bi.capture.poll"; 
		sc.type = 'text/javascript';
		sc.id = headId;
		sc.src = url;
		var head = document.getElementsByTagName('head')[0];	
		if(last_poll_node){
			head.removeChild(last_poll_node);
		}
		head.appendChild(sc);
		last_poll_node = sc;
	}
	
	function post(url){
		var sc = document.createElement('script');
		var headId ="movideo.bi.capture"; 
		sc.type = 'text/javascript';
		sc.id = headId;
		sc.src = url;
		var head = document.getElementsByTagName('head')[0];	
		if(last_post_node){
			head.removeChild(last_post_node);
		}
		head.appendChild(sc);
		last_post_node = sc;
	}
	
	function send(track){
		// ;
		if(track!==undefined && track!=null){
				//always expected
				var paramList="u="+encodeURI(track.location)+"&"+"k="+encodeURI(track.key)+"&"+"ct="+encodeURI(track.type)+"&"+"oid="+encodeURI(track.ownerId);
				paramList=paramList+"&"+"aid="+encodeURI(track.appAlias)+"&"+"clid="+encodeURI(track.clientId);
				
				if(track.height!=undefined){
					paramList=paramList+"&h="+encodeURI(track.height);
				}
				if(track.width!=undefined){
					paramList=paramList+"&w="+encodeURI(track.width);		
				}
				if(track.duration!=undefined){
					paramList=paramList+"&to="+encodeURI(track.duration);				
				}
				if(track.title!=undefined){
					paramList=paramList+"&t="+encodeURI(track.title);
				}
				if(track.mediaId!=undefined){
				paramList=paramList+"&"+"mid="+encodeURI(track.mediaId)+"&"+"mi="+encodeURI(track.image);
				}
				if(track.referrer!=undefined){
					paramList=paramList+"&"+"r="+encodeURI(track.referrer);
				}
				if(track.playlistId!=undefined){
					paramList=paramList+"&"+"pid="+encodeURI(track.playlistId);
				}
				if(track.channelId!=undefined){
					paramList=paramList+"&"+"cid="+encodeURI(track.channelId); 
				}
				if(track.event!=null){
					paramList=paramList+"&e="+encodeURI(track.event);
					if(track.description!=null){
						paramList=paramList+"&ede="+encodeURI(track.description);
					}	
				}
				else if(track.tags!=undefined) {
					paramList=paramList+"&"+"tags="+encodeURI(track.tags); 
				}
				
				paramList=paramList+'&rx='+Math.random();
				for (var i = 0; i < eventHandlers.length; i++) {
					eventHandlers[i](track);
				}
				track.url =dataCaptureDomain+dataCapturePath+paramList;
				post(track.url);
			}
	}

	function decorateTrack(track,width,height,duration,media,playlist,channel){
		// ;
		var oldTrack = getTrack(track.id);
		// ;
		if(track.location==undefined&&document.location!==undefined&&document.location!==null){
			track.location = document.location.toString();	
		}
		if(track.referrer==undefined&&document.referrer!==undefined&&document.referrer!==null){
			track.referrer=document.referrer;
		}
			if(oldTrack.type!=undefined){track.type = oldTrack.type;}
			if(oldTrack.duration!=undefined&&track.duration==undefined){track.duration = oldTrack.duration;}
			if(oldTrack.ownerId!=undefined&&track.ownerId==undefined){track.ownerId = oldTrack.ownerId;}
			if(oldTrack.tags!=undefined&&track.tags==undefined){track.tags = oldTrack.tags;}
			if(oldTrack.image!=undefined&&track.image==undefined){track.image = oldTrack.image;}
			if(oldTrack.clientId!=undefined&&track.clientId==undefined){track.clientId = oldTrack.clientId;}
			if(oldTrack.appAlias!=undefined&&track.appAlias==undefined){track.appAlias = oldTrack.appAlias;}
			if(oldTrack.title!=undefined&&track.title==undefined){track.title = oldTrack.title;}
			if(oldTrack.key!=undefined&&track.key==undefined){track.key = oldTrack.key;}
			if(oldTrack.mediaId!=undefined&&track.mediaId==undefined){track.mediaId = oldTrack.mediaId;}
			if(oldTrack.playlistId!=undefined&&track.playlistId==undefined){track.playlistId = oldTrack.playlistId;}
			if(oldTrack.channelId!=undefined&&track.channelId==undefined){track.channelId = oldTrack.channelId;}
		
			if(media!=undefined){
				// ;
				track.title = media.title;
				track.mediaId =media.id;
				track.type = media.mediaType;
				track.image = media.imagePath+"100x56.png";
				track.ownerId = media.client.id; 
				var tagArray = MOVIDEO.utils.getMachineTagArray(media.tags);
				var length = tagArray.length;
				var tags ="[";
				var i;
				for(i=0; i<length; i++){ 
					tags = tags+'"'+ tagArray[i]+ '"'; 
					if((i+1)<length){tags = tags+',';}
				}
				track.tags = tags+"]";
			}	
			if(playlist!=undefined){
				track.playlistId =playlist.id;
			}
			if(channel!=undefined){
				track.channelId = channel.id;
			}
			if(width!=undefined){
				track.width = width;
			}
			if(height!=undefined){
				track.height = height;
			}
			if(duration!=undefined){
				track.duration = duration;
			}
			// ;
			return updateTrack(track);
	}
	
	function createTrack(id,clientId,appAlias,key,type,location){
		var track = new Object;
		track.id=id;
		track.type=type;
		if(location!=null){
			track.location = location;
		}
		track.appAlias = appAlias;
		track.clientId = clientId;
		track.ownerId = clientId;
		if(key==null||key==undefined){
			track.key = getViewerKey();
		}else{
			track.key = key;
		}
		if(track.location==undefined&&document.location!==undefined&&document.location!==null){
			track.location = document.location.toString();	
		}
		if(track.referrer==undefined&&document.referrer!==undefined&&document.referrer!==null){
			track.referrer=document.referrer;
		}
		// ;
		return track;
	}

	function getTrack(id){
		return data[id];
	}
	
	function updateTrack(track){
		// ;
		return data[track.id]=track;
	}
	
	return {
		addEventHander:function(handler){
			eventHandlers.push(handler);
		},
		removeEventHander:function(handler){
			var i = eventHandlers.indexOf(handler);
			eventHandlers.splice(i);
		},
		closeSession:function(track){
			track = decorateTrack(track);
			post(dataCaptureDomain+dataCaptureExitPath+"u="+track.location+"&k="+track.key);
		},
		createSession:function(id,client,app,key,type,location,poll){
			var track = createTrack(id,client,app,key,type,location);
			track = decorateTrack(updateTrack(track));
			send(track);
			if(poll!==undefined&&poll==true){
				setInterval(function() {
					var track = createTrack(id,client,app,key,type,location);
					track.event="Poll";
					send(track);
				}, 20000);
			}
			return track;
		},
		loadVideoAd:function(track,width,height,duration){
			track = decorateTrack(track,width,height,duration);
			track.type="ad";
			track.title="";
			track.mediaId="";
			return track;
		},
		loadVideo:function(track,width,height,duration,media,playlist,channel){
			track = decorateTrack(track,width,height,duration,media,playlist,channel);
			return track;
		},
		play:function(track,bitrate){
			track.event = "Play";
			track.description = bitrate;
			track = decorateTrack(track);
			return send(track);
		},
		end:function(track){
			track.event = "End";
			track.description ="";
			track = decorateTrack(track);
			return send(track);
		},
		bitrateChange:function(track,bitrate){
			track.event ="BitrateChange";
			track.description =bitrate;
			track = decorateTrack(track);
			return send(track);
		},
		click:function(track,description){
			track.event ="Click";
			track.description =description;
			track = decorateTrack(track);
			return send(track);
		},
		event:function(track,event,description){
			track.event =event;
			track.description =description;
			track = decorateTrack(track);
			return send(track);
		},
		pause:function(track){
			track.event="Pause";
			track.description ="";
			track = decorateTrack(track);
			return send(track);
		},
		error:function(track,description){
			track.event="Error";
			track.description =description;
			track = decorateTrack(track);
			return send(track);
		},
		resume:function(track){
			track.event="Resume";
			track.description ="";
			track = decorateTrack(track);
			return send(track);
		},
		viewed:function(track,percent){
			track.event=percent+"% Complete";
			track.description ="";
			track = decorateTrack(track);
			return send(track);
		}				
	};
}
)();
	var utils = (function() {
	return {
		getQueryString: function() {
			return location.search.substring(1, location.search.length);
		},
		getQueryStringParameter : function(parameter) {
			var loc = MOVIDEO.utils.getQueryString();
			var param_value = false;
			var params = loc.split("&");
			for (i = 0; i < params.length; i++) {
				param_name = params[i].substring(0, params[i].indexOf('='));
				if (param_name == parameter) {
					param_value = params[i].substring(params[i].indexOf('=') + 1);
				}
			}
			if (param_value) {
				return param_value;
			} else {
				return null;
			}
		},
		getLocation : function() {
			return MOVIDEO.utils.cookie(cookieLocation());
		},
		getAPIKey : function() {
			return apiKey;
		},
		getApplicationAlias : function() {
			return appAlias;
		},
		getAccount : function() {
			return MOVIDEO.utils.cookie(cookieClientAlias());
		},
		getAccountId : function() {
			return MOVIDEO.utils.cookie(cookieClientId());
		},
		getToken : function() {
			return MOVIDEO.utils.cookie(cookieSessionKey());
		},
		getIP : function() {
			return MOVIDEO.utils.cookie(cookieIP());
		},
		getMediaURL : function(options) {
			var defaults = {
				url : location.href,
				mediaId : '',
				playlistId : ''
			};

			if (options !== undefined) {
				if (options.url !== undefined) {
					defaults.url = options.url;
				}
				if (options.mediaId !== undefined) {
					defaults.mediaId = options.mediaId;
				}
				if (options.playlistId !== undefined) {
					defaults.playlistId = options.playlistId;
				}
			}
			var m = defaults.url.match(/((s?ftp|https?):\/\/)?([^\/:]+)?(:([0-9]+))?([^\?#]+)?(\?([^#]+))?(#(.+))?/);
			var p = new Object();
			p['protocol'] = (m[2] ? m[2] : 'http');
			p['host'] = (m[3] ? m[3] : null);
			p['port'] = (m[5] ? m[5] : null);
			p['path'] = (m[6] ? m[6] : null);
			p['args'] = (m[8] ? m[8] : null);
			p['anchor'] = (m[10] ? m[10] : null);

			;
			;
			;
			;
			;

			if (defaults.mediaId.toString().length > 0) {
				;
				if (p['args'] == null) {
					p['args'] = "movideo_m=" + defaults.mediaId;
				} else {
					p['args'] = p['args'] + "&movideo_m=" + defaults.mediaId;
				}
			}

			if (defaults.playlistId.toString().length > 0) {
				;
				if (p['args'] == null) {
					p['args'] = "movideo_p=" + defaults.playlistId;
				} else {
					p['args'] = p['args'] + "&movideo_p=" + defaults.playlistId;
				}
			}
			defaults.url = p['protocol'] + "://" + p['host'];
			if (p['port'] != null) {
				defaults.url = defaults.url + ":" + p['port'];
			}

			;
			defaults.url = defaults.url + p['path'];
			if (p['args'] != null) {
				defaults.url = defaults.url + "?" + p['args'];
			}
			return defaults.url;
		},
		shortenURL : function(options) {
			;
			if (options === undefined) {
				return null;
			}
			if (options.handler === undefined) {
				return null;
			}
			var defaults = {
				login : 'movideo',
				apiKey : 'R_d738605e8933ff07e335389baf272f5c',
				url : location.href,
				mediaId : '',
				playlistId : '',
				handler : options.handler,
				errorHandler : options.errorHandler,
				extras : null
			};

			if (options.extras !== undefined) {
				defaults.extras = options.extras;
			}
			if (options.url !== undefined) {
				defaults.url = options.url;
			}
			if (options.login !== undefined) {
				defaults.login = options.login;
			}
			if (options.apiKey !== undefined) {
				defaults.apiKey = options.apiKey;
			}
			if (options.mediaId !== undefined) {
				defaults.mediaId = options.mediaId;
			}
			if (options.playlistId !== undefined) {
				defaults.playlistId = options.playlistId;
			}

			var daurl = "http://api.bit.ly/v3/shorten?" + "&longUrl=" + encodeURIComponent(MOVIDEO.utils.getMediaURL(defaults)) + "&login=" + encodeURIComponent(defaults.login)
					+ "&apiKey=" + encodeURIComponent(defaults.apiKey) + "&format=json&callback=?";
			$.ajax( {
				url : daurl,
				dataType : 'jsonp',
				async : false,
				success : function(data) {
					defaults.handler(data, data.data.url, defaults.extras);
				},
				error : defaults.errorHandler
			});
		},
		formatTime : function(sec) {
			sec = Math.round(sec);
			if (!sec)
				return "00:00";
			var hr = Math.floor((sec / 3600) % 60);
			hr = hr > 0 ? ((hr > 9 ? hr : "0" + hr) + ":") : "";
			var min = Math.floor((sec / 60) % 60);
			min = (min > 9) ? sec : "0" + min;
			sec = Math.floor(sec % 60);
			sec = (sec > 9) ? sec : "0" + sec;
			return hr + min + ":" + sec;
		},
		// // Deprecated, use MOVIDEO.device or MOVIDEO.devices.detect()
		getDevice : function() {
			return MOVIDEO.devices.detect();
		},
		generateGuid : function() {
			var result, i;
			result = '';
			for ( var j = 0; j < 32; j++) {
				if (j == 8 || j == 12 || j == 16 || j == 20) {
					result = result + '-';
				}
				i = Math.floor(Math.random() * 16).toString(16).toUpperCase();
				result = result + i;
			}
			return result;
		},
		getMachineTagArray : function(tagObjects) {
			var array = [];
			if (tagObjects === undefined || tagObjects.tag === undefined) {
				return array;
			}
			for ( var i = 0; i < tagObjects.tag.length; i++) {
				array[i] = tagObjects.tag[i].tag;
			}
			return array;
		},
		getTagValue: function(tagObjects,namespace,predicate) {
			;
			if (tagObjects === undefined) {
				return null;
			}
			var tags;
			if (tagObjects.tag === undefined){
				tags = tagObjects;
			} else if (tagObjects.tag.length === undefined) {
				tags = [ tagObjects.tag ];
			} else {
				tags = tagObjects.tag;
			}
			
			for ( var i = 0; i < tags.length; i++) {
				;
				if(tags[i].ns==namespace&&tags[i].predicate==predicate){
					return tags[i].value;
				}
			}
			return null;
		},
		cookie : function(name, value, options) {
			if (typeof value != 'undefined') { // name and value given, set cookie
				options = options || {};
				if (value === null) {
					value = '';
					options.expires = -1;
				}
				var expires = '';
				if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
					var date;
					if (typeof options.expires == 'number') {
						date = new Date();
						date.setTime(date.getTime() + (options.expires * 60 * 60 * 1000));
				//		;
					} else {
						date = options.expires;
					}
					expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
				}
			
				var path = options.path ? '; path=' + (options.path) : '';
				var domain = options.domain ? '; domain=' + (options.domain) : '';
				var secure = options.secure ? '; secure' : '';
		//		;
				document.cookie = [ name, '=', encodeURIComponent(value), expires, path, domain, secure ].join('');
			} else { // only name given, get cookie
				var cookieValue = null;
				if (document.cookie && document.cookie !== '') {
					var cookies = document.cookie.split(';');
					for ( var i = 0; i < cookies.length; i++) {
						var cookieval = $.trim(cookies[i]);
						// Does this cookie string begin with the name we want?
						if (cookieval.substring(0, name.length + 1) == (name + '=')) {
				//			;
							cookieValue = decodeURIComponent(cookieval.substring(name.length + 1));
							break;
						}
					}
				}
			//	;
				return cookieValue;
			}
		},
		shuffle : function(list, firstItem) {
			var length = list.length;
			if (list.length < 2)
				return list;
			var shuffled = [];
			var chosenIndex = 0;
			var chosenItem;
			var startIndex = 0;
			if (firstItem != undefined) {
				shuffled[0] = firstItem;
				startIndex = 1;
			}
			// Start at index 1 so we can insert the current item at index 0
			for ( var i = startIndex; i < length; i++) {
				chosenIndex = Math.random() * list.length;
				chosenItem = list.splice(chosenIndex, 1)[0];
				// If the one we chose is the current media we ignore it and choose again after slicing that one out
				if (chosenItem === firstItem) {
					chosenIndex = Math.random() * list.length;
					chosenItem = list.splice(chosenIndex, 1)[0];
				}
				// Set the current index with the chosen media
				shuffled[i] = chosenItem;
			}
			return shuffled;
		},
		/**
		 * Replace worded or numbered tokens in a string identified by {tokenName} with the values
		 * stored in data as either an Object or Array.
		 * 
		 * For example replaceTokens("my string {insertTokenHere}", { insertTokenHere: "blah" });
		 * returns "my string blah".
		 * 
		 * Douglas Crockford
		 * http://javascript.crockford.com/remedial.html
		 */
		replaceTokens : function(str, data) {
			return str.replace(/{([^{}]*)}/g, function (a, b) {
				var r = data[b];
				return typeof r === 'string' || typeof r === 'number' ? r : a;
			});
		}
	};
}
)();
	var extras = (function() {

	var addThisServcies = {
		twitter: "twitter",
		facebook: "facebook",
		myspace: "myspace",
		email: "email",
		qzone: "qzone",
		sinaweibo: "sinaweibo"
	};

	return {
		bitlyURL: function(options) {
			if (options === undefined) {
				return null;
			}
			
			if (options.handler === undefined) {
				return null;
			}
			
			var defaults = {
				login: 'movideo',
				apiKey: 'R_d738605e8933ff07e335389baf272f5c',
				url: location.href,
				mediaId: '',
				playlistId: '',
				handler: options.handler,
				errorHandler: options.errorHandler,
				extras: null
			};

			if (options.extras !== undefined) {
				defaults.extras = options.extras;
			}
			
			if (options.url !== undefined) {
				defaults.url = options.url;
			}
			
			if (options.login !== undefined) {
				defaults.login = options.login;
			}
			
			if (options.apiKey !== undefined) {
				defaults.apiKey = options.apiKey;
			}
			
			if (options.mediaId !== undefined) {
				defaults.mediaId = options.mediaId;
			}
			
			if (options.playlistId !== undefined) {
				defaults.playlistId = options.playlistId;
			}

			var daurl = "http://api.bit.ly/v3/shorten?"
				+ "&longUrl=" + encodeURIComponent(MOVIDEO.utils.getMediaURL(defaults))
				+ "&login=" + encodeURIComponent(defaults.login)
				+ "&apiKey=" + encodeURIComponent(defaults.apiKey)
				+ "&format=json&callback=?";
				
			$.ajax({
				url: daurl,
				dataType: 'jsonp',
				success: function(data) {
					defaults.handler(data, data.data.url, defaults.extras);
				},
				error: defaults.errorHandler
			});
		},
		supportedAddThisServicesEnum: addThisServcies
		,
		addThisEmbed: function(options) {

			var defaults = {
				url: location.href,
				appAlias: MOVIDEO.application.appAlias,
				addThisUsername: 'movideo',
				bitlyLogin: null,
				bitlyApiKey: null,
				medaia: null,
				playlist: null,
				service: addThisServcies.facebook,
				handler: myHandler,
				errorHandler: errorHandler,
				extras: null
			};

			if (options.extras !== undefined) {
				defaults.extras = options.extras;
			}
			
			if (options.url !== undefined) {
				defaults.url = options.url;
			}
			
			if (options.login !== undefined) {
				defaults.login = options.login;
			}
			
			if (options.apiKey !== undefined) {
				defaults.apiKey = options.apiKey;
			}
			
			if (options.mediaId !== undefined) {
				defaults.mediaId = options.mediaId;
			}
			
			if (options.playlistId !== undefined) {
				defaults.playlistId = options.playlistId;
			}

			MOVIDEO.extras.bitlyURL({
				handler: function(json, url, extras) {}
			});

			//http://api.addthis.com/oexchange/0.8/offer?url=http://addthis.com
		}
	};
}
)();
	
	// aliases for device-related keys deprecated in 2.2
	utils.deviceBrowserEnum = devices.browsers;
	utils.deviceTypeEnum = devices.types;
	
	return {
		/**
		 * Initialize the movideo JS SDK, authenticating with the API if required. 
		 *
		 * @param {Object} application Contains the information to connect to the Media API.
		 * @config {String} appAlias The name of the application used to connect to the API.
		 * @config {String} apiKey The API Key used to connect to the API.
		 * @config {String} [api = 'http://api.v2.movideo.com/rest/'] The path or URL to the API.
		 * @config {Function} [authHandler] Handles all authentication responses.
		 * 
		 * @example
		 * 	MOVIDEO.init({
		 * 		appAlias: 'basic',
		 * 		apiKey: 'dev',
		 * 		api: '/api/rest/', 
		 * 		authHandler: function(result, authenticated) {
		 * 			if (authenticated) {
		 * 				...
		 * 			} else {
		 * 				...
		 * 			}
		 * 		}
		 * 	});
		 *  
		 * The best place to put this code is right before the closing body tag.
		 **/
		init: function (app) {
			
			MOVIDEO.application = application = app;
			MOVIDEO.device = MOVIDEO.devices.detect();
			
			if (!app) {
				throw new Error("MOVIDEO.init() requires an application Object.");
			}
			
			if (!app.apiKey) {
				throw new Error("MOVIDEO.init() application requires an 'apiKey' value.");
			}
			
			if (MOVIDEO.device.isIOS) {
				app.appAlias = (app.iosAppAlias || app.appAlias);
				if (!app.appAlias) {
					throw new Error("MOVIDEO.init() application requires an 'appAlias' or 'iosAppAlias' value");
				}
			}
			else {
				app.appAlias = (app.flashAppAlias || app.appAlias);
				if (!app.appAlias) {
					throw new Error("MOVIDEO.init() application requires an 'appAlias' or 'flashAppAlias' value");
				}
			}
			
			appAlias = application.appAlias;
			apiKey = application.apiKey;
			
			if (application.api != null) {
				api = application.api;
			}
		
			if (app.authHandler) {
				addAuthHandler(app.authHandler);
			}
			
			if (app.clearSession) {
				MOVIDEO.session.clear();
			}
			
			MOVIDEO.media.getSession();
		},
		version: "2.3",
		session: session,
		devices: devices,
		media: 	 media,
		ads: 	 ads,
		bi: 	 bi,
		utils: 	 utils,
		extras:  extras
	};
	
})(jQuery);

// 
//  movideo.player.js
//  jQuery plugin for embedding either Flash based or iOS based movideo players
//  
//  Created by cmoore, nrobson
//  Copyright 2011 movideo. All rights reserved.
//  

(function($) {
	
	// Should we use HTML5 player?
	function _useHTML5() {
		var device = MOVIDEO.devices.detect();
		var useHtmlVideo = device && device.isIOS;
		return useHtmlVideo;
	}

	/**
	 * Embeddable HTML5 player wrapper
	 */
	var MovideoHTML5Player = (function() {
		/**
		 * Create an HTML5 video element
		 */
		function attachVideoElement(parent, controls, poster) {
			// ;
			var video = document.createElement("video");
			video.id = "movideoplayer_" + parent.id;
			video.style.width = "100%";
			video.style.height = "100%";
			video.controls = controls;
			video.poster = poster;
			parent.appendChild(video);
			return video;
		}

		/**
		 * Attach required listeners to video element
		 */
		function attachVideoListeners(target, handler) {
			// ;
			
			var i, n;
			var listeners = [ "durationchange", "click", "pause", "error", "play", "playing", "ended", "loadedmetadata", "timeupdate", "volumechange" ];
			for (i = 0, n = listeners.length; i < n; i++) {
				// IE9 is first IE to support HTML5 and also supports addEventListener(), no need for attachEvent()
				target.addEventListener(listeners[i], handler, false);
			}
		}

		//
		// Constructor
		//

		function Constructor(parent, options) {
			// ;

			//
			// Instance Variables
			//

			var _options = options, 
				_self = this, 
				_parentElement = parent, 
				_queue = [], 
				_queueIndex = -1, 
				_video = null, 
				_advert = null, 
				_media = null, 
				_playlist = null, 
				_channel = null, 
				_progressMarkers = [], 
				_volume = 1, 
				_firstPlay = true, 
				_defaultEventHandler = null;
				
			if (_options.posterFormat == null) {
				_options.posterFormat = "cropped";
			}
				
			if (_options.posterHeight == null) {
				_options.posterHeight = 338;
			}
			
			if (_options.posterWidth == null){
				_options.posterWidth = 600;
			}
			
			if (_options.adProxyPath == null){
				_options.adProxyPath = "/ads";
			}
			
			_status = {
				isAdvert : false,
				isPlaying : false,
				isPaused : false,
				isMuted : false
			},
			
			_progress = {
				time : 0,
				duration : 0,
				timeFormatted : "",
				durationFormatted : ""
			};

			//
			// Private methods
			//

			/**
			 * Progress marker for video: percent played, method to call and
			 * validity
			 */
			function _addProgressMarker(percent, method) {
				_progressMarkers.push( {
					p : percent,
					m : method,
					v : false
				});
			}

			function _handleAnalyticsEvent(track) {
				// ;
				_trigger("analytics", track);
			}

			/**
			 * Handle a loaded application
			 * 
			 * @param app
			 *            Movideo application object
			 */
			function _handleApplication(json, authenticated) {
				// ;
				
				var autoPlay = _options.autoPlay;

				// Data for initial load
				var data = {
					append : false
				};

				// Add options from init
				$.extend(data, _options);

				// Override with query parameters if available
				var qParams = {
					playlistId : "movideo_p",
					mediaId : "movideo_m",
					channelId : "movideo_c",
					searchLimit: "movideo_sl",
					searchTag: "movideo_st"
				};

				for (var name in qParams) {
					var val = MOVIDEO.utils.getQueryStringParameter(qParams[name]);
					if (val !== undefined && val)
						data[name] = val;
				}

				if (json.application.advertisingConfig !== undefined) {
					json.application.advertisingConfig.proxyPath = _options.adProxyPath;
					MOVIDEO.ads.setConfig(json.application.advertisingConfig);
				}

				if (json.application.applicationConfig !== undefined && json.application.applicationConfig.autoplay == "true") {
					autoPlay = true;
				}

				var id = _parentElement.id;
				
				_trigger("init", { playerId: id });
				
				MOVIDEO.bi.createSession(id, MOVIDEO.utils.getAccountId(), MOVIDEO.utils.getApplicationAlias(), null, "player", null, false);
				_load(data, autoPlay);
				
			}

			function _debug(video) {
				$('body').append('<div id="movideo-debug" title="Debug Video Events"></div>');
				video.bind('loadstart', function(evt) {
					logEvent(evt, '#000099');
				});
				video.bind('canplaythrough', function(evt) {
					logEvent(evt, '#66CC33');
				});
				video.bind('canplay', function(evt) {
					logEvent(evt, '#66CC33');
				});
				video.bind('loadeddata', function(evt) {
					logEvent(evt, '#00CCCC');
				});
				video.bind('loadedmetadata', function(evt) {
					logEvent(evt, '#00CCCC');
				});
				video.bind('abort', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('emptied', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('error', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('stalled', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('suspend', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('waiting', function(evt) {
					logEvent(evt, '#ff0000');
				});
				video.bind('pause', function(evt) {
					logEvent(evt, '#ff6600');
				});
				video.bind('play', function(evt) {
					logEvent(evt, '#ff6600');
				});
				video.bind('volumechange', function(evt) {
					logEvent(evt, '#ff6600');
				});
				video.bind('playing', function(evt) {
					logEvent(evt, '#912cee');
				});
				video.bind('seeked', function(evt) {
					logEvent(evt, '#008080');
				});
				video.bind('seeking', function(evt) {
					logEvent(evt, '#008080');
				});
				video.bind('durationchange', function(evt) {
					logEvent(evt, '#cc0066');
				});
				video.bind('progress', function(evt) {
					logEvent(evt, '#cc0066');
				});
				video.bind('ratechange', function(evt) {
					logEvent(evt, '#cc0066');
				});
				// video.bind('timeupdate',function(evt){logEvent(evt,'#c0c0c0');});
				video.bind('ended', function(evt) {
					logEvent(evt, '#000099');
				});

				function logString(type, value, color) {
					// if (value === undefined) {
						// ;
					// }
					var log = document.createElement("div");
					var note = document.createElement("span");
					log.style.color = color;
					log.innerHTML = "(" + type + ") " + value;
					note.setAttribute('class', 'note');
					log.appendChild(note);
					// ;
					$("#movideo-debug").prepend(log);
				}

				function logEvent(event, color) {
					switch (event.type) {
					case 'timeupdate':
						val = "timeupdate " + event.target.currentTime.toFixed(3);
						break;
					case 'emptied':
						val = "emptied";
						break;
					case 'error':
						if (event.target.error != null) {
							switch (event.target.error.code) {
							case event.target.error.MEDIA_ERR_ABORTED:
								val = 'Aborted the video playback.';
								break;
							case event.target.error.MEDIA_ERR_NETWORK:
								val = 'A network error caused the video download to fail part-way.';
								break;
							case event.target.error.MEDIA_ERR_DECODE:
								val = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.';
								break;
							case event.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
								val = 'The video could not be loaded, either because the server or network failed or because the format is not supported.';
								break;
							default:
								val = 'An unknown error occurred with code ' + event.target.error.code;
								break;
							}
						} else {
							val = 'An unknown error occurred as error is ' + event.target.error;
						}
						break;
					case 'ended':
						val = "ended";
						break;
					case 'abort':
						val = "abort";
						break;
					case 'click':
						val = "click";
						break;
					case 'loadstart':
						val = "begin loading media data";
						break;
					case 'progress':
						val = "fetching media...";
						break;
					case 'canplay':
						val = "can play, but will eventually have to buffer";
						break;
					case 'canplaythrough':
						val = "can play, won't have to buffer anymore";
						break;
					case 'loadeddata':
						val = "can render media data at current playback position";
						break;
					case 'loadedmetadata':
						val = "now we know duration, height, width, and more";
						break;
					case 'durationchange':
						val = "new info about the duration (" + event.target.duration.toFixed(0) + " seconds)";
						break;
					case 'volumechange':
						val = "volume or muted property has changed";
						break;
					case 'play':
						val = "just returned from the play function";
						break;
					case 'playing':
						val = "playback has started";
						break;
					case 'pause':
						val = "just returned from the pause function";
						break;
					case 'suspend':
						val = "loading has stopped, but not all of the media has downloaded";
						break;
					case 'waiting':
						val = "stopped playback because we're waiting for the next frame";
						break;
					case 'stalled':
						val = "fetching media data, but none is arriving";
						break;
					}
					logString(event.type, val, color);
				}
			}

			/**
			 * Handle Error information by dispatching new error event
			 * 
			 * @param type
			 *            The type of event
			 * @param code
			 *            The error code if available
			 * @param message
			 *            The error message if available
			 * @param source
			 *            The source of the error
			 */
			function _handleError(type, code, message, source) {
				// ;
				
				var error = {
					type : type,
					code : code,
					message : message,
					source : source
				};
				_trigger("error", error);
			}

			/**
			 * Handle an HTTP error
			 * 
			 * @param json
			 *            Data extracted from error
			 */
			function _handleHttpError(json) {
				// ;
				
				_handleError("http", json.code, json.message, json);
			}

			/**
			 * Handle an HTML5 video event
			 * 
			 * @param evt
			 *            video event
			 */
			function _handleVideoEvent(evt) {
				// ;
				
				var id = _parentElement.id;
				switch (evt.type) {
				case "click":
					// ;
					if (_status.isAdvert) {
						MOVIDEO.ads.click(id);
					}
					_trigger("click", _media);
					break;
					
				case "error":
					var message;
					var code;
					var error = evt.target.error;
					if (error) {
						switch (error.code) {
						case error.MEDIA_ERR_ABORTED:
							message = 'Aborted the video playback.';
							break;
						case error.MEDIA_ERR_NETWORK:
							message = 'A network error caused the video download to fail part-way.';
							break;
						case error.MEDIA_ERR_DECODE:
							message = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support.';
							break;
						case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
							message = 'The video could not be loaded because the format is not supported.';
							break;
						default:
							message = 'An unknown error occurred.';
							break;
						}
						code = error.code;
					} else {
						message = 'An unknown error occurred with no code.';
						code = "not found";
					}
					_handleError("video", code, message, evt);
					break;
					
				case "play":
				// ;
					if (_status.isPaused) {
						_status.isPaused = false;
						if (!_status.isAdvert) {
							MOVIDEO.bi.resume( {
								"id" : id
							});
						}
						_trigger("resume", _media);
					}
					
					break;
					
				case "pause":
					// TODO: What does first play do?
					if (!_firstPlay) {
						_status.isPaused = true;
						if (!_status.isAdvert) {
							MOVIDEO.bi.pause( {
								"id" : id
							});
						} else {
							MOVIDEO.ads.pause(id);
						}
						_trigger("pause", _media);
					}
					break;
					
				case "timeupdate":
					_updateProgress();
					break;
					
				case "durationchange":
					_updateDuration();
					break;
					
				case "ended":
					_updateProgress();
					_trigger("playcomplete", _media);
					_resetPlayHistory();
					if (_status.isAdvert) {
						_play();
					} 
					else if (_queueIndex + 1 >= _queue.length) {
						_trigger("queuecomplete");
						_load({}, false);
					} 
					else {
						_updateQueueIndex({
							queueIndex: _queueIndex + 1
						});
						_play();
					}
					break;
					
				case "volumechange":
					var wasMuted = _status.isMuted;
					_status.isMuted = (_video.volume == 0);
					
					_trigger("volumechange", { volume : _video.volume });
					
					if (wasMuted && !_status.isMuted) {
						_trigger("unmute");
					} 
					else if (_status.isMuted && !wasMuted) {
						_trigger("mute");
					}
					break;
					
				case "playing":	
					if (_firstPlay) {
						_firstPlay = false;
						// ;
					
						if (!_status.isAdvert) {
							MOVIDEO.bi.play({
								"id" : id
							});
						} else {
							MOVIDEO.bi.play({
								"id" : id
							});
							MOVIDEO.ads.start(id);
						}
					}
					break;
					
				case "loadedmetadata":
					_resetPlayHistory();
					if (!_status.isAdvert) {
						MOVIDEO.bi.loadVideo({
							"id" : id
						}, $(_parentElement).width(), $(_parentElement).height(), _video.duration, _media, _playlist, _channel);
					} 
					else {
						MOVIDEO.ads.impression(id);
						MOVIDEO.bi.loadVideoAd({
							"id" : id
						}, $(_parentElement).width(), $(_parentElement).height(), _video.duration);
					}
					_status.isPlaying = true;
					_trigger("play", _media);
					_video.play();
					break;
				}
			}

			/**
			 * Main queue adjuestment method. Allowable values:
			 * <ul>
			 * <li>mediaId</li>
			 * <li>playlistId</li>
			 * <li>channelId</li>
			 * <li>mediaIds</li>
			 * <li>queueIndex</li>
			 * <li>append</li>
			 * <li>appendIndex</li>
			 * </ul>
			 * 
			 * If data is already loaded or selectable (by using mediaId or
			 * queueIndex) the media will be selected. If data is not already
			 * loaded or locatable it will be loaded, obaying append and
			 * appendIndex properties. When data has been found and selected if
			 * 'play' is true the media player will begin playing. If nothing is
			 * passed the queue index will return to zero
			 * 
			 * For full documentation see
			 * http://wiki.mcmentertainment.com/display/mts/movideo+Universal+Player+API+Proposal
			 * 
			 * @param data
			 * @param play
			 * @return
			 */
			function _load(data, play) {
				// ;
				
				// Normalize...
				/*
				 * for (var key in data) { var value = data[key]; delete
				 * data[key]; data[key.toLowerCase()] = value; }
				 */
				
				// Clone so changes don't affect external programs
				data = $.extend({}, data);
				
				// Push mediaId to mediaIds if it's a number (i.e. not array or string)
				if (data.mediaId && typeof data.mediaId !== 'number') {
					data.mediaIds = data.mediaId;
					delete data.mediaId;
				}
				
				if (data.channelId && (!_channel || _channel.id != data.channelId)) {
					_loadChannel(data, play);
				} 
				else if (data.playlistId && (!_playlist || _playlist.id != data.playlistId)) {
					_loadPlaylist(data, play);
				} 
				else if (data.searchTag || data.searchTags) {
					_loadSearch(data, play);
				} 
				else if (data.mediaIds || (data.mediaId && _queueIndexOf(data.mediaId) == -1)) {
					_loadMedia(data, play);
				} 
				else {
					// ;
					_updateQueueIndex(data);
					
					if (play && _media) {
						// ;
						_play();
					} 
					else if (data.append === undefined) {
						
						if (_status.isPlaying) {
							_stop();
						}
						
						_setPoster(options);
						_trigger("idle");
						
					} 
					else if (!data.append && _media) {
						
						_status.isAdvert = false;
						
						if (MOVIDEO.ads.hasAdNext()) {
							_loadNextAd();
						}
						else {
							_loadSMIL();
						}
						
						_setPosterFromMedia(_media, options);
						_trigger("idle");
					}
					else
					{
						_setPoster(options);
						_trigger("idle");
					}
				}
			}
			
			function _setPoster(options) {
				if (_options.posterDisabled) {
					return;
				}
				
				var posterURL = _options.posterURL || _options.poster || "";
				if (posterURL.length > 0) {
					// ;
					_video.poster = posterURL;
				}
			}
			
			function _setPosterFromMedia(media, options) {
				if (_options.posterDisabled) {
					return;
				}
				
				var posterURL = _media.imagePath + _options.posterFormat + "/" + _options.posterWidth + "x" + _options.posterHeight + ".png";
				if (posterURL) {
					// ;
					_video.poster = posterURL;
				}
			}
			
			function _loadChannel(data, play) {
				// ;
				
				function withChannelOptionsFor(data, play) {
					return {
						id: data.channelId,
						handler: function(result) {
							// ;
							_channel = result.channel;
							delete data.channelId;
							
							var playlists = (result.channel && result.channel.playlists && result.channel.playlists.playlist)
								? result.channel.playlists.playlist
								: [];
							
							if (playlists.length > 0) {
								data.playlistId = playlists[0].id;
							}
							
							_load(data, play);
						},
						errorHandler: _handleHttpError
					}; 
				}
				
				MOVIDEO.media.getChannel(withChannelOptionsFor(data, play));
			}
			
			function _loadPlaylist(data, play) {
				// ;
				
				function withPlaylistOptionsFor(data, play) {
					return {
						id: data.playlistId,
						media: true,
						handler: function(result) {
							// ;
							_playlist = result.playlist;
							delete data.playlistId;
							
							var mediaList = (result.playlist && result.playlist.mediaList && result.playlist.mediaList.media)
								? result.playlist.mediaList.media
								: [];
							
							_updateQueue(mediaList, data.append, data.appendIndex, data.shuffle);
							_load(data, play);
						},
						errorHandler: _handleHttpError
					};
				}
				
				MOVIDEO.media.getPlaylist(withPlaylistOptionsFor(data, play));
			}
			
			function _loadSearch(data, play) {
				
				function withSearchOptionsFor(data, play) {
					// Load media for a search term
					var tags = data.searchTags || data.searchTag;
					if (!$.isArray(tags)) {
						tags = tags.split(",");
					}
					
					return {
						keywords: tags,
						keywordOperator: 'or',
						handler: function(result) {
							// ;
							var mediaList = result.media ? result.media : [ result ];
							delete data.searchTag;
							delete data.searchTags;
							_updateQueue(mediaList, data.append, data.appendIndex, data.shuffle);
							_load(data, play);
						},
						errorHandler: _handleHttpError
					};
				}
				
				function withPagingOptionsFor(data) {
					var limit = data.searchLimit === undefined ? 10 : Math.min(data.searchLimit, 50);
					return { 
						active: false, 
						size: limit 
					};
				}
				
				var searchOptions = withSearchOptionsFor(data, play);
				var pagingOptions = withPagingOptionsFor(data);
				
				// ;
				MOVIDEO.media.searchMedia(searchOptions, pagingOptions);
			}
			
			function _loadMedia(data, play) {
				
				function withMediaOptionsFor(data, play) {
					// Media id could be array of IDs, CSV of IDs or simple
					// number ID.
					var ids = data.mediaIds ? ($.isArray(data.mediaIds) ? data.mediaIds.join(",") : data.mediaIds.replace(/\s/gi, '')) : data.mediaId;
					
					return {
						id: ids,
						handler: function(result) {
							// ;
							// Media can come back as either a single object or an array
							var mediaList =  result.list ? result.list[0].media : [ result.media ];
							delete data.mediaIds;
							_updateQueue(mediaList, data.append, data.appendIndex, data.shuffle);
							_load(data, play);
						},
						errorHandler: _handleHttpError
					};
				}
				
				var mediaOptions = withMediaOptionsFor(data, play);
				
				// ;
				MOVIDEO.media.getMedia(mediaOptions);
			}

			function _loadSMIL() {
				// ;
				
				if (_media !== undefined) {
					_trigger("load");
					// ;
					MOVIDEO.media.getSMIL( {
						id : _media.id,
						handler : function(json){
							// ;
							var host = json.smil.head[0].meta[0]['@base'];
							var auth = json.smil.head[0].meta[2]['@content'];
							var device = MOVIDEO.utils.getDevice();
							var mediaURL;
							var src;
							if (!device.isIOS) {
								_handleError("device", 1, "Unsupported Device", device);
							} 
							else {
								if (json.smil.body['switch'].m3u8 instanceof Array) {
									if (device.type == MOVIDEO.utils.deviceTypeEnum.iPhone) {
										src = json.smil.body['switch'][0].m3u8[1]['@src'];
									} 
									else if (device.type == MOVIDEO.utils.deviceTypeEnum.iPad) {
										src = json.smil.body['switch'][0].m3u8[0]['@src'];
									}
								} else {
									src = json['smil']['body']['switch'][0].m3u8['@src'];
								}
								mediaURL = host + src + "?" + auth;
							}
							// ;
							_video.setAttribute('src', mediaURL);
							_video.load();
						},
						errorHandler : function(json) {
							_handleError("http", json.code, json.message, json);
						}
					});
				}
			}

			function _loadNextAd() {
				// ;
				
				var id = _parentElement.id;
				MOVIDEO.ads.callAd(id, _options.adURLPreProcesser, function(videoAd) {
					if (videoAd == null || videoAd === undefined) {
						// ;
						_loadSMIL();
					} 
					else {
						_advert = videoAd;
						_status.isAdvert = true;
						_trigger("ad", _advert);
						// ;
						// ;
						_video.src = videoAd.url;
						_video.load();
					}
				});
			}
			
			function _play() {
				// ;
				
				if (!_options.posterDisabled) {
					var posterMediaURL =_media.imagePath + _options.posterFormat+ "/" + _options.posterWidth+"x"+_options.posterHeight+".png";
					_video.setAttribute("poster",posterMediaURL);
				}
				_status.isAdvert = false;
				var id = _parentElement.id;
				if (MOVIDEO.ads.hasAdNext()) {
					_loadNextAd();
				} 
				else {
					_loadSMIL();
				}
			}
			
			function _seek(value) {
				try {
					// Will throw an INVALID_STATE_ERR exception if there is no selected media resource or if there is a current media controller. 
					// Will throw an INDEX_SIZE_ERR exception if the given time is not within the ranges to which the user agent can seek.
					_video.currentTime = value;
				}
				catch (error) {
					;
				}
			}
			
			function _stop() {
				// ;
				
				_video.pause();
				_video.currentTime = 0;
				_video.src = null;
				_resetPlayHistory();
				
				// inconsistent with the flash version, to be deprecated?
				_trigger("stop");
				
				// adding stopped for consistency with flash version
				_trigger("stopped");
			}

			function _queueIndexOf(mediaId) {
				var i, n;
				for (i = 0, n = _queue.length; i < n; i++) {
					if (_queue[i].id == mediaId) {
						return i;
					}
				}
				return -1;
			}

			function _requestSession(apiKey, applicationAlias) {
				// ;
				
				if (apiKey != null && applicationAlias != null) {
					// ;
					
					MOVIDEO.init({
						appAlias: applicationAlias,
						apiKey: apiKey,
						api: _options.api,
						authHandler: function(json, authenticated) {
							// ;
							
							if (authenticated) {
								_requestApplication();
							}
						}
					});
				} 
				else {
					// ;
					
					_requestApplication();
				}
			}
			
			function _requestApplication() {
				// ;
				
				MOVIDEO.media.callAPI({
					endpoint: "application",
					handler: _handleApplication,
					errorHandler: function(json) {
						_handleError("http", json.code, json.message, json);
					}
				});
			}

			function _resetPlayHistory() {
				// ;
				
				var i, n;
				_status.isPaused = false;
				_status.isPlaying = false;
				_firstPlay = true;
				for (i = 0, n = _progressMarkers.length; i < n; i++) {
					_progressMarkers[i].v = true;
				}
			}

			/**
			 * Trigger the default event handler passing the event type and
			 * event information as required
			 * 
			 * @param type
			 *            Event type
			 * @param info
			 *            Details of event such as old value if a change event
			 *            or cause error if an error event
			 */
			function _trigger(type, info) {
				if (_defaultEventHandler !== null) {
					_defaultEventHandler(type, info);
				}
			}

			function _updateDuration() {
				_progress.duration = +_video.duration;
				_progress.durationFormatted = MOVIDEO.utils.formatTime(_progress.duration);
			}

			function _updateProgress() {
				var id = _parentElement.id;
				var i = 0, n = _progressMarkers.length, marker, percent = Math.round(((_progress.time / _progress.duration) * 100));
				_progress.time = +_video.currentTime;
				_progress.timeFormatted = MOVIDEO.utils.formatTime(_progress.time);
				_progress.percent = percent;

				// While there are progress markers and there are progress
				// markers higher than current percent
				while (i < n && _progressMarkers[i].p <= percent) {
					// for (i = 0, n = _progressMarkers.length; i < n,
					// _progressMarkers[i].p <= percent; i++) {
					marker = _progressMarkers[i++];
					// skip if not active
					if (!marker.v)
						continue;

					if (_status.isAdvert) {
						MOVIDEO.bi.viewed( {
							"id" : id
						}, marker.p);
					} else {
						marker.m.call(null, _parentElement.id);
					}
					marker.v = false;
				}

				_trigger("progress", _progress);
			}

			function _updateQueue(mediaList, append, appendIndex, shuffle) {
				// ;
				
				var offset = 0;
				
				if (shuffle) {
					mediaList = MOVIDEO.utils.shuffle(mediaList, _media);
				}
				
				if (append || append === undefined) {
					// ;
					
					offset = _queue.length;
					if (appendIndex !== undefined && appendIndex > -1) {
						offset = appendIndex;
					}
				} 
				else {
					// ;
					
					_queue = [];
				}
				
				Array.prototype.splice.apply(_queue, [ offset, 0 ].concat(mediaList));
				
				_trigger("queuechange", {
					queueIndex: offset,
					length: mediaList.length
				});
			}
			
			function _removeFromQueue(mediaIds) {
				var queuechanged;
				var mediaId;
				
				while (mediaId = mediaIds.shift()) {
					var index = _queueIndexOf(mediaId);
					if (index > -1) {
						_queue.splice(index, 1);
						queuechanged = true;
					}
				}
				
				if (queuechanged) {
					_trigger("queuechange", {});
				}
			}

			/**
			 * Determine queue index from load instructions of either an
			 * explicit index or a media id which is searched for in the queue.
			 * Trigger a <code>queueindexchange</code> with the Media at the
			 * new index.
			 * 
			 * @param data
			 *            contains either a queueIndex or mediaId property.
			 *            Media id overrides queue index
			 * @return The selected index.
			 */
			function _updateQueueIndex(data) {
				// ;
				
				var index = 0;
				
				if (data.queueIndex !== undefined) {
					// ;
					index = data.queueIndex;
				}
				
				if (data.mediaId) {
					// ;
					index = _queueIndexOf(data.mediaId);
				}
				
				if (index >= _queue.length) {
					index = -1;
				}
				_queueIndex = index;
				// ;
				_media = _queueIndex == -1 ? null : _queue[_queueIndex];
				_trigger("queueindexchange", _media);
			}

			function _useDebugMode() {
				var param = MOVIDEO.utils.getQueryStringParameter("movideo_debug");
				var option = _options.debug !== undefined ? _options.debug.toString().toLowerCase() : "false";
				return (param == "true" || options == "true");

			}

			//
			// Instance methods
			//

			this.play = function(data) {
				if (data.append === undefined) {
					data.append = false;
				}
				_load(data, true);
			};

			this.addToQueue = function(data) {
				if (data.append === undefined) {
					data.append = true;
				}
				_load(data, false);
			};

			this.removeFromQueue = function(data) {
				if (data.mediaId !== undefined) {
					_removeFromQueue([ data.mediaId ]);
					
					if (_status.isPlaying && _media.id == data.mediaId) {
						_play();
					}
				}
				else if (data.mediaIds !== undefined) {
					var mediaIds = $.isArray(data.mediaIds) ? data.mediaIds : data.mediaIds.split(',');
					_removeFromQueue(mediaIds);
				}
				else if (data.queueIndex !== undefined) {
					var media = _queue[data.queueIndex];
					if (media) {
						_removeFromQueue([ media.id ]);
					}
				}
			};

			this.seek = function(time) {
				if (time.indexOf("%") > -1) {
					time = parseFloat(time.replace("%")) / 100;
					time = time * _video.duration;
				} 
				else {
					time = parseFloat(time);
				}
				_seek(time);
			};

			this.stop = function() {
				_stop();
				_load({}, false);
			};

			this.mute = function() {
				// ;
				if (!_status.isMuted) {
					_volume = _video.volume;
					_video.volume = 0;
					_status.isMuted = true;
					_trigger("mute");
				}
			};

			this.unmute = function() {
				// ;
				if (_status.isMuted) {
					_video.volume = _volume;
					_status.isMuted = true;
					_trigger("unmute");
				}
			};

			this.pause = function() {
				if (_status.isPlaying && !_status.isPaused) {
					_video.pause();
				}
			};

			this.resume = function() {
				if (_status.isPlaying && _status.isPaused) {
					_video.play();
				}
			};

			this.advert = function() {
				return _advert;
			};

			this.media = function() {
				return _media;
			};

			this.playlist = function() {
				return _playlist;
			};

			this.channel = function() {
				return _channel;
			};

			this.progress = function() {
				return _progress;
			};

			this.volume = function(value) {
				// ;
				
				if (value !== undefined) {
					_video.volume = _volume = value;
					
					// iOS as at 4.3.2 does not allow javascript control of volume or muted. 
					_trigger("volumechange", { volume : _video.volume });
					
					if (_status.isMuted) {
						if (value > 0) {
							_status.isMuted = false;
							_trigger("unmute");
						}
					}
					else {
						if (value == 0) {
							_status.isMuted = true;
							_trigger("mute");
						}
					}
				}
				return _video.volume;
			};

			this.queue = function() {
				return _queue;
			};

			this.queueIndex = function() {
				return _queueIndex;
			};

			this.isAdvert = function() {
				return _status.isAdvert;
			};

			this.isMuted = function() {
				return _status.isMuted;
			};

			this.isPaused = function() {
				return _status.isPaused;
			};

			this.isPlaying = function() {
				return _status.isPlaying;
			};

			this.playerId = function() {
				return _video.id;
			};

			this.player = function(method, data) {
				if (data !== undefined) {
					return this[method].call(_self, data);
				} else {
					return this[method].call(_self);
				}
			};
			
			this.next = function() {
				// ;
				// ;
				
				var currentQueueIndex = this.queueIndex();
				var nextQueueIndex = currentQueueIndex + 1;
				var currentMedia = _media;
				var nextMedia = nextQueueIndex > -1 && nextQueueIndex < _queue.length ? _queue[nextQueueIndex] : null;
				
				_trigger("next", { 
					currentQueueIndex: currentQueueIndex,
					currentMedia: currentMedia, 
					nextQueueIndex: nextQueueIndex, 
					nextMedia: nextMedia 
				});
				
				this.play({
					queueIndex: nextQueueIndex
				});
			};
			
			this.previous = function() {
				// ;
				
				var currentQueueIndex = this.queueIndex();
				var previousQueueIndex = currentQueueIndex - 1;
				var currentMedia = _media;
				var previousMedia = previousQueueIndex > -1 && previousQueueIndex < _queue.length ? _queue[previousQueueIndex] : null;
				
				_trigger("previous", { 
					currentQueueIndex: currentQueueIndex,
					currentMedia: currentMedia, 
					previousQueueIndex: previousQueueIndex, 
					previousMedia: previousMedia 
				});
				
				this.play({ 
					queueIndex: previousQueueIndex 
				});
			};
			
			/**
			 * Default event listener, to be replaced by the wrapping code which
			 * will then use its built in event dispatcher / trigger / signal
			 * setup to distrubute these player events. Method must follow
			 * prototype function(eventType, eventInfo);
			 */
			this.setDefaultEventHandler = function(value) {
				if (value !== undefined && typeof value === "function")
					_defaultEventHandler = value;
				return _defaultEventHandler;
			};

			//
			// Constructor code
			// 

			var showControls = _options.controls === undefined || _options.controls;
			var posterURL = _options.posterURL || _options.poster || "";
			var target = _parentElement;
			_video = attachVideoElement(target, showControls, posterURL);
			attachVideoListeners(_video, _handleVideoEvent);
			
			if (MOVIDEO.utils.getQueryStringParameter("movideo_debug") == "true") {
				// ;
				_debug($(_video));
			}
			
			MOVIDEO.bi.addEventHander(_handleAnalyticsEvent);
			
			_addProgressMarker(25, MOVIDEO.ads.firstQuartile);
			_addProgressMarker(50, MOVIDEO.ads.midpoint);
			_addProgressMarker(75, MOVIDEO.ads.thirdQuartile);
			_addProgressMarker(100, MOVIDEO.ads.complete);
			_requestSession(_options.iosApiKey || _options.apiKey, _options.iosAppAlias || _options.appAlias);
			
			// Constructor complete
		}

		return Constructor;
	})();

	/**
	 * Flash player wrapper
	 */
	var MovideoFlashPlayer = function(parent, options) {
		// ;
		
		// Create default player with default values that will
		// receive requests until the Flash object has initialised
		var playerId = [ parent.id, options.flashAppAlias, new Date().getTime() ].join("_"),
			$this = this,
			_defaultEventHandler,
			refs = {
				target: {
					player: _createDummy(playerId)
				}
			};

		// http://forum.jquery.com/topic/object-element-problems-in-jquery
		function _applyFrameworkObjectHack() {
			jQuery.noData.object = false;
		}

		function _createDummy(_playerId) {
			var defaults = {
				play : function(data) {
					;
				},
				addToQueue : function(data) {
					;
				},
				removeFromQueue : function(data) {
					;
				},
				seek : function(data) {
					;
				},
				stop : function() {
					;
				},
				mute : function() {
					;
				},
				unmute : function() {
					;
				},
				pause : function() {
					;
				},
				resume : function() {
					;
				},
				advert : function() {
					;
					return null;
				},
				media : function() {
					;
					return null;
				},
				playlist : function() {
					;
					return null;
				},
				channel : function() {
					;
					return null;
				},
				progress : function() {
					;
					return null;
				},
				volume : function(value) {
					;
					return 1;
				},
				queue : function() {
					;
					return [];
				},
				queueIndex : function() {
					;
					return -1;
				},
				isAdvert : function() {
					;
					return false;
				},
				isPlaying : function() {
					;
					return false;
				},
				isPaused : function() {
					;
					return false;
				},
				isMuted : function() {
					;
					return false;
				},
				next : function() {
					;
				},
				previous : function() {
					;
				},
				playerId : function() {
					;
					return _playerId;
				}
			};
			return function(method, data) {
				;
				return defaults[method].call(this, data);
			};
		}

		function _init() {
			// ;
			
			var element = $('<div id="' + playerId + '"></div>'),
				flashvars = $.extend( {}, options);
				
			$.extend(flashvars, {
				alias: options.flashAppAlias || options.appAlias,
				apiKey: options.flashApiKey || options.apiKey,
				playerId: playerId
			});
			
			if (options.swfURL === undefined) {
				options.swfURL = "http://static.movideo.com/flash/movideo_player.swf";
			}
			
			element.appendTo(parent);
			element.flash({
				src: options.swfURL,
				name: playerId,
				width: $(parent).width(),
				height: $(parent).height(),
				bgcolor: "#000000",
				wmode: "transparent",
				allowfullscreen: "true",
				allowscriptaccess: "always",
				flashvars: flashvars
			});

			// Get newly created player (Flash may not yet have initialised the <object> tag yet though)
			var p = document.getElementById(playerId);
			// Instruct player to prefix all events with an empty string (default is 'player')
			p.eventPrefix = "";
			
			// Called when player initialises and is ready for listeners
			p.init = function() {
				
				// ;
				
				var p = document.getElementById(playerId),
					ev = "idle,play,playcomplete,stopped,progress,stop,seeking,seeked,ad,pause,resume,next,previous,mute,unmute,volumechange,analytics,queuechange,queueindexchange,queuecomplete,click,error,cuepoint",
					evs = ev.split(","), 
					i, 
					n = evs.length;
					
				function playerEventHandler(evt, info) {
					// ;
					
					if (evt.type == "ad") {
						MOVIDEO.ads.updateCompanionAds(info.companionAds);
					}
					
					if (_defaultEventHandler) {
						_defaultEventHandler(evt.type, info);
					}
				}
				
				for (i = 0; i < n; i++) {
					p.bind(evs[i], playerEventHandler);
				}
				
				refs.target = p;
				
				// potentially causes issues with IE, disabled until verified.
				// try {
				// 	p.init = null;
				// 	// "Object may not support this operation". Thanks IE6.
				// 	delete p.init;
				// }
				// catch (error) {
				// 	// ignored!
				// }
				
				if (_defaultEventHandler) {
					_defaultEventHandler("init", { playerId: playerId, type: "init" });
				}
			};
		}

		this.setDefaultEventHandler = function(h) {
			if (h !== undefined && typeof h === "function")
				_defaultEventHandler = h;
			return _defaultEventHandler;
		};

		// Universal accessor method for this class
		this.player = function(method, value) {
			try {
				if (value !== undefined) {
					return refs.target.player(method, value);
				} else {
					return refs.target.player(method);
				}
			}
			catch (error) {
				// alert("error invoking player method '" + method + "' with value " + value);
				// ;
			}
		};

		_applyFrameworkObjectHack();
		_init();
	};
	
	MOVIDEO.player = {
		_Flash: MovideoFlashPlayer,
		_HTML5: MovideoHTML5Player, 
		create: function(element, options) {
			// ;
			options = options || {};
			
			if (options.companionAdLocations !== undefined) {
				MOVIDEO.ads.setCompanionAdLocations(options.companionAdLocations);
			}
			
			if (_useHTML5()) {
				return new MovideoHTML5Player(element, options);
			} else {
				return new MovideoFlashPlayer(element, options);
			}
		}
	};
	
	// Use jQuery UI method calling pattern
	$.fn.player = function(method, value) {
		var el = this[0];
		var playerObject = $(el).data("movideo_player");
		if (playerObject && typeof method === "string") {
			return playerObject.player(method, value);
		} 
		else if (!playerObject || typeof method === "object") {
			playerObject = $.fn.player.prototype._create(el, method);
			// Return jQuery object for chaining
			return this;
		}
	};

	// Declare constructor as a prototype method so that it's not
	// redefined every time $.fn.player is called, which will be lots.
	// Also helps with extension as this can be overridden to create
	// different types of players
	$.fn.player.prototype = {
		_create : function(element, options) {
			// ;
			
			var el = $(element);
			var playerObject = MOVIDEO.player.create(element, options);
			
			playerObject.setDefaultEventHandler(function(type, info) {
				el.trigger("player" + type, info);
			});
				
			el.data("movideo_player", playerObject);
			return playerObject;
		}
	};

})(jQuery);

(function($, undefined) {
	/*
	 * http://stephenbelanger.com/tag/flash/ jquery.flash v1.3.1 - 02/01/10
	 * (c)2009 Stephen Belanger - MIT/GPL. http://docs.jquery.com/License
	 */
	$.fn.extend({
		flash : function(opt) {
			var has, cv, ie;

			// Let's make some handy functions to minimize code repetition.
			function attr(a, b) {
				return ' ' + a + '="' + b + '"';
			}
			function param(a, b) {
				return '<param name="' + a + '" value="' + b + '" />';
			}

			// Do some browser and flash version checking.
			var p = navigator.plugins;
			if (p && p.length) {
				var f = p['Shockwave Flash'];
				if (f) {
					has = true;
					if (f.description) cv = f.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".");
				}
				if (p['Shockwave Flash 2.0']) {
					has = true;
					cv = '2.0.0.11';
				}
			} 
			else {
				try {
					var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
				} catch (e) {
					try {
						var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
						cv = [ 6, 0, 21 ];
						has = true;
					} catch (e) {}
					try {
						axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
					} catch (e) {}
				}
				if (axo != null) {
					cv = axo.GetVariable("$version").split(" ")[1].split(",");
					has = true;
					ie = true;
				}
			}

			// Finally, we're on to the REAL action.
			$(this).each(function() {
				// Don't even bother if we don't have Flash installed.
				if (has) {
					var e = $(this), 
						s = $.extend( {
						'id' : e.attr('id'),
						'name' : e.attr('id'),
						'class' : e.attr('class'),
						'width' : e.width(),
						'height' : e.height(),
						'src' : e.attr('href'),
						'classid' : 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000',
						'pluginspace' : 'http://get.adobe.com/flashplayer',
						'availattrs' : [ 'id', 'class', 'width', 'height', 'src' ],
						'availparams' : [ 'src', 'bgcolor', 'quality', 'allowscriptaccess', 'allowfullscreen', 'flashvars', 'wmode' ],
						'version' : '9.0.24'
					}, opt),
					a = s.availattrs,
					b = s.availparams,
					rv = s.version.split('.'),
					o = '<object';

					// Set codebase, if not supplied in the settings.
					if (!s.codebase) {
						s.codebase = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=' + rv.join(',');
					}

					// Use express install swf, if necessary.
					if (s.express) {
						for ( var i in cv) {
							if (parseInt(cv[i]) > parseInt(rv[i])) {
								break;
							}
							if (parseInt(cv[i]) < parseInt(rv[i])) {
								s.src = s.express;
							}
						}
					}

					// Convert flashvars to query string.
					if (s.flashvars) {
						s.flashvars = unescape($.param(s.flashvars));
					}

					// Set browser-specific attributes
					a = ie ? a.concat( [ 'classid', 'codebase', 'name' ]) : a.concat( [ 'pluginspage' ]);

					// Add attributes to output buffer.
					for (k in a) {
						var n = (k == $.inArray('src', a)) ? 'data' : a[k];
						o += s[a[k]] ? attr(n, s[a[k]]) : '';
					}
					o += '>';

					// Add parameters to output buffer.
					for (k in b) {
						var n = (k == $.inArray('src', b)) ? 'movie' : b[k];
						o += s[b[k]] ? param(n, s[b[k]]) : '';
					}

					// Close and swap.
					o += '</object>';
					e.replaceWith(o);
				}
				return this;
			});
		}
	});
})(jQuery);/*
 * jQuery JSONP Core Plugin 2.1.4 (2010-11-17)
 * 
 * http://code.google.com/p/jquery-jsonp/
 *
 * Copyright (c) 2010 Julian Aubourg
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 */
( function( $ , setTimeout ) {
	
	// ###################### UTILITIES ##
	
	// Noop
	function noop() {
	}
	
	// Generic callback
	function genericCallback( data ) {
		lastValue = [ data ];
	}

	// Add script to document
	function appendScript( node ) {
		head.insertBefore( node , head.firstChild );
	}
	
	// Call if defined
	function callIfDefined( method , object , parameters ) {
		return method && method.apply( object.context || object , parameters );
	}
	
	// Give joining character given url
	function qMarkOrAmp( url ) {
		return /\?/ .test( url ) ? "&" : "?";
	}
	
	var // String constants (for better minification)
		STR_ASYNC = "async",
		STR_CHARSET = "charset",
		STR_EMPTY = "",
		STR_ERROR = "error",
		STR_JQUERY_JSONP = "_jqjsp",
		STR_ON = "on",
		STR_ONCLICK = STR_ON + "click",
		STR_ONERROR = STR_ON + STR_ERROR,
		STR_ONLOAD = STR_ON + "load",
		STR_ONREADYSTATECHANGE = STR_ON + "readystatechange",
		STR_REMOVE_CHILD = "removeChild",
		STR_SCRIPT_TAG = "<script/>",
		STR_SUCCESS = "success",
		STR_TIMEOUT = "timeout",
		
		// Shortcut to jQuery.browser
		browser = $.browser,
		
		// Head element (for faster use)
		head = $( "head" )[ 0 ] || document.documentElement,
		// Page cache
		pageCache = {},
		// Counter
		count = 0,
		// Last returned value
		lastValue,
		
		// ###################### DEFAULT OPTIONS ##
		xOptionsDefaults = {
			//beforeSend: undefined,
			//cache: false,
			callback: STR_JQUERY_JSONP,
			//callbackParameter: undefined,
			//charset: undefined,
			//complete: undefined,
			//context: undefined,
			//data: "",
			//dataFilter: undefined,
			//error: undefined,
			//pageCache: false,
			//success: undefined,
			//timeout: 0,
			//traditional: false,		
			url: location.href
		};
	
	// ###################### MAIN FUNCTION ##
	function jsonp( xOptions ) {
		
		// Build data with default
		xOptions = $.extend( {} , xOptionsDefaults , xOptions );
		
		// References to xOptions members (for better minification)
		var completeCallback = xOptions.complete,
			dataFilter = xOptions.dataFilter,
			callbackParameter = xOptions.callbackParameter,
			successCallbackName = xOptions.callback,
			cacheFlag = xOptions.cache,
			pageCacheFlag = xOptions.pageCache,
			charset = xOptions.charset,
			url = xOptions.url,
			data = xOptions.data,
			timeout = xOptions.timeout,
			pageCached,
			
			// Abort/done flag
			done = 0,
			
			// Life-cycle functions
			cleanUp = noop;
		
		// Create the abort method
		xOptions.abort = function() { 
			! done++ &&	cleanUp(); 
		};

		// Call beforeSend if provided (early abort if false returned)
		if ( callIfDefined( xOptions.beforeSend, xOptions , [ xOptions ] ) === false || done ) {
			return xOptions;
		}
			
		// Control entries
		url = url || STR_EMPTY;
		data = data ? ( (typeof data) == "string" ? data : $.param( data , xOptions.traditional ) ) : STR_EMPTY;
			
		// Build final url
		url += data ? ( qMarkOrAmp( url ) + data ) : STR_EMPTY;
		
		// Add callback parameter if provided as option
		callbackParameter && ( url += qMarkOrAmp( url ) + encodeURIComponent( callbackParameter ) + "=?" );
		
		// Add anticache parameter if needed
		! cacheFlag && ! pageCacheFlag && ( url += qMarkOrAmp( url ) + "_" + ( new Date() ).getTime() + "=" );
		
		// Replace last ? by callback parameter
		url = url.replace( /=\?(&|$)/ , "=" + successCallbackName + "$1" );
		
		// Success notifier
		function notifySuccess( json ) {
			! done++ && setTimeout( function() {
				cleanUp();
				// Pagecache if needed
				pageCacheFlag && ( pageCache [ url ] = { s: [ json ] } );
				// Apply the data filter if provided
				dataFilter && ( json = dataFilter.apply( xOptions , [ json ] ) );
				// Call success then complete
				callIfDefined( xOptions.success , xOptions , [ json , STR_SUCCESS ] );
				callIfDefined( completeCallback , xOptions , [ xOptions , STR_SUCCESS ] );
			} , 0 );
		}
		
		// Error notifier
		function notifyError( type ) {
			! done++ && setTimeout( function() {
				// Clean up
				cleanUp();
				// If pure error (not timeout), cache if needed
				pageCacheFlag && type != STR_TIMEOUT && ( pageCache[ url ] = type );
				// Call error then complete
				callIfDefined( xOptions.error , xOptions , [ xOptions , type ] );
				callIfDefined( completeCallback , xOptions , [ xOptions , type ] );
			} , 0 );
		}
	    
		// Check page cache
		pageCacheFlag && ( pageCached = pageCache[ url ] ) 
			? ( pageCached.s ? notifySuccess( pageCached.s[ 0 ] ) : notifyError( pageCached ) )
			:
			// Initiate request
			setTimeout( function( script , scriptAfter , timeoutTimer ) {
				
				if ( ! done ) {
				
					// If a timeout is needed, install it
					timeoutTimer = timeout > 0 && setTimeout( function() {
						notifyError( STR_TIMEOUT );
					} , timeout );
					
					// Re-declare cleanUp function
					cleanUp = function() {
						timeoutTimer && clearTimeout( timeoutTimer );
						script[ STR_ONREADYSTATECHANGE ]
							= script[ STR_ONCLICK ]
							= script[ STR_ONLOAD ]
							= script[ STR_ONERROR ]
							= null;
						head[ STR_REMOVE_CHILD ]( script );
						scriptAfter && head[ STR_REMOVE_CHILD ]( scriptAfter );
					};
					
					// Install the generic callback
					// (BEWARE: global namespace pollution ahoy)
					window[ successCallbackName ] = genericCallback;

					// Create the script tag
					script = $( STR_SCRIPT_TAG )[ 0 ];
					script.id = STR_JQUERY_JSONP + count++;
					
					// Set charset if provided
					if ( charset ) {
						script[ STR_CHARSET ] = charset;
					}
					
					// Callback function
					function callback( result ) {
						( script[ STR_ONCLICK ] || noop )();
						result = lastValue;
						lastValue = undefined;
						result ? notifySuccess( result[ 0 ] ) : notifyError( STR_ERROR );
					}
										
					// IE: event/htmlFor/onclick trick
					// One can't rely on proper order for onreadystatechange
					// We have to sniff since FF doesn't like event & htmlFor... at all
					if ( browser.msie ) {
						
						script.event = STR_ONCLICK;
						script.htmlFor = script.id;
						script[ STR_ONREADYSTATECHANGE ] = function() {
							/loaded|complete/.test( script.readyState ) && callback();
						};
						
					// All others: standard handlers
					} else {					
					
						script[ STR_ONERROR ] = script[ STR_ONLOAD ] = callback;
						
						browser.opera ?
							
							// Opera: onerror is not called, use synchronized script execution
							( ( scriptAfter = $( STR_SCRIPT_TAG )[ 0 ] ).text = "jQuery('#" + script.id + "')[0]." + STR_ONERROR + "()" )
							
							// Firefox: set script as async to avoid blocking scripts (3.6+ only)
							: script[ STR_ASYNC ] = STR_ASYNC;
							
						;
					}
					
					// Set source
					script.src = url;
					
					// Append main script
					appendScript( script );
					
					// Opera: Append trailing script
					scriptAfter && appendScript( scriptAfter );
				}
				
			} , 0 );
		
		return xOptions;
	}
	
	// ###################### SETUP FUNCTION ##
	jsonp.setup = function( xOptions ) {
		$.extend( xOptionsDefaults , xOptions );
	};

	// ###################### INSTALL in jQuery ##
	$.jsonp = jsonp;
	
} )( jQuery , setTimeout );
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
(function($) {
//Converts XML DOM to JSON
$.extend ({
	xmlToJSON: function(xdoc) {
	try {
		if(!xdoc){ return null; }
		var tmpObj = {};
			tmpObj.typeOf = "JSXBObject";
		var xroot = (xdoc.nodeType == 9)?xdoc.documentElement:xdoc;
			tmpObj.RootName = xroot.nodeName || "";
		if(xdoc.nodeType == 3 || xdoc.nodeType == 4) {
			return xdoc.nodeValue;
		}
		var isNumeric = function(s) {
			var testStr = "";
			if(s && typeof s == "string") { testStr = s; }
			var pattern = /^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/;
			return pattern.test(testStr);
		};
		//Alters attribute and collection names to comply with JS
		function formatName(name) {
			var regEx = /-/g;
			var tName = String(name).replace(regEx,"_");
			return tName;
		}			
		//Set Attributes of an object
		function setAttributes(obj, node) {
			if(node.attributes.length > 0) {
				var a = node.attributes.length-1;
				var attName;
				obj._attributes = [];
				do { //Order is irrelevant (speed-up)
					attName = String(formatName(node.attributes[a].name));
					obj._attributes.push(attName);				
					obj[attName] = $.trim(node.attributes[a].value);
				} while(a--);
			}
		}
		//Set collections
		function setHelpers(grpObj) {
			//Selects a node withing array where attribute = value
			grpObj.getNodeByAttribute = function(attr, obj) {
				if(this.length > 0) {
					var cNode;
					var maxLen = this.length -1;
					try {
						do {
							cNode = this[maxLen];
							if(cNode[attr] == obj) {
								return cNode;
							}
						} while(maxLen--);
					} catch(e) {return false;}
					return false;
				}
			};
			
			grpObj.contains = function(attr, obj) {
				if(this.length > 0) {
					var maxLen = this.length -1;
					try {
						do {
							if(this[maxLen][attr] == obj) {
								return true;
							}
						} while(maxLen--);
					} catch(e) {return false;}
					return false;
				}
			};
			
			grpObj.indexOf = function(attr, obj) {
				var pos = -1;
				if(this.length > 0) {
					var maxLen = this.length -1;
					try {
						do {
							if(this[maxLen][attr] == obj) {
								pos = maxLen;
							}
						} while(maxLen--);
					} catch(e) {return -1;}
					return pos;
				}
			};
			
			grpObj.SortByAttribute = function(col, dir) {
				if(this.length) {				
					function getValue(pair, idx) {
						var out = pair[idx];
						out = (isNumeric(out))?parseFloat(out):out;
						return out;
					}
					function sortFn(a, b) {
						var res = 0;
						var tA, tB;						
						tA = getValue(a, col);
						tB = getValue(b, col);
						if(tA < tB) { res = -1;	} else if(tB < tA) { res = 1; }
						if(dir) {
							res = (dir.toUpperCase() == "DESC")?(0 - res):res;
						}
						return res;
					}
					this.sort(sortFn);
				}
			};
			
			grpObj.SortByValue = function(dir) {
				if(this.length) {
					function getValue(pair) {
						var out = pair.Text;
						out = (isNumeric(out))?parseFloat(out):out;
						return out;
					}
					function sortFn(a, b) {
						var res = 0;
						var tA, tB;
						tA = getValue(a);
						tB = getValue(b);
						if(tA < tB) { res = -1;	} else if(tB < tA) { res = 1; }
						if(dir) {
							res = (dir.toUpperCase() == "DESC")?(0 - res):res;
						}
						return res;
					}
					this.sort(sortFn);
				}
			};
			grpObj.SortByNode = function(node, dir) {
				if(this.length) {
					function getValue(pair, node) {
						var out = pair[node][0].Text;
						out = (isNumeric(out))?parseFloat(out):out;
						return out;
					}
					function sortFn(a, b) {
						var res = 0;
						var tA, tB;
						tA = getValue(a, node);
						tB = getValue(b, node);
						if(tA < tB) { res = -1;	} else if(tB < tA) { res = 1; }
						if(dir) {
							res = (dir.toUpperCase() == "DESC")?(0 - res):res;
						}
						return res;
					}
					this.sort(sortFn);
				}
			};
		}
		//Recursive JSON Assembler
		//Set Object Nodes
		function setObjects(obj, node) {
			var elemName;	//Element name
			var cnode;	//Current Node
			var tObj;	//New subnode
			var cName = "";
			if(!node) { return null; }				
			//Set node attributes if any
			if(node.attributes.length > 0){setAttributes(obj, node);}				
			obj.Text = "";
			if(node.hasChildNodes()) {
				var nodeCount = node.childNodes.length - 1;	
				var n = 0;
				do { //Order is irrelevant (speed-up)
					cnode = node.childNodes[n];
					switch(cnode.nodeType) {
						case 1: //Node
						//Process child nodes
						obj._children = [];
						//SOAP XML FIX to remove namespaces (i.e. soapenv:)
						elemName = (cnode.localName)?cnode.localName:cnode.baseName;
						elemName = formatName(elemName);
						if(cName != elemName) { obj._children.push(elemName); }
							//Create sub elemns array
							if(!obj[elemName]) {
								obj[elemName] = []; //Create Collection
							}
							tObj = {};
							obj[elemName].push(tObj);
							if(cnode.attributes.length > 0) {
								setAttributes(tObj, cnode);
							}
							//Set Helper functions (contains, indexOf, sort, etc);
							if(!obj[elemName].contains) {
								setHelpers(obj[elemName]);
							}	
						cName = elemName;
						if(cnode.hasChildNodes()) {
							setObjects(tObj, cnode); //Recursive Call
						}
						break;
						case 3: //Text Value
						obj.Text += $.trim(cnode.nodeValue);
						break;
						case 4: //CDATA
						obj.Text += (cnode.text)?$.trim(cnode.text):$.trim(cnode.nodeValue);
						break;
					}
				} while(n++ < nodeCount);
			}
		}						
		//RUN
		setObjects(tmpObj, xroot);
		//Clean-up memory
		xdoc = null;
		xroot = null;
		return tmpObj;
		
		} catch(e) {
			return null;	
		}
	}		
});

//Converts Text to XML DOM
$.extend({
	textToXML: function(strXML) {
		var xmlDoc = null;
		try {
			xmlDoc = ($.browser.msie)?new ActiveXObject("Microsoft.XMLDOM"):new DOMParser();
			xmlDoc.async = false;
		} catch(e) {throw new Error("XML Parser could not be instantiated");}
		var out;
		try {
			if($.browser.msie) {
				out = (xmlDoc.loadXML(strXML))?xmlDoc:false;
			} else {		
				out = xmlDoc.parseFromString(strXML, "text/xml");
			}
		} catch(e) { throw new Error("Error parsing XML string"); }
		return out;
	}
});
})(jQuery);


/*
### jQuery XML to JSON Plugin v1.0 - 2008-07-01 ###
* http://www.fyneworks.com/ - diego@fyneworks.com
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
###
Website: http://www.fyneworks.com/jquery/xml-to-json/
*//*
# INSPIRED BY: http://www.terracoder.com/
          AND: http://www.thomasfrank.se/xml_to_json.html
											AND: http://www.kawa.net/works/js/xml/objtree-e.html
*//*
This simple script converts XML (document of code) into a JSON object. It is the combination of 2
'xml to json' great parsers (see below) which allows for both 'simple' and 'extended' parsing modes.
*/
//Avoid collisions
if (window.jQuery) (function($) {
// Add function to jQuery namespace
$.extend({
	// converts xml documents and xml text to json object
	xml2json: function(xml, extended) {
		if (!xml) return {};
		// quick fail
		//### PARSER LIBRARY
		// Core function
		function parseXML(node, simple) {
			if (!node) return null;
			var txt = '',
			obj = null,
			att = null;
			var nt = node.nodeType,
			nn = jsVar(node.localName || node.nodeName);
			var nv = node.text || node.nodeValue || '';
			/*DBG*/
			//if(window.console) ;
			if (node.childNodes) {
				if (node.childNodes.length > 0) {
					/*DBG*/
					//if(window.console) ;
					$.each(node.childNodes,
					function(n, cn) {
						var cnt = cn.nodeType,
						cnn = jsVar(cn.localName || cn.nodeName);
						var cnv = cn.text || cn.nodeValue || '';
						/*DBG*/
						//if(window.console) ;
						if (cnt == 8) {
							/*DBG*/
							//if(window.console) ;
							return;
							// ignore comment node
						}
						else if (cnt == 3 || cnt == 4 || !cnn) {
							// ignore white-space in between tags
							if (cnv.match(/^\s+$/)) {
								/*DBG*/
								//if(window.console) ;
								return;
							};
							/*DBG*/
							//if(window.console) ;
							txt += cnv.replace(/^\s+/, '').replace(/\s+$/, '');
							// make sure we ditch trailing spaces from markup
						}
						else {
							/*DBG*/
							//if(window.console) ;
							obj = obj || {};
							if (obj[cnn]) {
								/*DBG*/
								//if(window.console) ;
								if (!obj[cnn].length) obj[cnn] = myArr(obj[cnn]);
								obj[cnn][obj[cnn].length] = parseXML(cn, true
								/* simple */
								);
								obj[cnn].length = obj[cnn].length;
							}
							else {
								/*DBG*/
								//if(window.console) ;
								obj[cnn] = parseXML(cn);
							};
						};
					});
				};
				//node.childNodes.length>0
			};
			//node.childNodes
			if (node.attributes) {
				if (node.attributes.length > 0) {
					/*DBG*/
					//if(window.console) 
					att = {};
					obj = obj || {};
					$.each(node.attributes,
					function(a, at) {
						var atn = jsVar(at.name),
						atv = at.value;
						att[atn] = atv;
						if (obj[atn]) {
							/*DBG*/
							//if(window.console) ;
							if (!obj[atn].length) obj[atn] = myArr(obj[atn]);
							//[ obj[ atn ] ];
							obj[atn][obj[atn].length] = atv;
							obj[atn].length = obj[atn].length;
						}
						else {
							/*DBG*/
							//if(window.console) ;
							obj[atn] = atv;
						};
					});
					//obj['attributes'] = att;
				};
				//node.attributes.length>0
			};
			//node.attributes
			if (obj) {
				obj = $.extend((txt != '' ? new String(txt) : {}),
				/* {text:txt},*/
				obj || {}
				/*, att || {}*/
				);
				txt = (obj.text) ? (typeof(obj.text) == 'object' ? obj.text: [obj.text || '']).concat([txt]) : txt;
				if (txt) obj.text = txt;
				txt = '';
			};
			var out = obj || txt;
			//;
			if (extended) {
				if (txt) out = {};
				//new String(out);
				txt = out.text || txt || '';
				if (txt) out.text = txt;
				if (!simple) out = myArr(out);
			};
			return out;
		};
		// parseXML
		// Core Function End
		// Utility functions
		var jsVar = function(s) {
			return String(s || '').replace(/-/g, "_");
		};
		var isNum = function(s) {
			return (typeof s == "number") || String((s && typeof s == "string") ? s: '').test(/^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/);
		};
		var myArr = function(o) {
			if (!o.length) o = [o];
			o.length = o.length;
			// here is where you can attach additional functionality, such as searching and sorting...
			return o;
		};
		// Utility functions End
		//### PARSER LIBRARY END
		// Convert plain text to xml
		if (typeof xml == 'string') xml = $.text2xml(xml);

		// Quick fail if not xml (or if this is a node)
		if (!xml.nodeType) return;
		if (xml.nodeType == 3 || xml.nodeType == 4) return xml.nodeValue;

		// Find xml root node
		var root = (xml.nodeType == 9) ? xml.documentElement: xml;

		// Convert xml to json
		var out = parseXML(root, true
		/* simple */
		);

		// Clean-up memory
		xml = null;
		root = null;

		// Send output
		return out;
	},

	// Convert text to XML DOM
	text2xml: function(str) {
		// NOTE: I'd like to use jQuery for this, but jQuery makes all tags uppercase
		//return $(xml)[0];
		var out;
		try {
			var xml = ($.browser.msie) ? new ActiveXObject("Microsoft.XMLDOM") : new DOMParser();
			xml.async = false;
		} catch(e) {
			throw new Error("XML Parser could not be instantiated")
		};
		try {
			if ($.browser.msie) out = (xml.loadXML(str)) ? xml: false;
			else out = xml.parseFromString(str, "text/xml");
		} catch(e) {
			throw new Error("Error parsing XML string")
		};
		return out;
	}
		
}); // extend $
})(jQuery);

