function cwpComScore(manager) {
	if ($("#cwpComScore").length==0){
		$(document.body).append("<img id='cwpComScore' height='1' width='1' />"); 
	}

	$("#cwpComScore").attr("src","http://beacon.securestudies.com/scripts/beacon.dll?C1=1&C2=3005660&C3=3005660&C4=" + window.location.hostname.toLowerCase() + window.location.pathname.toLowerCase() + "&C5=" + manager.currentPlay.Show + "&C6=&C7=" + escape(window.location.href) + "&C8=" + escape(document.title) + "&C9=" + escape(document.referrer) + "&rn=" + Math.floor(Math.random()*99999999));
}

// Helper methods
function cwpQS(qs) { // optionally pass a querystring to parse
	this.params = {};
	if (!qs){qs = location.search.substring(1, location.search.length);}
	if (qs.length === 0) return;
	qs = qs.replace(/\+/g, ' ');
	var args = qs.split('&'); // parse out name/value pairs separated via &
	for (var i=0;i<args.length;i++) {
		var pair = args[i].split('=');
		var name = decodeURIComponent(pair[0]);
		var value = (pair.length==2)?decodeURIComponent(pair[1]):name;
		this.params[name] = value;
	}
}
cwpQS.prototype.get=function(key, default_){var value = this.params[key];return (value!=null)?value:default_;};
cwpQS.prototype.contains=function(key){var value = this.params[key];return (value!=null);};

function symbolsToEntities(sText) {
	if(!sText){sText="";}
	var sNewText = "";
	var iLen = sText.length;
	for (i=0; i<iLen; i++) {
		iCode = sText.charCodeAt(i);
		sNewText += (iCode > 256? "&#" + iCode + ";": sText.charAt(i));
	}
	return sNewText;
}
function cwpAjaxLinks(){
	$("a[href^='?releasePID=']").click(function(){
		focusOnTargetRelease("PID", $(this).attr("href").split("&")[0].split("=")[1]);
		return false;
	});
}
function convertLength(data){
	var tl = new Date(data);
	var HH = tl.getHours(); var MM = tl.getMinutes(); var ss = tl.getSeconds();
	var str;

	if (ss<10){str=":0"+ss;}
	else{str=":"+ss;}
	str = MM + str;
	if (HH - 19 > 0){
		if (MM < 10){str="0"+str;}
		str = HH + ":" + str;
	}
	return str;
}

function cwpArrayContains(arr,field){
	for (var i=0;i<arr.length;i++){
		if (arr[i]==field){return true;}
	}
	return false;
}

function cwpDisableSelection(element) {
	element.bind("onselectstart",function(){return false;});
	element.attr("unselectable", "on");
	element.css("MozUserSelect", "none");
	element.css("cursor", "pointer");
}

