if (jQuery && Modernizr) {
(function( window, undefined ) {	
var document = window.document;
function UVPConfigParser(playerInstance) {
	
	var jsonData,
		loadBandwidth,
		loadCallback,
		chapterData;
		
		
	//--------------------------------------------------------------------------
	//
	//  Private methods
	//
	//--------------------------------------------------------------------------
	
	/*
		Create a chapters object from the loaded json configuration object.
		This involves filtering the jsonData by selecting the appropriate source and bandwidth.
	*/
	function parseChapters() {
		var source = jsonData.source,
			bitrates = [],
			allowFilter,
			contents,
			contentType,
			contentIndex = -1,
			currentBitrate = 0,
			chapters,
			urls,
			contentIndicies,
			i,
			j;
		
		// grab correct source
		if (source instanceof Array) {
			if (playerInstance.params.sourceID) {
				for (i = 0; i < source.length; ++i) {
					if (source[i].id && source[i].id === playerInstance.params.sourceID) {
						source = source[i];
						break;
					}
				}
			}
			
			// if a source hasn't been chosen --> default to the first source in the array
			if (source instanceof Array) {
				source = source[0];
			}
		}

		// grab chapters
		chapters = source.chapter;
		if (!(chapters instanceof Array)) {
			chapters = [chapters];
		}
		
		// grab the content indicies
		contentIndicies = [];
		for (i = 0; i < chapters.length; i++) {
			//reset the content index
			contentIndex = -1;
			
			//grab the content type
			contentType = chapters[i].contentType.toLowerCase();

			//grab the content nodes (place in array if necessary for consistency)
			contents = chapters[i].content;
			if (!(contents instanceof Array)) {
				contents = [contents];
			}

			// find url
			if (contentType === "video") {
				// grab proper bitrate
				if (loadBandwidth > 0) {
					for (j = 0; j < contents.length; j++) {
						if (contents[j].bitrate * 1024 <= loadBandwidth && contents[j].bitrate * 1024 >= currentBitrate) {
							contentIndex = j;
							currentBitrate = contents[j].bitrate * 1024;
						}
						
						urls = contents[j].url;
						if (!(urls instanceof Array)) {
							urls = [urls];
						}
						contents[j].url = urls;
					}
				}

				//save selected content index
				contentIndex = contentIndex === -1 ? 0 : contentIndex;
				contentIndicies.push(contentIndex);
				
				//save selected bitrate
				bitrates.push(contents[contentIndex].bitrate);
				
				chapters[i].content = contents;
			}
			else if (playerInstance.playerType === "html5") {
				uvp.log("ConfigParser - Ignoring chapter " + i + ": \"" + chapters[i].name + "\".  The HTML player does not support a contentType of \"" + contentType + "\"");
				chapters.splice(i--, 1);
			}
		}
		
		allowFilter = source.allowFilter && source.allowFilter.toLowerCase() === "true";
		chapterData = {chapters: chapters, contentIndicies: contentIndicies, allowFilter: allowFilter};
		
		// dispatch source selected event
		playerInstance.dispatchEvent("uvpSourceSelected", [source.id]);
		playerInstance.dispatchEvent("uvpBitratesSelected", bitrates);
	}
	
	function initFilter() {
		if (playerInstance.params.configurationFilter && playerInstance.params.configurationFilter.init) {
			playerInstance.params.configurationFilter.init(playerInstance, filter_initComplete);
		}
		else {
			filter_initComplete();
		}
	}
	
	function filterChapters() {
		if (chapterData.allowFilter && playerInstance.params.configurationFilter && playerInstance.params.configurationFilter.filter) {
			chapterData.chapters = playerInstance.params.configurationFilter.filter(chapterData.chapters, playerInstance.params.sourceID);
		}
	}
	
	
	//--------------------------------------------------------------------------
	//
	//  Callbacks
	//
	//--------------------------------------------------------------------------
	
	function load_success(data, textStatus, xhr) {
		// convert to json
		jsonData = $.xml2json(data);
		
		parseChapters();
		initFilter();
	}
	
	function load_error(xhr, textStatus, errorThrown) {
	    playerInstance.dispatchEvent("uvpError", [errorThrown]);
	}
	
	function filter_initComplete() {
		filterChapters();
		
		// do the callback
		if (loadCallback) {
			loadCallback(playerInstance.id);
		}
	}
	
	
	//--------------------------------------------------------------------------
	//
	//  Public methods
	//
	//--------------------------------------------------------------------------
	
	return {
		loadConfig: function (bandwidth, callback) {
			loadCallback = callback;
			loadBandwidth = bandwidth;

			$.ajax({url: playerInstance.params.configURL, type: 'GET', dataType: 'xml', success: load_success, error: load_error});
		},
		
		parseChapters: function () {
			parseChapters();
			filterChapters();
		},
		
		getConfig: function () {
			return jsonData;
		},
		
		getChapterData: function () {
			return chapterData;
		}
	};
}

function UVPBandwidthDetector(playerInstance) {
	
	var download,
		downloadSize,
		startTime,
		endTime,
		detectCallback,
		speedBps,
		speedKbps,
		speedMbps,
		enabled,
		tid;
	
	//--------------------------------------------------------------------------
	//
	//  Private methods
	//
	//--------------------------------------------------------------------------	
	
	function loadImage(url, timeout) {
		download = new Image();
		download.onload = download_onload;

		// save start time --> begin download
		startTime = (new Date()).getTime();
		download.src = url + "?n=" + Math.random();
		
		if (timeout > 0) {
			tid = setTimeout(download_timeout, timeout);
		}
	}
	
	function calculateResults() {
		var duration = Math.round((endTime - startTime) / 1000),
			bitsLoaded = downloadSize * 8;

		speedBps = Math.round(bitsLoaded / duration);
		speedKbps = (speedBps / 1024).toFixed(2);
		speedMbps = (speedKbps / 1024).toFixed(2);

		uvp.log("Your connection speed is: \n" +
		speedBps + " bps\n" +
		speedKbps + " kbps\n" +
		speedMbps + " Mbps");

		detectCallback(playerInstance.id, speedBps);
	}
		
	//--------------------------------------------------------------------------
	//
	//  Callbacks
	//
	//--------------------------------------------------------------------------
	
	function download_onload() {
		enabled = true;
		
		clearTimeout(tid);
		
		// save end time
		endTime = (new Date()).getTime();
		calculateResults();
	}
	
	function download_timeout() {
		clearTimeout(tid);
		
		download.src = null;
		download.onload = null;
		download = null;
		
		uvp.log("Bandwidth detection timed out.");
		detectCallback(playerInstance.id, -1);		
	}
	
	
	//--------------------------------------------------------------------------
	//
	//  Public Methods
	//
	//--------------------------------------------------------------------------
	
	return {
		detectBandwidth: function (testURL, fileSize, timeout, callback) {
			detectCallback = callback;
			downloadSize = fileSize;
			
			if (testURL) {
				loadImage(testURL, timeout);
			}
			else {
				uvp.log("Skipping bandwidth detection, no bandwidthURL was provided.");
				detectCallback(playerInstance.id, -1);
			}
		},
		
		getSpeedBps: function () {
			return enabled ? speedBps : -1;
		},
		
		getSpeedKbps: function () {
			return enabled ? speedKbps : -1;
		},
		
		getSpeedMbps: function () {
			return enabled ? speedMbps : -1;
		},
		
		getEnabled: function () {
			return enabled;
		}
	};
}

function UVPPlayerInjector(playerInstance) {
	
	var injectorCallback;
	
	//--------------------------------------------------------------------------
	//
	//  Private methods
	//
	//--------------------------------------------------------------------------
	
	/*
		Use SWFObject to embed a Flash object in the page
	*/
	function embedFlashVideoPlayer() {
		var	flashvars,
			parameters,
			attributes,
			width = playerInstance.params.divWidth,
			height = playerInstance.params.divHeight,
			swfName = "uvp-" + playerInstance.params.id,
			swfVersionStr = "10.0.32",
			xiSwfUrlStr = "playerProductInstall.swf";
		
		//init flash vars
		flashvars = {
			id: playerInstance.params.id
		};
		
		//init params
		parameters = {
			quality: "high",
			allowscriptaccess: "always",
			allowfullscreen: "true",
			wmode: "opaque"
		};
		
		//init attributes
		attributes = {
			id: swfName,
			name: swfName,
			align: "middle"
		};
		
		//embed swf
		swfobject.embedSWF(playerInstance.params.swfURL, playerInstance.params.replaceElementID, 
							width, height, swfVersionStr, xiSwfUrlStr, flashvars, parameters, attributes, swfobject_embedSWFComplete);
	}

	/*
		Use AJAX to load the HTML5 video player template
	*/
	function embedHTML5Player() {
		// load and inject the video player html
		$.ajax({
			url: playerInstance.params.htmlTemplateURL, 
			crossDomain: true,
			type: 'GET',
			success: loadHTMLPlayer_complete,
			error: loadHTMLPlayer_error,
			dataType: 'html',
			data: null
		});
	}
	
	/*
		Image content should be provided by default - this method is currently for debugging only
	*/
	function fallbackToImage() {
		if (playerInstance.params.fallbackImageURL) {
			uvp.log("Falling back to image.");
			$("#" + playerInstance.params.replaceElementID).replaceWith("<img src='" + playerInstance.params.fallbackImageURL + "'></img>");
		}
		else {
			uvp.log("No fallback image, no page modifications performed");
		}
		
		attemptCallback();
	}
	
	/*
		Inject new <source> elements into the HTML5 video tag based on the current chapter
	*/
	function updateChapterHTML(chapterNum) {
		var params = playerInstance.params,
			videoTag = $("#uvp-" + playerInstance.id),
			chapterData = playerInstance.configParser.getChapterData(),
			chapter = chapterData.chapters[chapterNum],
			contentIndex = chapterData.contentIndicies[chapterNum],
			urls = chapter.content[contentIndex].url,
			sourceHTML = "",
			url,
			codecs,
			i;
		
		// set <video> attributes
		videoTag.attr("poster", chapter.poster);
	
		// create <source> elements
		for (i = 0; i < urls.length; ++i) {
			url = urls[i];
			
			// filter out unsupported fileTypes such as swf and image
			if (url.fileType === "mp4" || url.fileType === "webM" || url.fileType === "ogg") {
				codecs = url.codecs;
				if (!codecs) {
					switch (url.fileType) {
					case "mp4": 
						codecs = "avc1.42E01E, mp4a.40.2"; 
						break;
					case "webM": 
						codecs = "vp8, vorbis"; 
						break;
					case "ogg": 
						codecs = "theora, vorbis"; 
						break;
					}
				}
				sourceHTML += "<source src='" + params.videoBasePath + url.location + "' type='video/" + url.fileType + "' codecs='" + codecs + "'></source>";
			}
		}
		
		// inject the new source elements
		videoTag.html(sourceHTML);
		
		// load() causes the <video> tag to update its content based on the new <source>
		videoTag[0].load();
	}
	
	/*
		Bind HTML5 video events and map them to our UVP event system
	*/
	function setPlayerEvents() {
		var params = playerInstance.params,
			videoTag = $("#uvp-" + playerInstance.id),
			videoElement = videoTag[0],
			chapterData = playerInstance.configParser.getChapterData(),
			chapters = chapterData.chapters;
		
		videoTag.bind("loadstart", function () {
			playerInstance.dispatchEvent("uvpPlayerStateChanged", ["loading"]);
		});
		
		videoTag.bind("play", function () {
			playerInstance.dispatchEvent("uvpPlayerStateChanged", ["playing"]);
		});
		
		videoTag.bind("pause", function () {
			playerInstance.dispatchEvent("uvpPlayerStateChanged", ["paused"]);
		});
		
		videoTag.bind("error", function (error) {
			playerInstance.dispatchEvent("uvpError", ["mediaError", error]);
		});
		
		videoTag.bind("timeupdate", function () {
			playerInstance.dispatchEvent("uvpCurrentTimeChanged", ["currentTimeChange"]);
		});
		
		videoTag.bind("durationchange", function (event) {
			playerInstance.dispatchEvent("uvpDurationChanged", [videoElement.duration]);
		});
		
		videoTag.bind("canplay", function () {
			playerInstance.dispatchEvent("uvpCapabilityChanged", ["canPlay", true]);
		});
		
		videoTag.bind("waiting", function () {
			playerInstance.dispatchEvent("uvpCapabilityChanged", ["canPlay", false]);
		});
		
		videoTag.bind("waiting", function () {
			playerInstance.dispatchEvent("uvpVolumeChanged", [false]);
		});
		
		videoTag.bind("ended", function () {
			if (playerInstance.currentChapter < chapters.length - 1) {
				playerInstance.setChapter(playerInstance.currentChapter + 1);
				videoElement.play();
			}
			else {
				playerInstance.dispatchEvent("uvpPlaybackComplete");
				
				if (params.loop) {
					if (videoElement.currentChapter === 0) {
						videoElement.currentTime = 0;
						videoElement.play();
					}
					else {
						playerInstance.setChapter(0);
					}
				}
				else if (params.autoRewind) {
					playerInstance.setChapter(0);
				}
			}
		});
	}
	
	//--------------------------------------------------------------------------
	//
	//  Callbacks
	//
	//--------------------------------------------------------------------------
	
	/*
		After the HTML template is loaded from embedHTML5Player(), we need to write the video control
		and chapter thumbnails elements to the template and then embed it in the page.
	*/
	function loadHTMLPlayer_complete(data, textStatus, requestObj) {
		var htmlTemplate = $("<div>" + data + "</div>"),
			videoContainer = htmlTemplate.find(".uvp-videoContainer"),
			chapterThumbnails = htmlTemplate.find(".uvp-chapterThumbnails"),
			chapterData = playerInstance.configParser.getChapterData(),
			chapters = chapterData.chapters,
			contentIndicies = chapterData.contentIndicies,
			params = playerInstance.params,
			videoHTML = "",
			chapterHTML = "",
			chapter = chapters[0],
			i;
		
		// create <video> element
		videoHTML = "<video id='uvp-" + params.id + "' width='" + params.videoWidth + "' height='" + params.videoHeight + "' preload='auto'";
		if (params.controls) {
			videoHTML += " controls='controls'";
		}
		if (params.loop) {
			videoHTML += " loop='loop' repeat='repeat'";
		}
		if (params.autoPlay) {
			videoHTML += " autoplay='autoplay'";
		}
		videoHTML += "></video>";
		
		// inject the <video> element into the template
		videoContainer.html(videoHTML);
		
		// create chapter thumbnails
		chapterHTML = "<ul>";
		for (i = 0; i < chapters.length; i++) {
			if (chapters[i].thumbnail) {
				chapterHTML += "<li><img src='" + chapters[i].thumbnail + "' onclick='uvp(\"" + params.id + "\").play(" + i + ")'></img></li>";
			}
		}
		chapterHTML += "</ul>";
		
		// inject the chapter thumbnails into the template
		chapterThumbnails.html(chapterHTML);
		
		// inject the template into the page
		$("#" + params.replaceElementID).replaceWith(htmlTemplate.children());
		setPlayerEvents();
		
		attemptCallback();
	}
	
	function loadHTMLPlayer_error(requestObj, textStatus, errorThrown) {
		playerInstance.dispatchEvent("uvpError", ["AJAX error - could not load HTML template."]);
		fallbackToImage();
	}
	
	/*
		Callback for swfobject.embedSWF() - tells us whether the call succeeded or failed
	*/
	function swfobject_embedSWFComplete(event) {
		if (event.success && (playerInstance.player = $("#uvp-" + playerInstance.id)[0])) {			
			playerInstance.swfEmbedComplete = true;
			attemptCallback();
		}
		else {
			playerInstance.dispatchEvent("uvpError", ["SWFObject error - Unable to embed SWF."]);
			fallbackToImage();
		}
	}
	
	/*
		Don't do a callback until SWFObject has reported success AND the SWF itself has notified us that its
		ExternalInterface methods are ready to go (in other words, when playerIsInitialized has been called).
	*/
	function attemptCallback() {
		if (playerInstance.playerType !== "flash" || (playerInstance.externalInterfaceReady && playerInstance.swfEmbedComplete)) {
			playerInstance.player = $("#uvp-" + playerInstance.id)[0];			
			injectorCallback(playerInstance.id);
		}	
	}
	
	
	//--------------------------------------------------------------------------
	//
	//  Public Methods
	//
	//--------------------------------------------------------------------------
	
	return {
		/*
			Write a SWF or HTML5 <video> tag to the page
		*/
		injectPlayer: function (callback) {
			injectorCallback = callback;

			if (playerInstance.playerType === "flash") {
				embedFlashVideoPlayer();
			}
			else if (playerInstance.playerType === "html5") {
				embedHTML5Player();
			}
			else {
				fallbackToImage();
			}
		},
		
		/*
			Called by UVP when the chapter has changed
		*/
		injectChapter: function (chapterNum) {			
			updateChapterHTML(chapterNum);
		},
		
		/*
			Called by uvp when a player reports that its ExternalInterface callbacks are online.
		*/
		playerIsInitialized: function () {
			playerInstance.externalInterfaceReady = true;
			attemptCallback();
		}
	};	
}

(function () {
	var uvp,
		playerIDs = [],
		players = {};
	
	
	//--------------------------------------------------------------------------
	//
	//  Private Methods
	//
	//--------------------------------------------------------------------------
	
	/*
		Test whether express install is available & allowed
			- must be enabled in the params
			- fp 6.0.65 or higher
			- Win/Mac OS only
			- no Webkit engines older than version 312
	*/
	function canExpressInstall(playerInstance) {
		return (playerInstance.params.expressInstallEnabled && swfobject.hasFlashPlayerVersion("6.0.65") && (swfobject.ua.win || swfobject.ua.mac) && !(swfobject.ua.wk && swfobject.ua.wk < 312));
	}
	
	/*
		Selects a value for playerInstance.playerType
	*/
	function getPlayerType(playerInstance) {
		var flashSupported = playerInstance.params.swfURL && (swfobject.hasFlashPlayerVersion("10.0.32") || canExpressInstall(playerInstance)),
			html5Supported = playerInstance.params.htmlTemplateURL && Modernizr.video && (Modernizr.video.ogg || Modernizr.video.h264 || Modernizr.video.webm);
		//return "image";
		if (playerInstance.params.preferHTML) {
			if (html5Supported) {
				return "html5";
			}
			else if (flashSupported) {
				return "flash";
			}
		}
		else {
			if (flashSupported) {
				return "flash";
			}
			else if (html5Supported) {
				return "html5";
			}
		}
		
		return "image";
	}
	
	/*
		The UVPInstance class is a wrapper for an individual HTML or Flash based
		UVP player.  It contains state information as well as references to its
		associated bandwidth detector, config parser and injector instances.
		
		A UVPInstance may be obtained by calling uvp(pid) where pid is the id
		provided in the params object which was passed to uvp.embedPlayer.
		
		Note that a UVPInstance will not be available immediately after calling
		uvp.embedPlayer.  Use embedPlayer's callback function to be notified
		when embedding is complete.
	*/
	function UVPInstance(params) {
		this.id = params.id;
		this.bandwidthDetector = new UVPBandwidthDetector(this);		
		this.configParser = new UVPConfigParser(this);
		this.injector = new UVPPlayerInjector(this);
		this.currentChapter = -1;
		this.params = params;
		this.player = null;
		this.callback = null;
		this.playerType = null;
		this.externalInterfaceReady = false;
		this.swfEmbedComplete = false;
		
		this.dispatchEvent = function (type, args) {
			if (params.client.hasOwnProperty(type)) {
				params.client[type].apply(null, args);
			}
		};
		
		this.mute = function () {
			if (this.playerType === "flash") {
				this.player.mute();
			}
			else if (this.playerType === "html5") {
				this.player.muted = true;
				this.dispatchEvent("uvpMutedChanged", [true]);
			}
		};
		
		this.unmute = function () {
			if (this.playerType === "flash") {
				this.player.unmute();
			}
			else if (this.playerType === "html5") {
				this.player.muted = false;
				this.dispatchEvent("uvpMutedChanged", [false]);
			}
		};
		
		this.setVolume = function (value) {
			value = Math.max(0, Math.min(1, value));
			if (this.playerType === "flash") {
				this.player.setVolume(value);
			}
			else if (this.playerType === "html5") {
				this.player.volume = value;
				this.dispatchEvent("uvpVolumeChanged", [value]);
			}
		};

		this.pause = function () {
			if (this.playerType === "flash" || this.playerType === "html5") {
				this.player.pause();
			}
		};
		
		this.nextChapter = function () {
			if (this.currentChapter < this.configParser.getChapterData().chapters.length-1) {
				var chapter = this.currentChapter + 1;
				
				if (this.playerType === "flash") {
					this.player.nextChapter();
				}
				else if (this.playerType === "html5") {				
					this.injector.injectChapter(chapter);
					this.dispatchEvent("uvpChapterChanged", [this.currentChapter, chapter]);
				}
				
				this.currentChapter = chapter;
			}
		};
		
		this.previousChapter = function () {
			if (this.currentChapter > 0) {
				var chapter = this.currentChapter - 1;
				
				if (this.playerType === "flash") {
					this.player.previousChapter();
				}
				else if (this.playerType === "html5") {				
					this.injector.injectChapter(chapter);
					this.dispatchEvent("uvpChapterChanged", [this.currentChapter, chapter]);
				}
							
				this.currentChapter = chapter;
			}
		};
		
		this.setChapter = function (chapter) {			
			if (!isNaN(chapter) && this.currentChapter !== chapter) {
				uvp.log("set chapter! " + chapter);
				
				if (this.playerType === "flash") {
					this.player.setChapter(chapter);
				}
				else if (this.playerType === "html5") {
					this.injector.injectChapter(chapter);
					this.dispatchEvent("uvpChapterChanged", [this.currentChapter, chapter]);
				}
				
				this.currentChapter = chapter;
			}
		};
		
		this.setSourceID = function (sourceID) {
			if (this.params.sourceID !== sourceID) {
				this.params.sourceID = sourceID;
				this.currentChapter = -1;
				this.configParser.parseChapters();
				
				if (this.playerType === "flash") {
					this.player.updateChapterData(this.configParser.getChapterData());
				}
				else {
					this.setChapter(0);
				}
			}
		};
		
		this.updateChapter = function (chapter) {
			this.currentChapter = chapter;
		};

		this.play = function (chapter) {
			if (this.playerType === "flash") {
				this.setChapter(chapter);
				this.player.playVideo();
			}
			else if (this.playerType === "html5") {
				this.setChapter(chapter);
				this.player.play();
			}
		};

		this.seek = function (time) {
			if (this.playerType === "flash") {
				this.player.seek(time);
			}
			else if (this.playerType === "html5") {
				this.player.currentTime = time;
			}
		};

		this.stop = function () {
			if (this.playerType === "flash") {
				this.player.stopVideo();
			}
			else if (this.playerType === "html5") {
				if(this.player.stop) {
					this.player.stop();
				}
				else {
					this.player.pause();
					this.player.currentTime = 0;
				}
			}
		};
	}
	
	/*
		Ensure all required params are present and set any omitted params to their defaults
	*/
	function validateParams(paramsObj) {
		var paramName,
			defaults = {
				replaceElementID: "defaultContent",
				id: "player",
				width: 300,
				height: 200,
				client: null,
				controls: true,
				volume: 1,
				currentChapter: 0,
				muted: false,
				autoPlay: false,
				autoRewind: true,
				loop: false,
				preferHTML: false,
				expressInstallEnabled: true,
				bandwidthDetectionSize: 0,
				bandwidthDetectionTimeout: -1,
				videoBasePath: ""
			};
		
		// check for required params
		if (!paramsObj.configURL) {
			return null;
		}
		
		// set default params
		for (paramName in defaults) {
			if (!paramsObj.hasOwnProperty(paramName)) {
				paramsObj[paramName] = defaults[paramName];
				uvp.log("Using default value for " + paramName + ": " + defaults[paramName]);
			}
		}
		
		return paramsObj;
	}
	
	/* 
		Create wrapper methods for the all players on the uvp object 
		e.g. uvp.playAll() calls play() on every uvp player
	*/
	function setupConvienenceMethods() {
		var name,
			tmpInstance = new UVPInstance({id: -1});

		for (name in tmpInstance) {
			if (typeof tmpInstance[name] === "function") {
				(function (name) {
					uvp[name + "All"] = function () {
						for (var i = 0; i < playerIDs.length; i++) {
							players[playerIDs[i]][name].apply(players[playerIDs[i]], arguments);
						}
					};
				}).call(this, name);
			}
		}
	}


	//--------------------------------------------------------------------------
	//
	//  Callbacks
	//
	//--------------------------------------------------------------------------
	
	/*
		Step 2 (bandwidth detection) of uvp.embedPlayer is complete, proceed to step 3 (parse config)
	*/
	function detectBandwidth_complete(pid) {
		var playerInstance = players[pid],
			bandwidth = players[pid].bandwidthDetector.getSpeedBps(),
			params = playerInstance.params;
		playerInstance.configParser.loadConfig(bandwidth, configParser_loadComplete);
	}
	
	/*
		Step 3 (parse config) of uvp.embedPlayer is complete, proceed to step 4 (inject player)
	*/
	function configParser_loadComplete(pid) {	
		var playerInstance = players[pid];
		playerInstance.injector.injectPlayer(playerInjector_complete);
	}
	
	/*
		Step 4 (inject player) of uvp.embedPlayer is complete, execute callback method
	*/
	function playerInjector_complete(pid) {	
		var name,
			playerInstance = players[pid];
		
		// create wrapper methods for the newest player on the uvp object 
		// e.g. uvp.play() is a wrapper for uvp(params.id).play()
		if (playerInstance.playerType !== "image") {
			for (name in players[pid]) {
				if (typeof players[pid][name] === "function") {
					(function (name) {
						uvp[name] = function () { 
							return players[pid][name].apply(players[pid], arguments);
						};
					}).call(this, name);
				}
			}
		}
		
		// initialize html5 video properties (Flash does this internally)
		if (playerInstance.playerType === "html5") {
			if (playerInstance.params.muted) {
				playerInstance.mute();
			}
			playerInstance.setVolume(playerInstance.params.volume);	
			playerInstance.setChapter(Math.max(playerInstance.params.currentChapter, 0));
		}
		
		// embedPlayer is complete!
		if (playerInstance.callback) {
			playerInstance.callback(playerInstance);
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Methods
	//
	//--------------------------------------------------------------------------
	
	/*
		The uvp method may be used to obtain a UVPInstance by id.
	*/
	window.uvp = uvp = function (pid) {
		return pid ? players[pid] : players[playerIDs[playerIDs.length - 1]];
	};
	
	/*
	   Expose the UVPInstance class so it can be extended
	 */
	uvp.fn = UVPInstance.prototype;
	
	/*
		Centralized logging system for all UVP javascript classes
	*/
	uvp.log = function (msg) {
		function pad(number, length) {  
			var str = '' + number;
			while (str.length < length) {
				str = '0' + str;
			}
			return str;
		}

		if (msg && (typeof window.console !== 'undefined') && (typeof window.console.log === 'function')) {
			var d = new Date();
			msg = "UVP - [" + pad(d.getHours(), 2) + ":" + pad(d.getMinutes(), 2) + ":" + pad(d.getSeconds(), 2) + ":" + pad(d.getMilliseconds(), 4) + "] - " + msg.replace(/[\n\r]/gi, "\n                      - ");
			window.console.log(msg);
		}
	};

	/*
		Creates and embeds a UVP player to the page in 4 steps:
			1. Construct a UVPInstance
			2. Run bandwidth detector
			3. Load and parse the configuration file
			4. Inject the video or object tag to the page
	*/
	uvp.embedPlayer = function (paramsObj, callbackFunction) {
		var playerInstance,
			validParams = validateParams(paramsObj);
		
		if (validParams) {	
			// Create a player instance
			playerInstance = new UVPInstance(validParams);
			playerInstance.playerType = getPlayerType(playerInstance);
			playerInstance.callback = callbackFunction;
			
			// Save the instance in the players dictionary
			players[playerInstance.id] = playerInstance;
			playerIDs.push(playerInstance.id);
			
			playerInstance.bandwidthDetector.detectBandwidth(validParams.bandwidthDetectionURL, validParams.bandwidthDetectionSize, validParams.bandwidthDetectionTimeout, detectBandwidth_complete);
		}
		else if (paramsObj.client && paramsObj.client.hasOwnProperty("uvpError")) {
			paramsObj.client.uvpError.apply(null, ["uvp.embedPlayer:  You must specify a configURL in the params."]);
		}
	};

	/*
		Retreive a parsed configuration object
	*/
	uvp.getConfig = function (pid, sid) {
		return players[pid].configParser.getChapterData();
	};
	
	/*
		Retreive UVP params for the Flash version of the player.
		This includes all of the params passed to uvp.embed player with a few additions (client, bandwidth & config)
	*/
	uvp.getFlashParams = function (pid) {
		var playerParams = players[pid].params,
			flashParams = $.extend(true, {}, playerParams),
			bandwidth = players[pid].bandwidthDetector.getSpeedBps(),
			chapters = players[pid].configParser.getChapterData().chapters,
			contentIndicies = players[pid].configParser.getChapterData().contentIndicies;
		
		// add UVP generated data into the params object
		flashParams.client = playerParams.client.id;
		flashParams.bandwidth = bandwidth;
		flashParams.chapters = chapters;
		flashParams.contentIndicies = contentIndicies;
			
		return flashParams;
	};
	
	/*
		Called from Flash once it's ExternalInterface callbacks have been set up.
		Indicates that Flash is ready for us to send the configuration data.
	*/
	uvp.playerInitialized = function (pid) {
		players[pid].injector.playerIsInitialized();
	};
	
	/*
		Removes all event bindings and callbacks.  Use this if you are removing a player from the page.
		Warning:  attempting to execute any UVPInstance methods after calling unregisterPlayer  is
		          likely to cause script errors.
	*/
	uvp.unregisterPlayer = function (pid) {
		var i,
			videoTag = $("#uvp-" + pid);
		
		if (videoTag) {
			videoTag.unbind();
		}
		
		for (i = 0; i < playerIDs.length; ++i) {
			if (playerIDs[i] === pid) {
				playerIDs.splice(i, 1);
				break;
			}
		}
		
		delete players[pid];
	};
	
	// dynamically generate additional methods on uvp such as uvp.playAll() and uvp.muteAll()
	setupConvienenceMethods();
}());
// 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
// Website: http://www.fyneworks.com/jquery/xml-to-json/
// Inspired by: http://www.terracoder.com/, http://www.thomasfrank.se/xml_to_json.html and http://www.kawa.net/works/js/xml/objtree-e.html
(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,
					nt = node.nodeType,
					nn = jsVar(node.localName || node.nodeName),
					nv = node.text || node.nodeValue || '',
					out;
                /*DBG*/
                //if(window.console) console.log(['x2j',nn,nt,nv.length+' bytes']);
                if (node.childNodes) {
                    if (node.childNodes.length > 0) {
                        /*DBG*/
                        //if(window.console) console.log(['x2j',nn,'CHILDREN',node.childNodes]);
                        $.each(node.childNodes,
                        function (n, cn) {
                            var cnt = cn.nodeType,
								cnn = jsVar(cn.localName || cn.nodeName),
								cnv = cn.text || cn.nodeValue || '';
                            /*DBG*/
                            //if(window.console) console.log(['x2j',nn,'node>a',cnn,cnt,cnv]);
                            if (cnt === 8) {
                                /*DBG*/
                                //if(window.console) console.log(['x2j',nn,'node>b',cnn,'COMMENT (ignore)']);
                                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) console.log(['x2j',nn,'node>c',cnn,'WHITE-SPACE (ignore)']);
                                    return;
                                }
                                /*DBG*/
                                //if(window.console) console.log(['x2j',nn,'node>d',cnn,'TEXT']);
                                txt += cnv.replace(/^\s+/, '').replace(/\s+$/, '');
                                // make sure we ditch trailing spaces from markup
                            }
                            else {
                                /*DBG*/
                                //if(window.console) console.log(['x2j',nn,'node>e',cnn,'OBJECT']);
                                obj = obj || {};
                                if (obj[cnn]) {
                                    /*DBG*/
                                    //if(window.console) console.log(['x2j',nn,'node>f',cnn,'ARRAY']);
                                    if (!obj[cnn].length) {
										obj[cnn] = myArr(obj[cnn]);
									}
                                    obj[cnn][obj[cnn].length] = parseXML(cn, true);
                                    obj[cnn].length = obj[cnn].length;
                                }
                                else {
                                    /*DBG*/
                                    //if(window.console) console.log(['x2j',nn,'node>g',cnn,'dig deeper...']);
                                    obj[cnn] = parseXML(cn);
                                }
                            }
                        });
                    }
                    //node.childNodes.length>0
                }
                //node.childNodes
                if (node.attributes) {
                    if (node.attributes.length > 0) {
                        /*DBG*/
                        //if(window.console) console.log(['x2j',nn,'ATTRIBUTES',node.attributes])
                        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) console.log(['x2j',nn,'attr>',atn,'ARRAY']);
                                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) console.log(['x2j',nn,'attr>',atn,'TEXT']);
                                obj[atn] = atv;
                            }
                        });
                        //obj['attributes'] = att;
                    }
                    //node.attributes.length>0
                }
                //node.attributes
                if (obj) {
                    obj = $.extend((txt !== '' ? 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 = '';
                }
                
				out = obj || txt;
                //console.log([extended, simple, out]);
                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 root,
				out,
				jsVar = function (s) {
					return String(s || '').replace(/-/g, "_");
	            },
	            isNum = function (s) {
	                return (typeof s === "number") || String((s && typeof s === "string") ? s: '').test(/^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/);
	            },
	            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
            root = (xml.nodeType === 9) ? xml.documentElement: xml;
			out = parseXML(root, true);

            // 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,
				xml;
            try {
                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 $
})(window.jQuery);
})(window);
}
