
(function() {



	/* private utility methods */
	// thanks: http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
	function clone(obj) {	
		if (!obj || typeof obj != 'object') { return obj; }		
		var temp = new obj.constructor();	
		for (var key in obj) {	
			if (obj.hasOwnProperty(key)) {
				temp[key] = clone(obj[key]);
			}
		}		
		return temp;
	}


	// stripped from jQuery, thanks John Resig 
	function each(obj, fn) {

		if (!obj) { return; }
		
		var name, i = 0, length = obj.length;
	
		// object
		if (length === undefined) {
			for (name in obj) {
				if (fn.call(obj[name], name, obj[name]) === false) { break; }
			}
			
		// array
		} else {
			for (var value = obj[0];
				i < length && fn.call( value, i, value ) !== false; value = obj[++i]) {				
			}
		}
	
		return obj;
	}


	// convenience
	function el(id) {
		return document.getElementById(id); 	
	}	

	
	// used extensively. a very simple implementation. 
	function extend(to, from, skipFuncs) {
		if (typeof from != 'object') { return to; }
		
		if (to && from) {
			each(from, function(name, value) {
				if (!skipFuncs || typeof value != 'function') {
					to[name] = value;		
				}
			});
		}
		
		return to;
	}


	// push an event listener into existing array of listeners
	function bind(to, evt, fn, repl) {
		to[evt] = to[evt] || [];
		if (repl) {
			var evtidx = (to[evt].length - 1 >= 0) ? to[evt].length - 1 : 0;
			to[evt][evtidx] = fn;
		} else {
			to[evt].push(fn);		
		}
	}


	// fix event inconsistencies across browsers
	function stopEvent(e) {
		e = e || window.event;
		
		if (e.preventDefault) {
			e.stopPropagation();
			e.preventDefault();
			
		} else {
			e.returnValue = false;	
			e.cancelBubble = true;
		} 
		return false;
	}


	function dump(obj, noFuncs, maxrecurse, level) {

		level = level ? level : 0;
		maxrecurse = maxrecurse ? maxrecurse : null;

		var indc = 0;
		var indent = "";
		indc = level ? 4 * level : 0;
		for (var i = 0; i < indc; i++) {
			indent += " ";
		}

		var ret = "";

		ret += "{\n";

		var otype;
		each(obj, function(k,v) {

			otype = typeof v;

			if (otype == 'function' && noFuncs) {
				return true; // continue
			}

			ret += indent + k+": ";

			switch(otype) {
				case 'object':
					if (!maxrecurse || level < maxrecurse) {
						ret += dump(v, noFuncs, maxrecurse, level + 1);
					}
					break;
				case 'function':
					if (!noFuncs) {
						ret += "function()";
					}
					break;
				case 'string':
					ret += "'"+v+"'";
					break;
				default:
					ret += v;
			}

			ret += "\n";
		});

		ret += indent + "}";

		return ret;
	}


	function build(tag, attribs, pElem, style, content) {
		var elem = document.createElement(tag);
		Element.extend(elem);
		if (typeof attribs == 'object') {
			each(attribs, function(name, val) {
				elem[name] = val;
			});
		}
		if (typeof style == 'object') {
			elem.setStyle(style);
		}
		if (typeof content == 'object') {
			if (content.push) { // array of children
				each(content, function() {
					elem.appendChild(this);
				});
			} else { // single child
				elem.appendChild(content);
			}
		} else if (typeof content == 'string') {
			elem.innerHTML = content;
		}
		if (typeof pElem == 'object' && pElem.appendChild != undefined) {
			pElem.appendChild(elem);
		}
		return elem;
	}



	/* launcher object */
	function Launcher(Name, Config) {

		/* Launcher private members */
		var
			self = this,
			bwApi = null,
			listeners = {},
			name = Name,
			config = Config,
			mystatus = {
				size: {}
			};



		/* Launcher public methods */

		/* Launcher constructor */
		self.construct = function() {
			self.name = name;
			self.mystatus = mystatus;

			if (!config.clip) {
				config.clip = {};
				config.clip.freeStream = false;
			}

			mystatus.toolbarIsOpen = config.toolbarOpen;
			mystatus.bwhidden = true;
			mystatus.h264Allowed = false;

			// check for flash
			self.detectFlash();

			// set default type and rate
			if (mystatus.h264Allowed) {
				mystatus.type = config.defaultType || 'flv';
			} else {
				mystatus.type = 'flv';
			}
			mystatus.bitrate = config.defaultRate || 192;

			makeStage(self, mystatus, config);

			self.typeButtonsSet();
			self.rateButtonsSet();

			mystatus.isLoaded = true;
		}

		self.isLoaded = function() {
			return mystatus.isLoaded;
		}

		self.detectFlash = function() {
			var version = flashembed.getVersion();
			if (version[0] > 9 || (version[0] == 9 && version[1] >= 115)) {
				mystatus.h264Allowed = true;
			}
		}

		self.showCanvas = function(onDone) {
			if (!mystatus.isShown) {
				var offsets = document.viewport.getScrollOffsets();
				self.stage.setStyle({ top:(offsets.top + 20)+"px" });
				if (!config.stageElement) {
					self.cover.show();
				}
				self.stage.show();
				mystatus.isShown = true;
			}
			if (typeof onDone == 'function') {
				onDone.call(self);
			}
		}

		self.hideCanvas = function(onDone) {
			if (mystatus.isShown) {
				self.stage.hide();
				if (!config.stageElement) {
					self.cover.hide();
				}
				mystatus.isShown = false;
			}
			if (typeof onDone == 'function') {
				onDone.call(self);
			}
		}

		self.show = function() {
			var offsets = document.viewport.getScrollOffsets();
			self.stage.setStyle({ top:(offsets.top + 20)+"px" });
			if (!config.stageElement) {
				self.cover.appear({duration: 0.5, to: 0.5});
			}
			Effect.BlindDown(self.stage, {duration: 0.5});
			mystatus.isShown = true;
		}

		self.hide = function() {
			if (!config.stageElement) {
				self.cover.fade({duration: 0.5, from: 0.5});
			}
			Effect.BlindUp(self.stage, {duration: 0.5});
			mystatus.isShown = false;
		}

		self.close = function(evt) {
			if (self.playerIsLoaded()) {
				$f(self.swfElem.id).stop();
				//self.unloadPlayer(); // this crashes on a reaload...
			}
			if (bwApi && typeof bwApi.stop == 'function') {
				bwApi.stop();
			}
			self.hide();
		}

		self.resizeSwf = function(elem, newsize, onFinish) {
			self.showCanvas();

			new Effect.Morph(elem, {
				style: {
					width: newsize[0]+'px',
					height: newsize[1]+'px'
				},
				duration: 0.5,
				afterFinish: onFinish
			});
			mystatus.size[elem.id] = newsize;
		}

		self.BWShow = function() {
			if (mystatus.bwhidden) {
				self.swfElemBW.setStyle({ display:'block' });
				mystatus.bwhidden = false;
			}
		}

		self.BWHide = function(elem) {
			if (!mystatus.bwhidden) {
				self.swfElemBW.hide();
				mystatus.bwhidden = true;
			}
		}

		self.resizeRequired = function(elem, newsize) {
			var ret = false;
			each(mystatus.size[elem.id], function(i) {
				if (this != newsize[i]) { ret = true; }
			});
			return ret;
		}


		/* bwcheck methods */
		self.doBWCheck = function() {

			//alert('self.doBWCheck() '+ self);

			// unload the player if it is running, and resize to 0
			// return and run doBWCheck again once its been unloaded
			if (self.playerIsLoaded()) {
				self.unloadPlayer(function() {
					self.resizeSwf(self.swfElem, [0,0], self.doBWCheck);
				});
				return self;
			}

			self.BWShow(); // show the bwchecker

			// if needed, resize the swfElemBW first
			if (self.resizeRequired(self.swfElemBW, config.bwCheck.size)) {
				self.resizeSwf(self.swfElemBW, config.bwCheck.size, self.doBWCheck);
				return self;
			}

			// is bwchecker already loaded or not?
			if (bwApi) {

				//alert('bwcheck is loaded, run it...');
				bwApi.start();

			} else {

				// register the onBWDone event
				self.onBWDone(function() {
					self.setSpeedPrefs(arguments[2]);
					self.bwDoneLink.innerHTML = "Video preferences set successfully!<br />Click here to play video";
					self.bwDoneLink.onclick = function(evt) {
						self.switchPlay();
						self.bwDoneBlock.hide();
					};
					self.bwDoneBlock.show();
				}, 1);

				// start the bwchecker app
				flashembed(self.swfElemBW, {
					src: '/static/swf/BWCheck.swf',
					id: self.name+"_bwapp",
        	version: [9, 0],
	        expressInstall: "/static/swf/expressInstall.swf"
				},
				{
					netConnectionUrl: 'rtmp://'+config.wmsServer+'/bwcheck_improved',
					minUpdateInterval: 100,
					MLName: self.name
				});

			}

		}

		self.unloadBWCheck = function() {
			bwApi = null;
			self.swfElemBW.innerHTML = '';
		}

		/* find the most suitable format and bitrate, and make the setting */
		/* this should be called by bwcheck when it is done measuring */
		self.setSpeedPrefs = function(kbps) {
			kbps = parseFloat(kbps);

			var bestRate = config.bitrates[0];
			for(i = 0; i < config.bitrates.length; i++) {
				if (kbps >= (config.bitrates[i] - 20)) {
					bestRate = config.bitrates[i];
				}
			}
			mystatus.bitrate = bestRate;
	
			if (!mystatus.type) {
				if (mystatus.h264Allowed) {
					mystatus.type = 'h264';
				} else {
					mystatus.type = 'flv';
				}
			}

			self.typeButtonsSet();
			self.rateButtonsSet();
		}


		self.playerIsLoaded = function() {
			if ($f(self.swfElem.id) && $f(self.swfElem.id).isLoaded()) {
				return true;
			} else {
				return false;
			}
		}

		self.unloadPlayer = function(CB) {

			//alert('self.unloadPlayer()');

			if (self.playerIsLoaded()) {
				//alert('self.unloadPlayer has fp and is loaded');
				if ($f(self.swfElem.id).getState() >= 0) {
					mystatus.clipTime = self.getClipTime();
					$f(self.swfElem.id).stop();
					$f(self.swfElem.id).onUnload(function() {
						//alert("callback - player is unloaded");
						$f(self.swfElem.id).onUnload(function(){}, 1);
						if (typeof CB == 'function') {
							CB.call(self);
						}
					}, 1);
					$f(self.swfElem.id).unload(1);
				}
			} else {
				//alert('self.unloadPlayer fp is NOT loaded');
				self.swfElem.innerHTML = "";
				CB.call(self);
			}

		}

		self.getClipTime = function() {
			var state, clipTime;
			if ($f(self.swfElem.id)) {
				state = $f(self.swfElem.id).getState();
				if (state == 3 || state == 4) { // playing or paused
					clipTime = $f(self.swfElem.id).getTime();
					return clipTime;
				}
			}
			return null;
		}

		self.loadVideoType = function(type) {

			mystatus.type = type;

			if (type == 'h264' && !mystatus.h264Allowed) {
				alert("Your system does not support H264 video\nUpgrade your system to Flash Player 10 for h264 video support");
				mystatus.type = 'flv';
			}

			self.typeButtonsSet();
			self.switchPlay();
		}

		self.typeButtonsSet = function() {
			var type, button;
			for(var i = 0; i < config.types.length; i++) {
				type = config.types[i];
				button = el('video_cp_button_'+self.name+'_type_'+type);
				if (type == mystatus.type) {
					button.addClassName('video_cp_button_on');
					el('video_cp_toolbar_title_type_'+self.name).innerHTML = config.typeLabels[type];
				} else {
					button.removeClassName('video_cp_button_on');
				}
			}
			if (config.onCPSettingsChange && typeof config.onCPSettingsChange == 'function') {
				config.onCPSettingsChange.call(this);
			}
		}

		self.loadVideoRate = function(rate) {
			mystatus.bitrate = rate;
			self.rateButtonsSet();
			self.switchPlay();
		}

		self.rateButtonsSet = function() {
			var type, button;
			for(var i = 0; i < config.bitrates.length; i++) {
				rate = config.bitrates[i];
				button = el('video_cp_button_'+self.name+'_rate_'+rate);
				if (rate == mystatus.bitrate) {
					button.addClassName('video_cp_button_on');
					el('video_cp_toolbar_title_bitrate_'+self.name).innerHTML = config.bitrateLabels[rate];
				} else {
					button.removeClassName('video_cp_button_on');
				}
			}

			if (config.onCPSettingsChange && typeof config.onCPSettingsChange == 'function') {
				config.onCPSettingsChange.call(this);
			}
		}

		self.switchPlay = function() {

			//alert('switchPlay - '+self.swfElem.id);

			if (bwApi) {
				// hide the flash container and the 'done' block
				if (typeof bwApi.stop == 'function') {
					bwApi.stop();
				}
				self.BWHide();
				self.bwDoneBlock.hide();
			}

			// do we need to resize?
			var sizeVar = config.sizes[mystatus.bitrate];
			if (self.resizeRequired(self.swfElem, [sizeVar[0],(sizeVar[1] + config.controlbarHeight)])) {
				//alert('switchPlay - resize, reload - '+self.swfElem.id);
				// resize, reload

				if (self.playerIsLoaded()) {
					//alert('switchPlay self.playerIsLoaded() = true');

					mystatus.clipTime = self.getClipTime();
					$f(self.swfElem.id).stop();

					// we need to unload first, then resize
					// deregister our onUnload event when we are done
					$f(self.swfElem.id).onUnload(function() {
						self.switchPlayResize();
						$f(self.swfElem.id).onUnload(function(){}, 1);
					}, 1);

					$f(self.swfElem.id).unload(1);

				} else {
					// resize, loading new player when we are done resizing
					//alert('switchPlay - resize, reload - load new player ');
					self.switchPlayResize();
				}

			} else if (!self.playerIsLoaded()) {
				//alert('switchPlay - no player is loaded, start new player, no resize reqd');
				// no player exists, start new player, no resize reqd
				self.switchPlayReload();

			} else {

				//alert('switchPlay - load new clip without reloading the player');
				// load new clip without reloading the player

				mystatus.clipTime = self.getClipTime();

				//alert("mystatus.clipTime = "+mystatus.clipTime)

				// load the new clip
				self.switchPlayLoadClip();

			}
		}

		self.switchPlayResize = function() {

			var sizeVar;
			if (config.fixedSize) {
				sizeVar = config.fixedSize;
			} else {
				sizeVar = config.sizes[mystatus.bitrate];
			}

			self.resizeSwf(self.swfElem, [sizeVar[0],(sizeVar[1] + config.controlbarHeight)], self.switchPlayReload);
		}

		self.switchPlayReload = function() {

			//alert('self.switchPlayReload');

			if ($f(self.swfElem.id)) {
				$f(self.swfElem.id).load(self.switchPlayLoadClip);
			} else {
				var CB = null;
				if (mystatus.clipTime) {
					CB = function(clip) {
						alert("reload onStart("+clip+") @ "+mystatus.clipTime+"");
						this.seek(Math.floor(mystatus.clipTime));
					};
				}
				self.createPlayer(null, CB);
			}
		}	

		self.switchPlayLoadClip = function() {

			//alert("switchPlayLoadClip()");

			// load the new clip
			$f(self.swfElem.id).play(self.getClipUrl());

			// seek to clipTime on start of playback
			if (mystatus.clipTime > 1) {
				$f(self.swfElem.id).getClip().onBegin(function(clip) {
					//alert("loadclip onStart("+clip+") @ "+mystatus.clipTime+"");
					clip.onBegin(function(){}, 1);
					this.seek(Math.floor(mystatus.clipTime));
				}, 1);
			}

		}

		self.getClipUrl = function() {
			var prefix = config.prefixes[mystatus.type];
			var extension = config.fileExtensions[mystatus.type];
			return prefix + config.clip.name + '_' + mystatus.bitrate + '.' + extension;
		}


		self.clipConf = function(cConf) {
			config.clip.name = cConf.name;
			config.clip.userId = cConf.userId;
			config.clip.token = cConf.token;

			//$f(self.swfElem.id).getClip().url = self.getClipUrl();
			$f(self.swfElem.id).onLoad(function() {
				this.getClip().update({url:self.getClipUrl()});
				this.onLoad(function(){});
			}, 1);
		}


		/* startup the video player */
		self.createPlayer = function(CBonLoad, CBClipOnBegin) {
	
			//alert(self.swfElem.id + " --  createPlayer("+CBonLoad+", "+CBClipOnBegin+")\n");
			//alert('rtmp://'+config.wmsServer+'/'+(config.clip.freeStream ? 'freestreams' : 'authstreams'));
			CBonLoad = CBonLoad ? CBonLoad : null;
			CBClipOnStart = CBClipOnBegin ? CBClipOnBegin : null;

			var url = self.getClipUrl();

			// /static/swf/flowplayer/flowplayer.commercial-3.1.1-release.swf
			$f(self.swfElem.id, {
				src: "/static/swf/flowplayer/flowplayer.commercial-3.1.3.swf",
				version: [9, 0],
				expressInstall: "/static/swf/expressInstall.swf"
			}, {
	
				//onLoad: CBonLoad,

				clip: {
					provider: 'rtmp_stream',
					scaling: 'fit',
					accelerated: true,
					onBegin: CBClipOnBegin,
					url: url,
					tfcSubscribe: (config.clip.freeStream ? false : true)
				},

				plugins: {
					rtmp_stream: {
						url: '/static/swf/flowplayer/flowplayer.rtmp-3.1.2_tfc.swf',
						netConnectionUrl: 'rtmp://'+config.wmsServer+'/'+(config.clip.freeStream ? 'freestreams' : 'authstreams'),
						userId: config.clip.userId,
						authHash: config.clip.token
					},
					controls: {
						url: '/static/swf/flowplayer/flowplayer.controls-tube-3.1.3.swf' 
					},
				}
				//,
				//debug : true,
				//log : { 
				//	level : 'debug',
				//	filter : 'org.flowplayer.rtmp.*'
				//}
			});

		}

		self.toggleCpContent = function(evt) {
			if (mystatus.toolbarIsOpen) {
				self.toolbarClose();
			} else {
				self.toolbarOpen();
			}
		}

		self.toolbarClose = function() {
			Effect.BlindUp('ML_cp_content_'+self.name, { duration: 0.5 });
			mystatus.toolbarIsOpen = false;
			el('video_cp_toolbar_button_min_'+self.name).innerHTML = '+';
		}

		self.toolbarOpen = function() {
			Effect.BlindDown('ML_cp_content_'+self.name, { duration: 0.5 });
			mystatus.toolbarIsOpen = true;
			el('video_cp_toolbar_button_min_'+self.name).innerHTML = '-';
		}

		self.helpPopup = function() {
			window.open('/jit/video_help_popup', 'jit_video_help', 'width=550,height=350,top=100,left=100,location=no,directories=no,resizable=yes,status=no,toolbar=no,scrollbars=yes');
		}




















		/* launcher event handlers */
		each(("BWLoad,BWCheck,BWDone,BWDoneClick").split(","),
			function() {		 
				var name = "on" + this;
			
				// normal event
				self[name] = function(fn, repl) {
					bind(listeners, name, fn, repl);
					return self;
				};
			}
		);
	
		self._fireEvent = function(evt, args) {

			// internal bwcheck onLoad
			if (!bwApi && evt == 'onBWLoad') {
				bwApi = el(self.name+"_bwapp");
			}

			var ret = true;
			each(listeners[evt], function() {
				ret = this.apply(self, args);
			});
			return ret;			
		};
			
 




		// add us to the array of launchers
		launchers.push(self);

		// call constructor
		self.construct();

		if (typeof config.onLoad == 'function') {
			config.onLoad.call(self);
		}

	}


	function makeCover(launcher) {

		var exCover = $('ML_cover');
		if (exCover) {
			launcher.cover = exCover;
		} else {
			var body = document.getElementsByTagName('body')[0];
			launcher.cover = build('div', {
				id: 'ML_cover',
				className: 'mediaLauncher_cover'
			}, body, {
				display: 'none',
				position: 'fixed',
				left: '0px',
				top: '0px',
				width: '100%',
				height: '100%',
				background: '#000',
				opacity: 0.5
			});
		}
	}


	function makeStage(launcher, launcherStatus, launcherConfig) {

		makeCover(launcher);

		// construct the html for the launcher
		var body = document.getElementsByTagName('body')[0];

		if (launcherConfig.stageElement) {
			launcher.stage = launcherConfig.stageElement;
		} else {
			launcher.stage = build('div', {
				id: 'ML_stage_'+launcher.name,
				className: 'mediaLauncher_stage'
			}, body, {
				display: 'none',
				overflow:'auto',
				position: 'absolute',
				left: '0px',
				top: '0px',
				width: '100%',
				height: '1000px'
			});
		}

		if (launcherConfig.stageElement) {

			launcher.canvas = build('div', {
				id: 'ML_canvas_'+launcher.name,
				className: 'mediaLauncher_canvas'
			}, launcher.stage, {
				background: '#fff',
				padding: '15px',
				border: '1px solid #000',
				marginLeft: 'auto',
				marginRight: 'auto'
			});

		} else {
			launcher.canvas = build('div', {
				id: 'ML_canvas_'+launcher.name,
				className: 'mediaLauncher_canvas'
			}, launcher.stage, {
				width: '700px',
				background: '#fff',
				padding: '15px',
				border: '1px solid #000',
				marginLeft: 'auto',
				marginRight: 'auto',
				marginTop: '10px',
				marginBottom: '10px'
			}, '<div id="mediaLauncher_canvas_close_'+launcher.name+'" class="canvasCloseButton" style="float:right;"><img src="/static/images/closebutton.gif" width="13" height="13" border="0" style="cursor:normal;"></div>');
	
			$('mediaLauncher_canvas_close_'+launcher.name).observe('click', launcher.close.bindAsEventListener(launcher));
		}

		launcher.swfElem = build('a', {
			id: 'ML_swf_'+launcher.name
		}, launcher.canvas, {
			display: 'block',
			width: '0px',
			height: '0px',
			background: 'black',
			marginLeft: 'auto',
			marginRight: 'auto'
		});
		launcherStatus.size['ML_swf_'+launcher.name] = [0,0];

		launcher.swfElemBW = build('a', {
			id: 'ML_swf_BW_'+launcher.name
		}, launcher.canvas, {
			display: 'none',
			width: '0px',
			height: '0px',
			background: 'black',
			marginLeft: 'auto',
			marginRight: 'auto',
			border: '1px solid black'
		});
		launcherStatus.size['ML_swf_BW_'+launcher.name] = [0,0];

		launcher.bwDoneBlock = build('div', {
			id: 'ML_BWDoneBlock_'+launcher.name,
		}, launcher.canvas, {
			display:'none',
			padding:'10px',
			textAlign: 'center',
			fontSize: '16px'
		});

		launcher.bwDoneLink = build('a', {
			id: 'ML_BWDoneLink_'+launcher.name,
			href: 'javascript:void(0);'
		}, launcher.bwDoneBlock, {
			fontWeight: 'bold'
		});


		// video control panel
		launcher.cp = build('div', {
			id: 'ML_cp_'+launcher.name,
			className: 'video_cp'
		}, launcher.canvas, {
			width:'500px',
			marginTop:'10px',
			marginLeft: 'auto',
			marginRight: 'auto'
		});

		var tbbContent = launcherStatus.toolbarIsOpen ? '-' : '+';

		launcher.cpToolbar = build('div', {
			id: 'ML_cp_toolbar'+launcher.name,
			className: 'video_cp_toolbar'
		}, launcher.cp, {}, 
			'<div id="video_cp_toolbar_button_min_'+launcher.name+'" class="video_cp_toolbar_button_min" style="float:right;">'+tbbContent+'</div><div class="video_cp_toolbar_content"><span id="video_cp_toolbar_title_'+launcher.name+'" style="padding-right:10px; font-weight:bold;">Video Options</span><span class="video_cp_toolbar_title1" id="video_cp_toolbar_title_type_'+launcher.name+'"></span><span class="video_cp_toolbar_title1" id="video_cp_toolbar_title_bitrate_'+launcher.name+'"></span></div>'
		);

		launcher.cpToolbar.observe('dblclick', function(e) {
			this.toggleCpContent();
		}.bindAsEventListener(launcher));

		$('video_cp_toolbar_button_min_'+launcher.name).observe('click', function(e) {
			this.toggleCpContent();
		}.bindAsEventListener(launcher));


		var contentStyle = !launcherStatus.toolbarIsOpen ? {display:'none'} : {};
		launcher.cpContent = build('div', {
			id: 'ML_cp_content_'+launcher.name
		}, launcher.cp, contentStyle);

		var cpButtonBlock = build('div', {
			className: 'video_cp_sub'
		}, launcher.cpContent, {
			marginLeft: 'auto',
			marginRight: 'auto',
			textAlign:'center'
		});

		var row, cell;
		var cpButtonTable = build('table', {
			className: 'video_cp_buttontable'
		}, cpButtonBlock, {
			marginLeft: 'auto',
			marginRight: 'auto',
		});
		row = cpButtonTable.insertRow(-1);
		cell = row.insertCell(-1);
		cell.className = 'video_cp_buttontable_header';
		cell.innerHTML = 'Video Format';
		cell = row.insertCell(-1);
		cell.className = 'video_cp_buttontable_header';
		cell.innerHTML = 'Quality';

		// create the buttons in cpButtonTable
		// how many rows do we have
		var numrows = 0, i = 0, button = null;
		numrows = launcherConfig.types.length > launcherConfig.bitrates.length ? launcherConfig.types.length : launcherConfig.bitrates.length;
		for (i = 0; i < numrows; i++) {
			row = cpButtonTable.insertRow(-1);

			// type button
			cell = row.insertCell(-1);
			if (launcherConfig.types[i]) {
				button = build('div', {
					id: 'video_cp_button_'+launcher.name+'_type_'+launcherConfig.types[i],
					className: 'video_cp_button'
				}, cell, {}, ' '+launcherConfig.typeLabels[launcherConfig.types[i]]+' ');
				button.onclick = function(evt, type) { 
					launcher.loadVideoType(type);
				}.bindAsEventListener(launcher, launcherConfig.types[i]);
			}

			// bitrate button
			cell = row.insertCell(-1);
			if (launcherConfig.bitrates[i]) {
				button = build('div', {
					id: 'video_cp_button_'+launcher.name+'_rate_'+launcherConfig.bitrates[i],
					className: 'video_cp_button'
				}, cell, {}, ' '+launcherConfig.bitrateLabels[launcherConfig.bitrates[i]]+' ');
				button.onclick = function(evt, rate) { 
					launcher.loadVideoRate(rate);
				}.bindAsEventListener(launcher, launcherConfig.bitrates[i]);
			}

		}


		var bwLinkBlock = build('div', {
			id: "video_fc_bandwidth_info_result_"+launcher.name
		}, launcher.cpContent, {
			marginTop: '10px',
			marginBottom: '10px',
			marginLeft: 'auto',
			marginRight: 'auto',
			textAlign:'center',
			border:'1px solid #94b7d3',
			padding: '10px', 
			width:'400px'
		}, '<a href="javascript:void(0);" id="ML_cp_bwlink_'+launcher.name+'">Run bandwidth test</a> (Auto-set video settings)');

		el('ML_cp_bwlink_'+launcher.name).onclick = function(evt) {
			launcher.doBWCheck();
		};


		var bwLinkBlock = build('div', {}, launcher.cpContent, {
			marginLeft: 'auto',
			marginRight: 'auto',
			textAlign:'center',
			padding: '10px'
		});

		var helpLink = build('a', {
			id: 'video_fc_help_link_'+launcher.name,
			href: 'javascript:;'
		}, bwLinkBlock, {}, 'Need help with video settings?');

		helpLink.onclick = function(e) {
			window.open('/jit/video_help_popup', 'vhelp_pop', 'left=100, top=100, width=550, height=350, directories=no, location=no, menubar=no, resizable=yes, status=no, titlebar=no, toolbar=no, scrollbars=yes');
		};

		if (!launcherConfig.stageElement) {

			var closeButtonLinkBlock = build('div', {
				id: 'closeButtonLinkBlock_'+launcher.name
			}, launcher.canvas, {
				marginLeft: 'auto',
				marginRight: 'auto',
				marginTop: '20px',
				textAlign:'center',
				padding: '3px 10px 3px 10px',
				background: '#94b7d3',
				border:'1px solid #000',
				width:'150px',
				fontWeight:'bold',
				cursor: 'pointer'
			}, 'Close Video Player');
			Element.extend(closeButtonLinkBlock);
			closeButtonLinkBlock.observe('click', launcher.close.bindAsEventListener(launcher));


			var helpLinkBlock = build('div', {
				id: 'helpLink_'+launcher.name
			}, launcher.canvas, {
				marginLeft: 'auto',
				marginRight: 'auto',
				marginTop: '10px',
				textAlign:'center',
				padding: '3px 10px 3px 10px',
				fontWeight:'normal',
				cursor: 'pointer',
				textDecoration: 'underline'
			}, 'Need help viewing video?');
			Element.extend(helpLinkBlock);
			helpLinkBlock.observe('click', launcher.helpPopup.bindAsEventListener(launcher));

		}

	}



	var launchers = [];

	window.mediaLauncher = window.$ML = function() {

		var instance = null;
		var argc = arguments.length;
		var argv = arguments;

		// with no arguments, we want to return the first loaded instance
		if (!argc) {
			each(launchers, function(name, value) {
				if (this.isLoaded()) {
					instance = this;
					return false;
				}
			});
			return instance || launchers[0];
		}


		// 1 arg - return the loaded instance with the specified name 
		if (argc == 1) {
			each(launchers, function() {
				if (this.name == argv[0] && this.isLoaded()) {
					instance = this;
					return false;
				}
			});
			return instance;
		}

		// 2 args, create a new launcher with the specified name
		if (argc == 2) {
			return new Launcher(argv[0], argv[1]);
		}

		return null;
	}







	/* add some utility methods to the $ML function */
	extend(window.$ML, {

		// called by Flash External Interface 		
		fireEvent: function() {
			var a = [].slice.call(arguments);
			var p = $ML(a[0]);
			return p ? p._fireEvent(a[1], a.slice(2)) : null;
		},

		// utility methods
		each: each,
		dump: dump,
		extend: extend
	});





})();