function cwpRemoveSpaces(s){return s && s.replace(/[ ]/g,"");}
function cwpCleanStr(s){return s && s.toLowerCase().replace(/\s|[.,<>?\/;:"'{\[}\]|\\+=_-`!@#$%\^&*()]*|~*/g,"");}
function cwpCleanAdParams(s){return s && s.toLowerCase().replace(/[.,<>?\/;:"'{\[}\]|\\+=_-`!@#$%\^&*()]*|~*/g,"").replace(/[ ]/g,"_");}

function logError(errorMsg) {
	alert(errorMsg);
	return false;
}

function logDebug(debugMsg) {
	var dbgDiv = $("#debugOutput");
	var d = new Date();
	if (dbgDiv.length>0){dbgDiv.prepend("<" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ">: " + debugMsg + "<br/>");}
}

function cwpGetFeedContents(feedJSON, callback){
	$.getJSON(feedJSON, function(data){
		var str = "";
		for (ind in data.listInfo){
			str += ind + ": " + data.listInfo[ind] + "|";	
		}
		str += "<br/>";
		$.each(data.items,function(i,dataItem){
			str += i + " - ";
			for (ind in dataItem){
				if (ind.indexOf("ustomData") >0){
					str += ind + ":: ";
					for (cd in dataItem[ind]){
						str += dataItem[ind][cd].title + ": " + dataItem[ind][cd].value + ", ";
					}
					str += " | ";
				}else{
					str += ind + ": " + dataItem[ind] + " | ";
				}
			}
			str += "<br/>";
		});
		callback(str);
	});
}

function cwpEventProto(obj,name,callback){
	if (this.mode == CWP_UNRENDERED) return;
	if (typeof callback == "function"){
		obj[name + "Callback"] = callback;
	}else{
		if (obj[name + "Callback"]) obj[name + "Callback"](obj); // use local callback if available 
		else {
			if (obj.type==CWP_CATEGORY) {
				//logDebug("Looking for category event (" + obj.ID + ", " + obj.depth + ", '" + name + "').");
				obj.manager.catExists(obj.depth)[name](obj);
			}else{
				//logDebug("Looking for release event (" + obj.ID + ", '" + name + "').");
		 	 	if (obj.manager.rel[name])
			 		obj.manager.rel[name](obj);
			}
		}
	}
}

function cwpVideoEventProto(obj,name,pdkEvent,callback){
	if (this.mode == CWP_UNRENDERED) return;
	if (typeof callback == "function"){
		obj[name + "Callback"] = callback;
	}else{
		if (obj[name + "Callback"]) obj[name + "Callback"](obj,pdkEvent); // use local callback if available 
		else {
			if (obj.type==CWP_CATEGORY) {
				logDebug("Looking for category event (" + obj.ID + ", " + obj.depth + ", '" + name + "').");
				obj.manager.catExists(obj.depth)[name](obj,pdkEvent);
			}else{
				logDebug("Looking for release event (" + obj.ID + ", '" + name + "').");
		 	 	if (obj.manager.rel[name])
			 		obj.manager.rel[name](obj,pdkEvent);
			}
		}
	}
}

// Constants
var CWP_RELEASE = 1;
var CWP_CATEGORY = 2;

var CWP_UNRENDERED = 0;
var CWP_RENDERED = 1;
var CWP_PLAYING = 2;
var CWP_PAUSED = 3;

var CWP_CLASS_HL = "cwpHighlighted";
var CWP_CLASS_SEL = "cwpSelected";

var CWP_PLACEHOLDER_ID = 1; // ID used when we create fake categories, won't try to match the categoryIDs of child releases

// Objects
function cwpCategory(manager, data, parentCat, fieldNames, customFieldNames){
	// Initialization
	this.ID = data.ID;
	this.manager = manager;
	this.parentCat = parentCat || null;
	this.children = new Array();
	this.depth = data.depth;
	this.fullTitle = data.fullTitle;
	this.hasDirectReleases = data.hasReleases; // whether there are direct child releases
	this.hasReleases = this.hasDirectReleases; // whether they are child releases at any depth
	if (this.hasReleases && parentCat) parentCat.setHasReleases();
	this.releasesLoaded = false;
	this.type = CWP_CATEGORY;
	fieldNames = fieldNames || manager.catFields;
	for (i=0;i<fieldNames.length;i++){
		this[fieldNames[i]] = data[fieldNames[i]];
	}
	customFieldNames = customFieldNames || manager.catCustomFields;
	if (customFieldNames){
		for (i=0;i<data.customData.length;i++){
			// If we have a displayTitle, override the title property
			if (data.customData[i].title=="DisplayTitle"){
				if (data.customData[i].value.length>0) this["title"] = data.customData[i].value;
			}else{this[cwpRemoveSpaces(data.customData[i].title)] = data.customData[i].value;}
		}
	}

	this.playlistContainer = false;
	this.mode = CWP_UNRENDERED;
	this.jsonRequested = false;
	this.isSelected = false;

	// Methods
	this.getRenderObj = function(){
		var renderObj = this.manager.catRendering[this.depth];
		var renderDepth = this.depth;
		while (!renderObj || renderObj == null) {
			renderDepth--;
			if (renderDepth<0) return logError("Cannot render Category.  No rendering methods defined.");
			renderObj = this.manager.catRendering[renderDepth];
		}
		return renderObj;
	}
	
	this.setUnrendered = function(setRootUnrendered){
		if (setRootUnrendered){this.element=null;this.mode=CWP_UNRENDERED;this.isSelected=false;}
		$.each(this.children, function(i,obj){
			obj.setUnrendered(true);
		});
	}

	this.click = function(callback){cwpEventProto(this.cwpTarget,"click",callback);}
	this.mouseover = function(callback){
		if (this.cwpTarget.element)
			this.cwpTarget.element.addClass(CWP_CLASS_HL);
		cwpEventProto(this.cwpTarget,"mouseover",callback);
	}
	this.mouseout = function(callback){if (this.cwpTarget.element) this.cwpTarget.element.removeClass(CWP_CLASS_HL);cwpEventProto(this.cwpTarget,"mouseout",callback);}
	this.select = function(callback){
		if (callback)
			cwpEventProto(this,"select",callback);
		else if (!this.isSelected){
			this.isSelected = true;
			this.manager.setSelected(this,true);
			if (this.element) this.element.addClass(CWP_CLASS_SEL);
			cwpEventProto(this,"select",callback);
		}else logDebug("Cannot double select category: " + this.title);
	}
	this.unselect = function(callback){
		if (callback)
			cwpEventProto(this,"select",callback);
		else if (this.isSelected){
			this.isSelected = false;
			this.manager.setSelected(this,false);
			if (this.element) this.element.removeClass(CWP_CLASS_SEL);
			cwpEventProto(this,"unselect",callback);
		}else logDebug("Cannot double unselect category: " + this.title);
	}
	this.onChildrenShow = function(callback){cwpEventProto(this,"onChildrenShow",callback);}

	// Used to propagate hasReleases upwards, so we know when a category has releases at any depth below
	this.setHasReleases = function(){
		if (!this.hasReleases){
			this.hasReleases = true;
			if (this.parentCat) this.parentCat.setHasReleases();
		}
	}

	this.loadChildren = function(callback){
		if (!this.jsonRequested){
			if (!this.checkReleasesLoaded()){
				this.jsonRequested = true;
				this.manager.loadingPlaylistCat = this;
				var cat = this;
				this.fetchReleases(function(releaseList){
					cat.jsonRequested = false;
					cat.manager.releasesLoaded();
					if (callback) callback();
				});
			} else if (callback) callback();
		}
	}

	this.showChildren = function(targetContainer,startDepth,endDepth){
		logDebug("Entering showChildren method (" + this.title + ")");
		if (this.checkReleasesLoaded()){
			if (endDepth==undefined || endDepth == null) endDepth = -1;
			if (startDepth==undefined || startDepth == null) startDepth = -1;

			this.manager.loadingPlaylistCat = this;
			this.playlistContainer = targetContainer;
			if (this.manager.displayedPlaylistCat) this.manager.displayedPlaylistCat.setUnrendered(false); 
			targetContainer.html("");
			$.each(this.children,function(i,obj){
				obj.renderHTML(targetContainer,startDepth,endDepth,true);
			});
			this.manager.displayedPlaylistCat = this;
			this.onChildrenShow();
		}
		logDebug("Exiting showChildren method (" + this.title +")");
	}

	this.renderHTML = function(targetContainer,startDepth,endDepth,withReleases){
		if (this.manager.hideEmptyCats && !this.hasReleases) return;
		if (endDepth==undefined || endDepth == null) endDepth = -1;
		if (startDepth==undefined || startDepth == null) startDepth = -1;
		if (withReleases==undefined || withReleases == null) withReleases = false; // By default do not load releases as well
		
		var renderObj = null;
		if (this.mode == CWP_UNRENDERED && this.depth>=startDepth){
			// We want to use the rendering methods for the depth of the element, but can default to rendering for higher depths
			//   this way is we want nested <ul> tags, we can just define one rendering that cascades down
			
			renderObj = this.getRenderObj();
			// The top level element is just the container for the list item and sublists.  The first child is the actual element we want
			this.element = renderObj.makeContent(targetContainer,this)
			cwpDisableSelection(this.element);
			this.element[0].cwpTarget = this;
			this.element.click(this.click);
			this.element.mouseout(this.mouseout);
			this.element.mouseover(this.mouseover);
			this.mode = CWP_RENDERED;
		}
		// If we have children that have not yet been rendered
		if (this.children.length > 0 && this.children[0].mode == CWP_UNRENDERED) {
			// If the children respect the endDepth
			if (endDepth == -1 || this.children[0].depth <= endDepth){
				var nextTarget = targetContainer;
				// Create the child container if we are past the start depth
				if (this.depth>=startDepth){
					if (!renderObj) renderObj = this.getRenderObj();
					nextTarget = renderObj.makeChildContainer(this.element,this);
				}
				for(var i=0;i<this.children.length;i++)
					this.children[i].renderHTML(nextTarget,startDepth,endDepth,withReleases);
				this.onChildrenShow();
			}
		}
	}

	this.checkReleasesLoaded = function(){
		if (this.releasesLoaded) return true;
		if (this.hasDirectReleases) return false; // if releasesLoaded is false and we have direct child releases, we need to load them

		newReleasesLoaded = true;
		catObj = this;
		if (this.children.length>0){
			$.each(this.children,function(i,cwpObj){
				// If we find a child category without releases loaded
				if (cwpObj.type==CWP_CATEGORY && cwpObj.checkReleasesLoaded() == false){
					newReleasesLoaded = false;
					return false;
				}
			});

		// Else if we are at a leaf node with no releases
		}else if (!this.hasDirectReleases) newReleasesLoaded = true;
		else newReleasesLoaded = false;
		
		this.releasesLoaded = newReleasesLoaded;
		return this.releasesLoaded;
	}

	this.fetchReleases = function(callback,fieldNames,customReleaseCall,jsonURL){
		var customFieldNames = null;
		if (jsonURL==undefined || jsonURL == null) jsonURL = this.manager.jsonURL;
		if (customReleaseCall==undefined || customReleaseCall == null) customReleaseCall = this.manager.releaseCall;
		if (fieldNames==undefined || fieldNames == null){
			fieldNames = this.manager.relFields;
			customFieldNames = this.manager.relCustomFields;
		}else if (typeof fieldNames == 'string'){
			var tempFields = fieldNames.split(";");
			fieldNames = tempFields[0].split(",");
			if (tempFields.length>1)
				customFieldNames = tempFields[1].split(",");
		}

		if (this.hasReleases){
			var constructedJSON = jsonURL + customReleaseCall + "&query=CategoryIDs|" + this.getCatIDList();

			var sortCriteria = this.getInheritedSortCriteria(false);
			// Make sure we retrieve any fields we need for sorting
			var extraFields = new Array();
			if (sortCriteria.length>0){
				var splitChar = ";";
				if (sortCriteria.indexOf(splitChar)==-1) splitChar = ":"; // could be ; or :
				sortCriteria = sortCriteria.split(splitChar);
				sortField = sortCriteria[0];
				if (sortField.match(/^\d+$/)==null){
					extraFields.push(sortField);
					var sortDesc = (sortCriteria.length<=1)?false:(sortCriteria[1]=="DESC")?true:false;
					constructedJSON = constructedJSON.replace(/&sortField=[^&]*/,"&sortField="+sortField);
					constructedJSON = constructedJSON.replace(/&sortDescending=[^&]*/,"");
					if (sortDesc) constructedJSON += "&sortDescending=true";
				}
			}
			this.addExtraSortFields(extraFields);
			for (var i=0;i<extraFields.length;i++){
				if (!cwpArrayContains(fieldNames,extraFields[i])){
					fieldNames.push(extraFields[i]);
				}
			}
			
			$.each(fieldNames, function(i,field){
				constructedJSON += "&field=" + field;
			});
			if (customFieldNames){
				$.each(customFieldNames, function(i,field){
					constructedJSON += "&contentCustomField=" + field;
				});
			}

			var currentObj = this;
			logDebug("Retrieving release list: " + constructedJSON);
			$.getJSON(constructedJSON, function(data){
				logDebug("Retrieved JSON release data.");
				//if (data.listInfo.itemCount<data.listInfo.totalCount) alert("Unfortunately, the '" + currentObj.fullTitle + "' category has -" + data.listInfo.totalCount + "- releases under it and we currently support a maximum of -" + data.listInfo.itemCount + "-.  Anything after the release '" + data.items[data.listInfo.itemCount-1].title + "' alphabetically will not show up right now.  We are looking into a solution, but for now, please make a note of this category so we can address it later."); //commented out by Arron 2009.08.24 because there were more than 500 clips live and this alert was firing.
				currentObj.processFeed(data,fieldNames,customFieldNames);
				logDebug("Processed JSON release data.");
				if (callback) callback();
			});
		}
	}

	this.getCatIDList = function(){ // Used to query for all releases in a category node and all its children
		if (this.children.length==0) return "" + this.ID;
		else{
			var catIDList = this.ID;
			$.each(this.children,function(i,childObj){
				if (childObj.type == CWP_CATEGORY) catIDList += "," + childObj.getCatIDList();
			});
			return catIDList;
		}
	}

	this.processFeed = function(data,fieldNames,customFieldNames){
		if (fieldNames==undefined || fieldNames == null) fieldNames = this.manager.relFields;
		else if (typeof fieldNames == 'string') fieldNames = fieldNames.split(",");
		if (customFieldNames==undefined || customFieldNames == null) customFieldNames = this.manager.relCustomFields;
		else if (typeof customFieldNames == 'string') customFieldNames = customFieldNames.split(",");

		if (this.hasDirectReleases){
			catObj = this;
			var sortCriteria = this.getInheritedSortCriteria(true);
			var straightPush = (sortCriteria.length==0);
			// Process releases belonging in this category
			$.each(data.items,function(i,relData){
				$.each(relData.categoryIDs,function(j,categoryID){
					// If the release belongs in this category, or if this category is one where we should not match ids
					if (catObj.ID==CWP_PLACEHOLDER_ID || categoryID==catObj.ID) {
						var newRelease = new cwpRelease(catObj.manager, relData, catObj, fieldNames, customFieldNames);
						if (straightPush)
							catObj.children.push(newRelease);
						else
							catObj.addReleaseInOrder(newRelease,sortCriteria);
						return false;
					}
				});
			});
		}
		$.each(this.children,function(i,childObj){
			if (childObj.type == CWP_CATEGORY) this.processFeed(data,fieldNames,customFieldNames);
		});
		this.releasesLoaded = true;
	}

	this.addReleaseInOrder = function(rel, sortCriteria){
		var splitChar = ";";
		if (sortCriteria.indexOf(splitChar)==-1) splitChar = ":"; // could be ; or :
		sortCriteria = sortCriteria.split(splitChar);
		var sortField = sortCriteria[0];
		var sortDesc = (sortCriteria.length<=1)?false:(sortCriteria[1]=="DESC")?true:false;
		for (var i=0;i<this.children.length;i++){
			if (sortDesc){
			  if (rel[sortField]>this.children[i][sortField]){
					this.children.splice(i,0,rel);
					return;
				}
			}else if (rel[sortField]<this.children[i][sortField]){
				this.children.splice(i,0,rel);
				return;
			}
		}
		catObj.children.push(rel);
	}

	this.getCategory = function(key,value){
		if (this[key]==value) return this;
		var returnCat = null;
		$.each(this.children,function(i,cat){
			returnCat = cat.getCategory(key,value);
			if (returnCat) return false;
		});
		return returnCat;
	}

	this.getRelease = function(key,value){
		var returnCat = null;
		$.each(this.children,function(i,childObj){
			returnCat = childObj.getRelease(key,value);
			if (returnCat) return false;
		});
		return returnCat;
	}

	this.getFirstRelease = function(doRecursive){
		doRecursive = (doRecursive!==false);
		var rel = null;
		for(var i=0;i<this.children.length;i++){
			if (this.children[i].type==CWP_RELEASE) rel=this.children[i];
			else if (doRecursive) rel = this.children[i].getFirstRelease();
			if (rel != null) break;
		}
		return rel;
	}
	
	this.findFirstParentCategory = function(rel){
		var returnCat = null;
		if (this.hasDirectReleases){
			var cat = this;
			$.each(rel.categoryIDs,function(i,relCatID){
				if (cat.ID == relCatID){
					returnCat = cat;
					return false;
				}
			});
		}
		if (!returnCat){
			$.each(this.children,function(i,childCat){
				if (childCat.type == CWP_CATEGORY){
					returnCat = childCat.findFirstParentCategory(rel);
					if (returnCat) return false;
				}
			});
		}
		return returnCat;
	}

	this.loadChildrenAtDesiredDepth = function(targetContainer,depth,callback){
		if (this.depth == depth) {var cat=this;this.loadChildren(function(){callback(cat)});return true;}
		else if (this.parentCat) return this.parentCat.loadChildrenAtDesiredDepth(targetContainer,depth,callback);
		else return false;
	}

	this.selectWithParents = function(toDepth){
		if (this.depth<toDepth) return;
		if (parentCat)
			parentCat.selectWithParents(toDepth);
		this.select();
	}

	this.getInheritedSortCriteria = function(stopAtLoadedCat){
		if (this.SortCriteria && this.SortCriteria.length>0 && this.SortCriteria != this.manager.defaultSort)
			return this.SortCriteria;
		// if we want to stop at loadedCat, we need to not continue when the parent of parent hasn't been loaded
		else if (this.parentCat && (!stopAtLoadedCat || (this.parentCat.parentCat && this.parentCat.parentCat.releasesLoaded)))
			return this.parentCat.getInheritedSortCriteria(stopAtLoadedCat);
		else
			return "";
	}

	this.addExtraSortFields = function(extraFields){
		if (this.SortCriteria && this.SortCriteria.length>0 && this.SortCriteria != this.manager.defaultSort){
			var splitChar = ";";
			if (this.SortCriteria.indexOf(splitChar)==-1) splitChar = ":"; // could be ; or :
			var sortField = this.SortCriteria.split(splitChar)[0];
			if (sortField.match(/^\d+$/)==null && !cwpArrayContains(extraFields,sortField)) extraFields.push(sortField);
		}
		for (var i=0;i<this.children.length;i++)
			if (this.children[i].type==CWP_CATEGORY)
				this.children[i].addExtraSortFields(extraFields);
	}
}

function cwpRelease(manager, data, parentCat, fieldNames, customFieldNames){
	// Initialization
	this.ID = data.ID;
	this.contentID = data.contentID;
	this.PID = data.PID;
	this.manager = manager;
	this.parentCat = parentCat || null;
	this.depth = (this.parentCat)?this.parentCat.depth+1:0;
	this.type = CWP_RELEASE;
	this.URL = data.URL;
	this.length = data.length;
	this.mode = CWP_UNRENDERED;
	this.categoryIDs = data.categoryIDs;
	fieldNames = fieldNames || manager.relFields;
	for (i=0;i<fieldNames.length;i++){
		this[fieldNames[i]] = data[fieldNames[i]];
	}
	customFieldNames = customFieldNames || manager.customRelFields;
	// There will always be custom data because of the ad content
	for (i=0;i<data.contentCustomData.length;i++){
		this[cwpRemoveSpaces(data.contentCustomData[i].title)] = data.contentCustomData[i].value;
	}
	if (!this.ClipType) this.ClipType = (!this.WebExclusive || this.WebExclusive=="No")?"episode":"other";

	// Modify release URL with advertising information
	// set a cnt keyvalue for each item in the comma separated Subject field
	if (this.Subject){
		var cntVals = this.Subject.split(",");
		for (i=0;i<cntVals.length;i++){
			this.URL += "&k"+(this.manager.extraParamCount+i)+"=cnt&v"+(this.manager.extraParamCount+i)+"="+cwpCleanAdParams(cntVals[i]);	
		}
	}

	// calculate the correct zone and 	
	newZone = "Zone="
	if (this.Zone) newZone += this.Zone;
	else if (this.Network||this.Show) newZone += cwpCleanStr(this.Network+this.Show);
	else newZone += this.manager.defaultZone;
	if (/Zone=[^&]*/.test(this.URL)) this.URL = this.URL.replace(/Zone=[^&]*/, newZone);
	else this.URL += "&" + newZone;
	if (/Format=[^&]*/.test(this.URL)) this.URL += "&format=SMIL";
	// If the clip has a duration greater than 2min, set clipLength to "long", otherwise it is "show"
	this.clipLength = "short";	
	if (this.length>120000) this.clipLength = "long";
	
	// Add some metadata to the release call
	this.URL += "&show=" + cwpCleanAdParams(this.Show) + "&episode=" + cwpCleanAdParams(this.Episode) + "&network=" + cwpCleanAdParams(this.Network) + "&season=" + cwpCleanAdParams(this.Season) + "&clipLength=" + this.clipLength;
	this.isSelected = false;

	// Methods
	this.renderHTML = function(targetContainer,startDepth,endDepth,withReleases){
		if (!this.manager.relRendering || this.manager.relRendering == null) logError("Cannot render Release. No rendering method defined.");
		this.element = this.manager.relRendering(targetContainer,this);
		cwpDisableSelection(this.element);
		this.element[0].cwpTarget = this;
		this.element.click(this.click);
		this.element.mouseover(this.mouseover);
		this.element.mouseout(this.mouseout);
		this.mode = CWP_RENDERED;
	}

	this.setUnrendered = function(){
		this.element = null;
		this.mode = CWP_UNRENDERED;
		this.isSelected = false;
	}

	this.click = function(callback){cwpEventProto(this.cwpTarget,"click",callback);}
	this.mouseover = function(callback){if (this.cwpTarget.element) this.cwpTarget.element.addClass(CWP_CLASS_HL);cwpEventProto(this.cwpTarget,"mouseover",callback);}
	this.mouseout = function(callback){if (this.cwpTarget.element) this.cwpTarget.element.removeClass(CWP_CLASS_HL);cwpEventProto(this.cwpTarget,"mouseout",callback);}
	this.select = function(callback){
		logDebug("Selecting: "  + this.title);
		if (callback)
			cwpEventProto(this,"select",callback);
		else if (!this.isSelected){
			this.isSelected = true;
			this.manager.setSelected(this,true);
			if (this.element) this.element.addClass(CWP_CLASS_SEL);
			cwpEventProto(this,"select",callback);
		}else logDebug("Cannot double select release: " + this.title);
	}
	this.unselect = function(callback){
		if (callback)
			cwpEventProto(this,"select",callback);
		else if (this.isSelected){
			this.isSelected = false;
			this.manager.setSelected(this,false);
			if (this.element) this.element.removeClass(CWP_CLASS_SEL);
			cwpEventProto(this,"unselect",callback);
		}else logDebug("Cannot double unselect release: " + this.title);
	}
	this.onVideoStart = function(evt,callback){cwpVideoEventProto(this,"onVideoStart",evt,callback);}
	this.onVideoPause = function(evt,callback){cwpVideoEventProto(this,"onVideoPause",evt,callback);}
	this.onVideoUnpause = function(evt,callback){cwpVideoEventProto(this,"onVideoUnpause",evt,callback);}
	this.onVideoEnd = function(evt,callback){cwpVideoEventProto(this,"onVideoEnd",evt,callback);}
	this.onVideoComplete = function(evt,callback){
		if (this.manager.playAllVideosInCategory) this.playNextVideo();
		cwpVideoEventProto(this,"onVideoComplete",evt,callback);
	}
	
	this.requestVideoPlay = function(){
		this.manager.setPlayingRelease(this);
		this.manager.queueReleaseForPlayback(this);
	}

	this.playVideo = function(){
		if (!tpController){
			logError("Could not find thePlatform playback component!");
		}else{
			logDebug("Attempting to start playback on release (" + this.ID + ") with URL: " + this.URL);
			tpController.setReleaseURL(this.URL);
			// Skip omniture setup if we need to disable it for this page
			if (this.manager.disableOmniture) return;
			// Send omniture information
			if ($("#playerwidget") && $("#playerwidget")[0].sendCategories && this.parentCat){
				var bucketList = new Array();
				// If a playerTag, has been defined, use that
				if (this.manager.playerTag){
					bucketList.push(this.manager.playerTag);
				}
				bucketList.push(this.parentCat.fullTitle);
				$("#playerwidget")[0].sendCategories(bucketList);
			}else{
				logDebug("Failed to find 'playerwidget' to send bucket information for Omniture.");
			}
		}
	}

	this.pauseVideo = function(doPause){
		if (!tpController){
			logError("Could not find thePlatform playback component!");
		}else{
			tpController.pause(doPause);
		}
	}

	this.playNextVideo = function(){
		if (!this.manager.relPlaylist){
			// If we don't have a parent there is no next video
			if (this.parentCat){
				// If the current video is the last in the list, we can't play next
				for (i=0;i<this.parentCat.children.length-1;i++){
					if (this.parentCat.children[i]==this){
						this.parentCat.children[i+1].requestVideoPlay();
						this.parentCat.children[i+1].select();
						break;
					}
				}
			}
		}
	}

	this.playPrevVideo = function(){
		if (!this.manager.relPlaylist){
			// If we don't have a parent there is no next video
			if (this.parentCat){
				// If the current video is the first in the list, we couldn't play next
				for (i=1;i<this.parentCat.children.length;i++){
					if (this.parentCat.children[i]==this){
						this.parentCat.children[i-1].requestVideoPlay();
						this.parentCat.children[i-1].select();
						break;
					}
				}
			}
		}
	}

	this.getRelease = function(key,value){
		if (this[key]==value) return this;
		else return null;
	}

	this.selectWithParents = function(toDepth){
		if (this.depth<toDepth) return;
		if (parentCat)
			parentCat.selectWithParents(toDepth);
		this.select();
	}
}

function catRender(makeContent, makeChildContainer){
	this.makeContent = makeContent;
	this.makeChildContainer = makeChildContainer;
}

var cwpManager = null;

function cwpInitializeManager(data){
	cwpManager = new cwpManagerObj(data);
	return cwpManager;
}

function cwpManagerObj(data){
	var qs = new cwpQS();
	if (qs.contains("cwpDebug"))
		$("body").append("<div id='debugOutput'></div>");
	if (!data.PID) return logError("You must provide a pid.");
	if (!data.playerTag && !data.pidList){return logError("You must provide a playerTag or pidList to initialize the cwpManager.");}
	// Constants
	this.PID = data.PID;
	this.playerTag = data.playerTag;
	this.pidList = data.pidList;
	this.catPlayerTag = data.catPlayerTag || data.playerTag;
	this.categoryCall = "getCategoryList?callback=?&field=ID&field=depth&field=hasReleases&field=fullTitle&PID=" + this.PID;
	this.categoryCall += "&query=CustomText|PlayerTag|" + this.catPlayerTag;
	this.releaseCall = "getReleaseList?callback=?&field=ID&field=contentID&field=PID&field=URL&field=categoryIDs&field=length&startIndex=1&endIndex=500&sortField=title&query=Categories|" + this.playerTag + "&PID=" + this.PID +
		"&contentCustomField=Show&contentCustomField=Episode&contentCustomField=Network&contentCustomField=Season&contentCustomField=Zone&contentCustomField=Subject"; // these are ad fields
	this.site = data.site;
	if (data.site) this.releaseCall += "&param=Site|"+data.site;
	this.defaultZone = "NOZONE";
	if (data.siteZone){this.defaultZone = data.siteZone;this.releaseCall += "&param=Zone|"+this.defaultZone;}
	this.version = "1.3.5";
	this.hideEmptyCats = !(data.hideEmptyCats==false);
	this.syncPageTitle = (data.syncPageTitle===true);

	// Incorporate ad parameters from in-page javascript object
	this.extraParamCount = 0;
	if (data.adParameters) {
		this.adParameters = data.adParameters;
		var extraParamCount = 0;
		var extraReleaseParams = "";
		for (key in data.adParameters.keyvalues){
			if (data.adParameters.keyvalues.hasOwnProperty(key)){
				var values = data.adParameters.keyvalues[key].split(";");
				for (var i=0;i<values.length;i++){
					extraReleaseParams += "&param=k"+(extraParamCount)+"|"+cwpCleanAdParams(key);
					extraReleaseParams += "&param=v"+(extraParamCount)+"|"+cwpCleanAdParams(values[i]);	
					extraParamCount++;
				}
			}
		}
		this.releaseCall += extraReleaseParams;
		this.extraParamCount = extraParamCount;
	}

	// Properties
	this.relRendering = null;
	this.catRendering = new Array();
	if (!data.categoryFields) data.categoryFields = ""; 
	data.categoryFields += ((data.categoryFields.length==0)?"":((data.categoryFields.indexOf(";")==-1)?";":",")) + "SortCriteria";
	this.defaultSort = "title"; // the default sort criteria
	// If a user wants to retrieve the title, retrieve displayTitle as well to make sure we give them the right string
	if (data.categoryFields.indexOf("title")!=-1 && data.categoryFields.indexOf("DisplayTitle")==-1) data.categoryFields += ((data.categoryFields.indexOf(";")==-1)?";":",") + "DisplayTitle";
	if (!data.categoryFields){
		this.catFields = new Array();
		this.customCatFields = new Array();
	}else{
		var tempFields = data.categoryFields.split(";");
		this.catFields = tempFields[0].split(",");
		if (tempFields.length>1){ this.catCustomFields = tempFields[1].split(","); }
	}
	if (!data.releaseFields){
		this.relFields = new Array();
		this.customRelFields = new Array();
	}else{
		var tempFields = data.releaseFields.split(";");
		this.relFields = tempFields[0].split(",");
		if (tempFields.length>1){ this.relCustomFields = tempFields[1].split(","); }
	}
	this.rootCats = new Array();
	this.targetPlaylistContainer = null;
	if (!data.jsonURL) // optional property
		this.jsonURL = "http://feeds.theplatform.com/ps/JSON/PortalService/2.2/"
	else
		this.jsonURL = data.jsonURL;
	this.currentPlay = null;
	this.previousPlay = null;
	this.playlistRootCat = null;
	this.jsonRequested = false;
	this.selectedCat = new Array();
	this.selectedRel = null;
	// Unless otherwise defined, we will play all the videos in a category
	this.playAllVideosInCategory = (data.playAllInCat!==false);
	this.playBlocked = false;
	this.queuedRelease = null;
	this.disableOmniture = (data.disableOmniture===true);

	this.cat = new Array();
	
	managerObj = this;
	this.OnPlayBlocked = null;
	this.catExists = function(depth){
		var curCat = this.cat[depth];
		if (!curCat) {
			curCat = new Object();
			curCat.depth = depth;
			if (depth>0){
				nextCat = this.catExists(depth-1);
				curCat.click = function(obj){managerObj.cat[this.depth-1].click(obj);}
				curCat.mouseover = function(obj){managerObj.cat[this.depth-1].mouseover(obj);}
				curCat.mouseout = function(obj){managerObj.cat[this.depth-1].mouseout(obj);}
				curCat.onChildrenShow = function(obj){managerObj.cat[this.depth-1].onChildrenShow(obj);}
				curCat.select = function(obj){managerObj.cat[this.depth-1].select(obj);}
				curCat.unselect = function(obj){managerObj.cat[this.depth-1].unselect(obj);}
			}else{
				curCat.click = function(obj){obj.select();} // click selects the category by default
				curCat.mouseover = function(obj){}
				curCat.mouseout = function(obj){}
				curCat.onChildrenShow = function(obj){}
				curCat.select = function(obj){}
				curCat.unselect = function(obj){}
			}
			this.cat[depth] = curCat;
		}
		return curCat;
	}
	this.catExists(0);
	
	this.evtProto = function(name,depth,callback){this.catExists(depth)[name] = function(obj){callback(obj);}}
	this.catClick = function(depth,callback){this.evtProto("click",depth,callback);}
	this.catMouseover = function(depth,callback){this.evtProto("mouseover",depth,callback);}
	this.catMouseout = function(depth,callback){this.evtProto("mouseout",depth,callback);}
	this.catSelect = function(depth,callback){this.evtProto("select",depth,callback);}
	this.catUnselect = function(depth,callback){this.evtProto("unselect",depth,callback);}
	this.catOnChildrenShow = function(depth,callback){this.evtProto("onChildrenShow",depth,callback);}

	this.rel = new Object();
	this.relClick = function(callback){this.rel.click = callback;}
	this.relMouseover = function(callback){this.rel.mouseover = callback;}
	this.relMouseout = function(callback){this.rel.mouseout = callback;}
	this.relSelect = function(callback){this.rel.select = callback;}
	this.relUnselect = function(callback){this.rel.unselect = callback;}
	this.relOnVideoStart = function(callback){this.rel.onVideoStart = callback;}
	this.relOnVideoEnd = function(callback){this.rel.onVideoEnd = callback;}
	this.relOnVideoComplete = function(callback){this.rel.onVideoComplete = callback;}
	this.relOnVideoPause = function(callback){this.rel.onVideoPause = callback;}
	this.relOnVideoUnpause = function(callback){this.rel.onVideoUnpause = callback;}
	this.rel.click = function(obj){obj.requestVideoPlay();obj.select();} // click selects and plays a release by default

	this.misc = new Object();
	this.miscFormatPageTitle = function(callback){this.misc.formatPageTitle = callback;}
	this.misc.formatPageTitle = new function(rel){return (rel)?rel.title:"";}

	this.toggleDevMode = function(devMode){
		if (this.playerTag){ // If there is no player tag, we won't do anything
			this.releaseCall = this.releaseCall.replace(/&query=Categories[|][^&]*/,"");
			if (!devMode)
				this.releaseCall += "&query=Categories|" + playerTag;
		}
	}

	this.setPlayingRelease = function(rel){
		if (this.currentPlay != rel){
			this.previousPlay = this.currentPlay;
			this.currentPlay = rel;
		}
	}

	// Methods
	this.getCategories = function(callback){
		var constructedJSON = this.jsonURL + this.categoryCall;
		$.each(this.catFields, function(i,field){
			constructedJSON += "&field=" + field;
		});
		if (this.catCustomFields){
			$.each(this.catCustomFields, function(i,field){
				constructedJSON += "&customField=" + field;
			});
		}
		var currentObj = this;
		this.jsonRequested = true;
		logDebug("Requested category feed: " + constructedJSON);
		$.getJSON(constructedJSON, function(data){
			currentObj.jsonRequested = false;
			logDebug("Retrieved JSON category data.");
			currentObj.handleCategoryFeed(data,callback);
		});
	}

	this.handleCategoryFeed = function(data,callback){
		if (typeof data == 'string') return logError("Category JSON request failed: " + data);
		if (data.items.length == 0){if (this.OnNoCategories) this.OnNoCategories();return;}
		var currentDepth = 0;
		var rootCatLevel = this.rootCats;
		var currentCatLevel = rootCatLevel;
		var parentCat = null;
		var managerObj = this;

		$.each(data.items,function(i,catData){
			if (currentDepth < catData.depth){
				if (currentCatLevel.length == 0){
					logDebug("We skipped a level of depth (expected " + currentDepth + " and got " + catData.depth + ").");
				}else{
					parentCat = currentCatLevel[currentCatLevel.length-1]; // the last category we added
					currentCatLevel = parentCat.children;
				}
				currentDepth = catData.depth;
			}else if (currentDepth > catData.depth){
				do{
					parentCat = parentCat.parentCat;
					if (parentCat == null) currentCatLevel = rootCatLevel;
					else currentCatLevel = parentCat.children;
				}while (currentCatLevel[0].depth > catData.depth);
				currentDepth = catData.depth;
			}
			currentCatLevel.push(new cwpCategory(managerObj, catData, parentCat, managerObj.catFields, managerObj.catCustomFields));
		});
		logDebug("Processed JSON category data.");
		callback();
	}

	this.renderCategories = function(targetContainer, startDepth, endDepth, withReleases){
		logDebug("Begin rendering top level categories.");
		if (startDepth==undefined || startDepth==null) startDepth = -1;
		if (endDepth==undefined || endDepth==null) endDepth = -1;
		if (withReleases==undefined || withReleases==null) withReleases = false;
		if (this.rootCats.length > 0) {
			$.each(this.rootCats, function(i,cat){
				cat.renderHTML(targetContainer, startDepth, endDepth, withReleases);
			});
		}
		logDebug("End rendering top level categories.");
	}

	this.getReleasesFromPidList = function(zone,pidList,callback){
		pidList = pidList || this.pidList; // If we don't override the pidList, use the default one from initialization
		// We use "story" as the fullTitle, as that is the bucket used for Omniture tracking
		var catData = {"depth":0,"fullTitle":"story","hasReleases":true,"ID":CWP_PLACEHOLDER_ID,"title":"FAKECAT"};
		cwpManager.rootCats[0] = new cwpCategory(cwpManager, catData);
		
		for (var i=0;i<pidList.length;i++){
			this.rootCats[0].children.push(this.getReleaseFromPid(pidList[i],zone,this.rootCats[0]));
		}
		if (pidList.length>0){
			this.rootCats[0].releasesLoaded = true;
			this.releasesLoaded();
		}
	}

	this.getReleaseFromPid = function(pid,zone,parentCat){
		var relData = {"categoryIDs":[CWP_PLACEHOLDER_ID],"contentCustomData":[{"title":"Show","value":""},{"title":"Episode","value":""},{"title":"Network","value":""},{"title":"Season","value":""},{"title":"Zone","value":zone}],"contentID":CWP_PLACEHOLDER_ID,"ID":CWP_PLACEHOLDER_ID,"length":120000,"PID":pid,"URL":"http://release.theplatform.com/content.select?pid=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&UserName=Unknown&Embedded=True&Portal=Global%20News&Site=" + this.site + "&TrackBrowser=True&Tracking=True&TrackLocation=True"};
		relData.URL = relData.URL.replace(/pid=[^&]*/, "pid=" + pid);
		// We repurpose the code from initialization, but since we need the params in a different format, we don't use the same code
		if (typeof(this.adParameters)=="object") {
			var extraParamCount = 0;
			for (key in data.adParameters.keyvalues){
				if (data.adParameters.keyvalues.hasOwnProperty(key)){
					var values = data.adParameters.keyvalues[key].split(";");
					for (var i=0;i<values.length;i++){
						relData.URL += "&k"+(extraParamCount)+"="+cwpCleanAdParams(key);
						relData.URL += "&v"+(extraParamCount)+"="+cwpCleanAdParams(values[i]);
						extraParamCount++;
					}
				}
			}
		}
		return new cwpRelease(this, relData, parentCat);
	}

	// Event listeners
	this.onMediaStart = function(evt){
		logDebug("Received onMediaStart event for: " + evt.data.baseClip.contentID);
		if (!cwpManager.currentPlay) logError("Received a start event without an active release.");
		cwpManager.currentPlay.onVideoStart(evt);
		cwpManager.updatePlaybackQueueStatus(evt.data.baseClip.noSkip);
		cwpManager.firstPlayHappened = true;
		cwpManager.ffdRewClicked = false;
		cwpComScore(cwpManager);
	}

	this.onMediaEnd = function(evt){
		logDebug("Received onMediaEnd event for: " + evt.data.baseClip.contentID);
		if (!cwpManager.currentPlay) logDebug("Received an end event without an active release.");
		if (!evt.data.isAd){
			if (cwpManager.currentPlay && cwpManager.currentPlay.contentID == evt.data.baseClip.contentID)
				cwpManager.currentPlay.onVideoEnd(evt);
			else if (cwpManager.previousPlay && cwpManager.previousPlay.contentID == evt.data.baseClip.contentID)
				cwpManager.previousPlay.onVideoEnd(evt);
		}
	}

	this.onReleaseEnd = function(evt){
		logDebug("Received onReleaseEnd event for: " + cwpManager.currentPlay.ID);
		if (evt.data.isComplete){
			cwpManager.updatePlaybackQueueStatus(false); // Turn off queue blocking because we know the ad is over
			if (cwpManager.currentPlay.ID==CWP_PLACEHOLDER_ID || cwpManager.currentPlay.ID==evt.data.playlistID){
				cwpManager.currentPlay.onVideoComplete(evt);
			}
		}
	}

	this.onMediaPause = function(evt){
		logDebug("Received onMediaPause event for: " + evt.data.baseClip.contentID);
		if (!cwpManager.currentPlay) logError("Received a pause event without an active release.");
		cwpManager.currentPlay.onVideoPause(evt);
	}

	this.onMediaUnpause = function(evt){
		logDebug("Received onMediaUnpause event for: " + evt.data.baseClip.contentID);
		if (!cwpManager.currentPlay) logError("Received an unpause event without an active release.");
		cwpManager.currentPlay.onVideoUnpause(evt);
	}

	this.ffdRewClicked = false;
	this.onNextClip = function(){
		if (!cwpManager.ffdRewClicked){
			cwpManager.ffdRewClicked = true;
			logDebug("tpController onNextClip()");
			if (!cwpManager.currentPlay) logDebug("Received an nextClip event without an active release.");
			cwpManager.currentPlay.playNextVideo();
		}
	}

	this.onPrevClip = function(){
		if (!cwpManager.ffdRewClicked){
			cwpManager.ffdRewClicked = true;
			logDebug("tpController onPreviousClip()");
			if (!cwpManager.currentPlay) logDebug("Received an previousClip event without an active release.");
			cwpManager.currentPlay.playPrevVideo();
		}
	}

	this.onPlayerLoaded = function(){
		logDebug("*** Player has loaded ***");
		if (tpController.disablePlayControl && cwpManager.anyReleasesLoaded) tpController.disablePlayControl(false);
		if (cwpManager.queuedRelease){
			cwpManager.queueReleaseForPlayback(cwpManager.queuedRelease);
			cwpManager.queuedRelease = null;
		}
	}

	this.onPlayClickCallback = null;
	this.firstPlayHappened = false;
	this.OnPlayClick = null;
	this.onPlayClick = function(){
		if (cwpManager.anyReleasesLoaded){
			if (!cwpManager.firstPlayHappened){
				logDebug("tpController onPlayClick()");
				if (cwpManager.OnPlayClick) cwpManager.OnPlayClick();
				cwpManager.firstPlayHappened = true;
			}
			tpController.removeEventListener("OnPlayButtonClick", "cwpManager.onPlayClick");
		}
	}

	this.OnNoCategories = null;

	// Add event listeners
	if (tpController){
		tpController.addEventListener("OnMediaStart","cwpManager.onMediaStart");
		tpController.addEventListener("OnMediaEnd","cwpManager.onMediaEnd");
		tpController.addEventListener("OnReleaseEnd","cwpManager.onReleaseEnd");
		tpController.addEventListener("OnMediaComplete","cwpManager.onMediaComplete");
		tpController.addEventListener("OnMediaPause","cwpManager.onMediaPause");
		tpController.addEventListener("OnMediaUnpause","cwpManager.onMediaUnpause");
		tpController.addEventListener("OnNextClip", "cwpManager.onNextClip");
		tpController.addEventListener("OnPreviousClip", "cwpManager.onPrevClip");
		tpController.addEventListener("OnPlayButtonClick", "cwpManager.onPlayClick");
		tpController.addEventListener("OnPlayerLoaded", "cwpManager.onPlayerLoaded");
	}

	this.anyReleasesLoaded = false;
	this.releasesLoaded = function(){
		if (!this.anyReleasesLoaded){
			this.anyReleasesLoaded=true;
			if (tpController.disablePlayControl) tpController.disablePlayControl(false);
		}
	}

	this.getCategory = function(key,value){
		var returnCat = null;
		$.each(this.rootCats,function(i,cat){
			returnCat = cat.getCategory(key,value);
			if (returnCat) return false;
		});
		return returnCat;
	}

	this.getRelease = function(key,value){
		var returnCat = null;
		$.each(this.rootCats,function(i,cat){
			returnCat = cat.getRelease(key,value);
			if (returnCat) return false;
		});
		return returnCat;
	}

	this.fetchSingleRelease = function(key,value,callback){
		if (!this.jsonRequested){
			// construct category list
			var constructedJSON = this.jsonURL + this.releaseCall + "&query=" + key + "|" + value;
			$.each(this.relFields, function(i,field){
				constructedJSON += "&field=" + field;
			});
			if (this.relCustomFields){
				$.each(this.relCustomFields, function(i,field){
					constructedJSON += "&contentCustomField=" + field;
				});
			}
			var currentObj = this;
			this.jsonRequested = true;
			var currentObj = this;
			//$("#currentReleaseFrame").html(constructedJSON);
			$.getJSON(constructedJSON, function(data){
				currentObj.jsonRequested = false;
				var result = currentObj.processSingleReleaseFeed(data,callback);
				if (callback) callback(result);
			});
		}	
	}
	
	this.processSingleReleaseFeed = function(data,callback){
		this.singleRelease = null;
		if (typeof data == 'string') return logDebug("Release JSON request failed: " + data);
		if (data.items.length!=1){
			logDebug("Single release request returned " + data.items.length + " retults.  Expected exactly 1.");
			return false;
		}else{
			this.singleRelease = new cwpRelease(this,data.items[0],null,this.relFields,this.relCustomFields);
			return true;
		}
	}

	this.findFirstParentCategory = function(rel){
		var mObj = this;
		var parentCat = null;
		$.each(mObj.rootCats,function(i,obj){
			parentCat = obj.findFirstParentCategory(rel);
			if (parentCat) return false;
		});
		return parentCat;
	}

	this.setSelected = function(obj,isSelect){
		// If we are selecting something, call unselect on the previously selected item at the same level
		if (obj.type==CWP_CATEGORY){
			if (isSelect){
				if (this.selectedCat[obj.depth] && this.selectedCat[obj.depth] != obj){
					this.selectedCat[obj.depth].unselect();
				}
				this.selectedCat[obj.depth] = obj;
			}else if (this.selectedCat[obj.depth] == obj)
				this.selectedCat[obj] = null;
		}else if (obj.type==CWP_RELEASE){
			if (isSelect){
				if (this.selectedRel && this.selectedRel != obj){
					this.selectedRel.unselect();
				}
				this.selectedRel = obj;
				// We have selected a release, update the page title if necessary
				this.setPageTitle(obj);
			}else if (this.selectedRel == obj)
				this.selectedRel = null;
		}
	}

	this.setPageTitle = function(rel){
		if (this.syncPageTitle) {
			document.title = this.misc.formatPageTitle(rel);
		}
	}
	
	this.queueReleaseForPlayback = function(rel){
		if (!this.playBlocked){
			// This method won't be available until the player finishes loading.
			if ($("#playerwidget")[0].sendCategories){
				this.playBlocked = true; // We should block until we confirm that it's not an ad playing next
				rel.playVideo();
			}else{
				this.queuedRelease = rel;
			}
		}else{
			if (this.OnPlayBlocked) this.OnPlayBlocked(true,rel);
			this.queuedRelease = rel;
		}
	}

	this.updatePlaybackQueueStatus = function(blockQueue){
		this.playBlocked = blockQueue;
		// If the queue has become unblocked, play the most recent video request
		if (!blockQueue && this.queuedRelease){
			logDebug("Trying to play queued video: " + this.queuedRelease.title);
			this.queuedRelease.playVideo();
			if (this.OnPlayBlocked) this.OnPlayBlocked(false,this.queuedRelease);
			this.queuedRelease = null;
		}
	}
}
