//Move Networks Template Player
//Copyright 2007 Move Networks, Inc.

/********************************************************************************************/
//																							//
//-----------------------------------------  MN  -------------------------------------------//
//																							//
/********************************************************************************************/
//The MN "namespace" is used by the SDK.  I have created a few functions under it when I	//
//thought it might be more appropriate to do so, i.e., they are used by several of the		//
//widgets and/or are more generic than just a "template player" function.  Otherwise, the   //
//MN.TP "namespace" is used to wrap the functions.											//
/********************************************************************************************/


//MN.SecToObj ------------------------------------------------------------------------------//
//Takes seconds and converts it into an object with hours, minutes, and seconds				//
//------------------------------------------------------------------------------------------//
MN.SecToObj = function(s){
	if (s < 0)
		s = 0;

	var h = Math.floor(s / 3600);
	s -= h * 3600;
	var m = Math.floor(s / 60);
	s -= m * 60;
	
	return {'hour':h, 'minute':m, 'second':s};
};

//MN.HourTo12Time---------------------------------------------------------------------------//
//Takes hours and converts it to a 12 hour time object, with the hours						//
//and "am" or "pm".																			//
//------------------------------------------------------------------------------------------//
MN.HourTo12Time = function(h){
	var am_pm = 'am';
	if(h >= 12) {
		if(h != 12)
			h = h - 12;
		am_pm = 'pm';
	}
	else{
		if(h == 0)
			h = 12;
	}
	return {'hour' : h, 'am_pm' : am_pm};
};

//MN.ConvertToTimestamp---------------------------------------------------------------------//
//This ConvertToTimestamp function takes either seconds or a date object with properties	//
//"hour", "minute", and "second" (the object returned from PosToDatetime *hint hint*) and   // 
//formats it according to the formatString provided in the second parameter.  The string is // 
//of the basic format "HH:MM:SS.SS", but can be changed to produce the desired timestamp.   //
//For example, "HH:MM" will only show hours and minutes; "MM:SS" will show minutes and		//
//seconds, etc.  For hours, you can use a single "H" to hide the leading zero ("HH" would   //
//produce "01", but "H" will just show "1").  You can also use a question mark ("?") after  //
//the "H"(s) to hide the hour slot if hours computes to zero ("HH" produces "00:MM", "HH?"  //
//produces "MM").  Putting a period (".") after seconds will display subsecond digits up to //
//as many "S"s that you add after the period ("MM:SS.S" produces "00:00:01.1",				//
//"MM:SS.SS" produces "00:00:01.11", etc.)  And finally, lowercase characters display 12	//
//hour time format, so use "hh:mm:ss" if you want am/pm.  Note that some options are not	//
//applicable to 12 hour time ("?", and there will never be leading zeros in the hour slot - //	 
//"HH" and "H" are the same).																//
//																							//
//SPECIAL NOTE:  THERE IS ALREADY A FUNCTION NAMED "MN.ConvertToTimestamp" IN THE SDK THAT  //
//THIS OVERRIDES.  ALL SDK WIDGETS AND OBJECTS FOUND IN THE TEMPLATE PLAYER THAT USE		//
//MN.ConvertToTimestamp HAVE BEEN MODIFIED TO USE THIS VERSION								//
//------------------------------------------------------------------------------------------//
MN.ConvertToTimestamp = function(arg, formatString){
	var ret = '';
	var is12 = false;
	var am_pm = '';
	var obj = null;
	
	if(typeof arg == 'number')
		obj = MN.SecToObj(arg);
	else if(typeof arg == 'object')
		obj = arg;
	else{
		//logError('MN.TP.ConvertToTimestamp: Invalid argument: ', arg);
		return;
	}
	
	if(formatString.charAt(0) >= 'a' && formatString.charAt(0) <= 'z'){ //lowercase, 12 hour time
		is12 = true;
	}
	
	formatString = formatString.toLowerCase();  //convert to lowercase to reduce number of conditionals
	var parts = formatString.split(':');
	for(var i = 0; i < parts.length; i++){
		if(parts[i].charAt(0) == 'h'){
			if(is12){
				//I don't think anybody is going to want to pad the hours when in 12 hour time
				ret += MN.HourTo12Time(obj.hour).hour + ':';
				am_pm = MN.HourTo12Time(obj.hour).am_pm;
			}
			
			//a '?' hides the hours if it is 0. Also, this option only applies to 24 hour format; 
			//you will never have "0 o'clock" with 12 hour time
			else if(parts[i].indexOf('?') == -1 || obj.hour > 0){
				ret += parts[i].charAt(1) == 'h' ? MN.PadDigits(obj.hour, 2) : obj.hour; //if it is 'hh', pad the digits
				ret += ':';
			} 
		}
		else if(parts[i].charAt(0) == 'm'){
			if(parts[i].charAt(1) == 'm' || formatString.indexOf('h') != -1) //if timestamp has hours, pad minutes no matter what
				ret += MN.PadDigits(obj.minute, 2);
			else
				ret += obj.minute;
			ret += ':';
		}
		else if(parts[i].charAt(0) == 's' && parts[i].charAt(1) == 's'){ //can be 'ss' or 'ss.s', 'ss.ss', etc.
			var subSec = parts[i].split('.')[1];
			if(subSec)
				ret += MN.PadDigits(obj.second.toFixed(subSec.length), subSec.length + 2);
			else
				ret += MN.PadDigits(obj.second.toFixed(0), 2);
			ret += ':';
		}
	}
	//chop off the extra ':'
	ret = ret.slice(0,-1);
	ret += am_pm;
	return ret;
};

//MN.PopulateTemplate-----------------------------------------------------------------------//
//This function is used for both the playlist and the show timeline.  It takes a DOM element//
//(template) and populates it with data from the showObj (which in these two cases			//
//originates from a qvt) by finding special class names in the element and its children.	//
//------------------------------------------------------------------------------------------//

MN.PopulateTemplate = function(template, showObj, posToDatetime){
	var newBox = template.cloneNode(true);
	var curElement;
	for(var curProp in showObj){
		if(curProp == 'tlStartTime' || curProp == 'tlStopTime')
			curProp = curProp.replace('tlS', 's');
	
		//append "mn_show" to the property and search for and element with that class
		elements = MN.GetElementsByClassName(newBox, 'mn_show_' + curProp, true);
		if(curProp == 'startTime' || curProp == 'stopTime' || curProp == 'duration'){ //these three use timestamps
			var timeFormat;
			var ts;
			if(curProp == 'startTime' || curProp == 'stopTime')
				curProp = 'tlS' + curProp.slice(1);  //the property in the showObj is tlStartTime or tlStopTime
				
			for(var i = 0; i < elements.length; i++){
				curElement = elements[i];
				timeFormat = curElement.innerHTML || 'HH:MM:SS';
				
				//force to 24 hour timestamp when necessary.  The absence of posToDatetime
				//means that the content is "vod", and so we force a 24 hour timestamp
				if(curProp == 'duration' || !posToDatetime)
					timeFormat = timeFormat.toUpperCase();
				
				if(posToDatetime)
					ts = MN.ConvertToTimestamp(posToDatetime(showObj[curProp]), timeFormat);
				else
					ts = MN.ConvertToTimestamp(showObj[curProp], timeFormat);
				 
				curElement.innerHTML = ts;
			}
		}
		else if(curProp == 'thumbnail'){
			for(var i = 0; i < elements.length; i++){
				elements[i].innerHTML += '<img src="' + showObj[curProp] + '" />';
			}
		}
		else{
			for(var i = 0; i < elements.length; i++){
				elements[i].innerHTML += showObj[curProp];
			}
		}
	}
	
	return newBox;
};

//MN.GetElementsByClassName-----------------------------------------------------------------//
//This function searches an element's children for a specific class name, and returns an	//
//array of those elements.  The third option, recursive, is a boolean flag that tells the   //
//function to search the children's children, and the children's childrens' children, etc.  //
//------------------------------------------------------------------------------------------//
MN.GetElementsByClassName = function(element, className, recursive){
	var array = [];
	if(element.hasChildNodes()){
		var child;
		for(var i = 0; i < element.childNodes.length; i++){
			child = element.childNodes[i];
			if(child.nodeType == 1){ //make sure it is not a text node
				//I don't think the order matters much
				if(child.className == className)
					array.push(child);
				if(recursive)
					array = array.concat(this.GetElementsByClassName(child, className, recursive));
			}
		}
	}
	return array;
};


/********************************************************************************************/
//																							//
//----------------------------------------  MN.TP  -----------------------------------------//
//																							//
/********************************************************************************************/
//The MN.TP class is used to protect the template player functions from possibly	  		//
//conflicting with other things on the page.												//
/********************************************************************************************/


//------------------------------------------------------------------------------------------//
//-------------------------------  Variables and Constants ---------------------------------//
//------------------------------------------------------------------------------------------//
MN.TP = {}; 
 //the parameters passed to this page (obtained by calling MN.GetPageParams in the 
 //MN.TP.WindowLoaded function)
MN.TP.params; 
//the player object reference
MN.TP.qmp = null;  
//The template player works based on a configuration file, config.js, that sets up a few 
//data structures that are used to configure the page behavior.  The most atomic of these
//is a "channel".  MN.TP.curChnl stores the channel that we are on so we can reference the
//configuration.
MN.TP.curChnl;
MN.TP.maximized = false;
MN.TP.resizable = false;
MN.TP.firstTl = true;
MN.TP.paused = false;

MN.TP.MAX_FFORWARD_SPEED = 16;
MN.TP.MAX_REWIND_SPEED = -16;
MN.TP.LIVE_THRESHOLD = 90;

//MN.TP.SetStatusMsg------------------------------------------------------------------------//
//Sets the status message area with a particular message.  									//
//------------------------------------------------------------------------------------------//
MN.TP.SetStatusMsg = function(msg){
	//find the elements with a special class, "mn_player_status"
/*	var elements = MN.GetElementsByClassName(document.body, 'mn_player_status', true);
	for(var i = 0; i < elements.length; i++){
		MN.SetInnerText(elements[i], msg);
	}
*/
	if($('mn_status'))
		MN.SetInnerText($('mn_status'), msg);
};

//MN.TP.PopupWin----------------------------------------------------------------------------//
//This is called when a user clicks on an anchor that has been set as a "popup" via			//
//MN.TP.InitPopups.  It opens a new window to the link.										//
//------------------------------------------------------------------------------------------//
MN.TP.PopupWin = function(){
	//the function has been bound via MN.MakeBound, so "this" references the anchor element
	var width = this.getAttribute('popup_width') || 650;
	var height = this.getAttribute('popup_height') || 664;
	var scroll = this.getAttribute('popup_scroll') != 'false' ? 'yes' : 'no';
	var resize = this.getAttribute('popup_resize') != 'false' ? 'yes' : 'no';
	window.open(this.getAttribute('href'),'popup','width=' + width + ',height=' + height + ',status=yes,scrollbars=' + scroll + ',resizable=' + resize + ',location=no,toolbar=no');
};


//------------------------------------------------------------------------------------------//
//--------------------------------  Player Control Functions  ------------------------------//
//------------------------------------------------------------------------------------------//
//These functions are used to control the player, and are called by the buttons on the page.//
//------------------------------------------------------------------------------------------//

//The back button behaves similar to the back button on a DVD player.  When first clicked, it
//will restart the current show.  If pressed again within a certain amount of time, it
//will start skipping back shows.  The following two variables are used to maintain that
//behavior
MN.TP.skipping = false;
MN.TP.skipTimer = null;
//MN.TP.Back--------------------------------------------------------------------------------//
//Called to skip back a show in the timeline.  If the current channel has an epg (meaning  	//
//there is more than just one timeline available to view) check to see if there is a	   	//
//"previous" timeline, if so, skip to the last show of that timeline					   	//
//------------------------------------------------------------------------------------------//
MN.TP.Back = function(){
   /* log('back');
	var showNum = MN.TP.qmp.CurrentShow();
	var qvt = MN.TP.qmp.CurrentQVT();
	if(showNum < 0)
		showNum = qvt.ShowCount() - 1;

	if(!MN.TP.skipping){ //restart the show
		MN.TP.qmp.CurrentPosition(qvt.StartTime(showNum));
		MN.TP.skipping = true;
		MN.TP.skipTimer = setTimeout('MN.TP.skipping = false', 5000);
	}
	else{ //skip back a show
		clearTimeout(MN.TP.skipTimer);
		
		if(MN.TP.curChnl._epg && showNum == 0){ //we have more than one day of content, and are on the first show
			var prevURL = qvt.PrevURL();
			if(prevURL){
				//see if the previous timeline is in the epg, and therefore valid to skip back to.
				//by the time a user gets to this point, the previous url will have been loaded into the
				//epg if it is valid (the epg loads fairly quickly), so if prevURL is not in the epg
				//it is assumed as invalid, not unloaded.
				for(var i = 0; i < MN.TP.curChnl._epg.urlList.length; i++){
					if(MN.TP.curChnl._epg.urlList[i] == prevURL){
						MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.SkipToLastShow);
						MN.TP.qmp.Load(prevURL);
						return;
					}
				}
			}
		} 
		else{
			MN.TP.qmp.CurrentShow(showNum - 1);
		}
		
		MN.TP.skipTimer = setTimeout('MN.TP.skipping = false', 5000);
	}*/
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;
	MN.PB.backward();
};

//MN.TP.SkipToLastShow----------------------------------------------------------------------//
//Skips to the last show of a qvt.  This is used in conjunction with the MN.TP.Back function//
//------------------------------------------------------------------------------------------//
MN.TP.SkipToLastShow = function(qvt){
	var numOfShows = qvt.ShowCount();
	MN.TP.qmp.CurrentShow(numOfShows - 1);
	MN.Event.StopObserving(MN.TP.qmp, 'TimelineLoaded', MN.TP.SkipToLastShow);
};


//The following two variables are used with the MN.TP.Rewind and MN.TP.FastForward functions
MN.TP.scrubRate = 0;
//States:
// -1 ready to scrub
//  0 starting to scrub
// >0 preparing to scrub
MN.TP.scrubState = -1;

//MN.TP.Rewind------------------------------------------------------------------------------//
//The rewind button works much like the rewind function of a DVD player.  You can click it	//
//multiple times to obtain different rewind rates.  In this case, the rates double each time//
//they are clicked, up to MN.TP.MAX_REWIND_SPEED.  Both the MN.TP.Rewind and				//
//MN.TP.FastForward functions work on a "queue" system.  Because of limitations in the		//
//player, calls to player.Scrub should not be made rapidly.  To overcome this, every time	//
//this function is called, it queues up a timeout to call the MN.TP.DoScrub function (which	//
//actually makes the call to player.Scrub).  This timeout is set for 500 milliseconds. When	//
//MN.TP.DoScrub is actually called, it checks to see if there has been a more recent call	//
//queued up.  This queue allows the user to click on the rewind and fast forward buttons as	//
//many times as they like without causing problems with the player.							//
//------------------------------------------------------------------------------------------//
MN.TP.Rewind = function(){
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;

	if(MN.TP.scrubState < 0)
		MN.TP.scrubState = 1;
	else if(MN.TP.scrubState == 0)
		return;
	else
		MN.TP.scrubState++;
   
	if(MN.TP.scrubRate >=0 || (MN.TP.scrubRate * 2) < MN.TP.MAX_REWIND_SPEED)
		MN.TP.scrubRate = -2;
	else
		MN.TP.scrubRate *= 2;
	
	log('attempting to scrub at rate: ', MN.TP.scrubRate, 'x');
	MN.TP.SetStatusMsg('Rewind: ' + MN.TP.scrubRate + 'x');
	setTimeout('MN.TP.DoScrub(' + MN.TP.scrubRate + ')', 500);
};

//MN.TP.Pause-------------------------------------------------------------------------------//
//Pauses the player and sets the appropriate status message.								//
//------------------------------------------------------------------------------------------//
MN.TP.Pause = function(){
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;

	log('pause');
	MN.TP.qmp.Paused(true);
	MN.TP.paused = true;
	MN.TP.SetStatusMsg('Paused');
};

//MN.TP.Play--------------------------------------------------------------------------------//
//Called after a pause or while scrubbing to start normal playback							//
//------------------------------------------------------------------------------------------//
MN.TP.Play = function(){
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;

	log('play');
	if(MN.TP.qmp.scrubbing)
	{
		MN.TP.qmp.StopScrubbing();
		MN.TP.scrubRate = 0;
	}
	MN.TP.qmp.Paused(false);
	MN.TP.paused = false;
	var br = MN.TP.qmp.CurrentBitRate();
	if(br > 0)
		MN.TP.SetStatusMsg('Playing ' + br + 'kbps');
	else
		MN.TP.SetStatusMsg('Playing ');
};

//MN.TP.FastForward-------------------------------------------------------------------------//
//See MN.TP.Rewind																			//
//------------------------------------------------------------------------------------------//
MN.TP.FastForward = function(){
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;

	if(MN.TP.scrubState < 0)
		MN.TP.scrubState = 1;
	else if(MN.TP.scrubState == 0)
		return;
	else
		MN.TP.scrubState++;
	
	if(MN.TP.scrubRate <=0 || (MN.TP.scrubRate * 2) > MN.TP.MAX_FFORWARD_SPEED)
		MN.TP.scrubRate = 2;
	else
		MN.TP.scrubRate *= 2;
	
	log('attempting to scrub at rate: ', MN.TP.scrubRate, 'x');
	MN.TP.SetStatusMsg('Forward: ' + MN.TP.scrubRate + 'x');
	setTimeout('MN.TP.DoScrub(' + MN.TP.scrubRate + ')', 500);
};

//MN.TP.Forward-----------------------------------------------------------------------------//
//Skips forward a show in the timeline.  If we are on the last show of the timeline, and the//
//current channel has an EPG, the EPG is checked to see if there is a "next" timeline.  If	//
//so, the next timeline is played.															//
//------------------------------------------------------------------------------------------//
MN.TP.Forward = function(){
	/*log('forward');
	var showNum = MN.TP.qmp.CurrentShow();
	var qvt = MN.TP.qmp.CurrentQVT();
	if(showNum < 0)
		showNum = qvt.ShowCount() - 1;
	
	if(MN.TP.curChnl._epg && showNum == (qvt.ShowCount() - 1)){ //there are more timelines, and we are on the last show
		var nextURL = qvt.NextURL();
		if(nextURL){
			for(var i = 0; i < MN.TP.curChnl._epg.urlList.length; i++){
				if(MN.TP.curChnl._epg.urlList[i] == nextURL){
					//the playlist ignores "live" qvts when they reload (playlist.js _LoadQVT), and due
					//to that, we need to explicitly tell the playlist to load when it is an okay instance
					//like this
					if(MN.TP.qmp.playlist){		
						MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.LiveTlLoaded);
					}	
					log('forward, switching urls');
					 MN.TP.qmp.Play(nextURL, 0);
					return;
				}
			}
		}   
	}
	else{
		MN.TP.qmp.CurrentShow(showNum+ 1);
	}*/
	if(typeof MN.PB == 'object' && typeof MN.PB.adPlaying != 'undefined' && MN.PB.adPlaying == true)
		return;
	MN.PB.forward();
};

MN.TP.LiveTlLoaded = function(qvt){
	MN.Event.StopObserving(MN.TP.qmp, 'TimelineLoaded', MN.TP.LiveTLLoaded);
	log('live tl loaded');
	if(qvt.IsOpenEnded())
		MN.TP.qmp.playlist._Populate(qvt);
};


//MN.TP.DoScrub-----------------------------------------------------------------------------//
//This function is called by a timeout set up by the MN.TP.Rewind and MN.TP.FastForward		//
//functions in order to actually make the call to the player to scrub.  By checking the		//
//MN.TP.scrubState variable, the function determines if another call to scrub was queued up	//
//during the timeout.  If there was, then the current scrub call is ignored and the queue 	//
//starts to empty.  When the last queued call to scrub is made, it actually calls			//
//MN.TP.qmp.Scrub with the appropriate rate.												//
//------------------------------------------------------------------------------------------//
MN.TP.DoScrub = function(rate){
	MN.TP.scrubState--;
	//another scrub call was queued up, ignore this one
	if(MN.TP.scrubState > 0)
		return;
  
	//allow user to queue up more scrubbing
	MN.TP.scrubState = -1;
	if(MN.TP.qmp.Scrub(rate)){
		log('sucessful scrub at rate: ', rate);
		MN.TP.SwapPausePlay(!MN.TP.paused);
		
	}
	else{
		log('scrub unsuccessful at rate: ', rate);
		var msg = rate > 0 ? 'fast forward ' : 'rewind ';
		MN.TP.SetStatusMsg('Unable to ' + msg);
		MN.TP.scrubRate = 0;
		
		if(MN.TP.qmp.Paused()){
			setTimeout(function(){
				MN.TP.SetStatusMsg('Paused');
			}, 3000);
		}
		else{
			setTimeout(function(){
				MN.TP.SetStatusMsg('Playing ' + MN.TP.qmp.CurrentBitRate() + 'kbps');
			}, 3000);
		}
	}
};

//MN.TP.GoLive------------------------------------------------------------------------------//
//Sets playback at "live".  This could be used with VOD content, but would just skip to the	//
//end of the content, so hopefully the user is smart enough not to have a go live button for//
//VOD content.  Also, the convention is that MN.TP.curChnl.url is "today's" url, so if the 	//
//current channel has an epg, we check to see if we need to load today's url and start 		//
//playback at the end of that "live" url.													//
//------------------------------------------------------------------------------------------//
MN.TP.GoLive = function(){
	log('go live');
	MN.TP.qmp.playlist._boxTemplate = MN.TP.curChnl.playlist.template;
	var position = MN.TP.qmp.CurrentPosition();
	var duration = MN.TP.qmp.CurrentQVT().Duration();
	if(MN.TP.curChnl._epg){
		var todaysQVT = MN.QVT.AcquireQVT(MN.TP.curChnl.url);
		if(todaysQVT != MN.TP.qmp.CurrentQVT()){
			log('going live and switching qvt');
			MN.TP.qmp.Play(todaysQVT, todaysQVT.Duration());
			if(MN.TP.qmp.playlist)
				MN.TP.qmp.playlist._Populate(todaysQVT);
			return;
		}
	}
	//if playback is less than MN.TP.LIVE_THRESHOLD seconds behind "live", don't bother going live
	if((duration - position > MN.TP.LIVE_THRESHOLD)){  
		log('going live');
		MN.TP.qmp.CurrentPosition(duration);
		return;
	}
};


//MN.TP.Maximize----------------------------------------------------------------------------//
//Switches the page to fullscreen.  This is tailored to the HTML.  There are basically two	//
//methods to get a fullscreen player.  One is to explicitly set the width and height of the	//
//player whenever the window resizes (via the window.OnResize event), or the HTML can		//
//carefully be crafted so that the player container resizes naturally with the window.  The	//
//player width and height can then be set to 100%, and will automatically resize to fit its	//
//container (which has been set to a percentage value, thus resizing with the page).  The 	//
//latter is easier from a javascript standpoint, however, in our experience	it has been a 	//
//bit tricky to get the HTML correct to display the player when its height is set to a 		//
//percentage (the player disappears).  In this implementation, we have gotten the height to	// 
//behave correctly, and so we set the player width and height to 100%, and then	control the //
//player container ("mn_player") size to do the things we need, such as adjusting the player//
//height for aspect ratio.  This also allows the CSS to control the size of the player, 	//
//separating look from function. 															//																		
//------------------------------------------------------------------------------------------//
MN.TP.Maximize = function(evt){
	if(MN.TP.maximized)
		return;
		
	window.moveTo(0,0);
	window.resizeTo(screen.availWidth, screen.availHeight);

	MN.TP.SetToResizable(evt);

	MN.TP.maximized = true;
	MN.TP.OnResize();
	if (!MN.nonIE) { //all this is added to try and bypass low memory ie6 scariness (see larger note in SetToResizable)
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
	}
};

MN.TP.SetToResizable = function(evt){
	evt = evt || window.event;
	evt.cancelBubble = true;
	
	var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'mn_show_title', 'copyright', 'shows', 'episodes_container', 'logo', 'back_home', 'ads', 'page_brand'];
	for(var i = 0; i < hiddenArray.length; i++){
		if($(hiddenArray[i]))
			$(hiddenArray[i]).className = 'mn_hidden';
	}
	
	var maximizeArray = ['mn_public_page', 'mn_controller', 'mn_player_container', 'mn_container'];
	for(var i = 0; i < maximizeArray.length; i++){
		if($(maximizeArray[i]))
			$(maximizeArray[i]).className = 'mn_maximized';
	}

	$('resizer1').style.display = 'none';
	$('mn_normal_size').style.display = 'block';
	
	MN.TP.resizable = true;
	MN.TP.OnResize(); //added to try and bypass low memory ie6 scariness (previos page state visible through black non-video portions of video pane)
};


//MN.TP.NormalSize--------------------------------------------------------------------------//
//Returns the page from fullscreen mode.													//
//------------------------------------------------------------------------------------------//
MN.TP.NormalSize = function(evt){
	log('normal size');
	
	  if(MN.nonIE)
			window.resizeTo(1065,790);
		else
			window.resizeTo(1065,825);

	var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'mn_show_title', 'copyright', 'shows', 'episodes_container', 'logo', 'back_home', 'ads', 'page_brand'];
	for(var i = 0; i < hiddenArray.length; i++){
		if($(hiddenArray[i]))
			$(hiddenArray[i]).className = 'mn_visible';
	}

	var minimizeArray = ['mn_public_page', 'mn_controller', 'mn_player_container', 'mn_container'];
	for(var i = 0; i < minimizeArray.length; i++){
		if($(minimizeArray[i]))
			$(minimizeArray[i]).className = '';
	}

	$('resizer1').style.display = 'block';
	$('mn_normal_size').style.display = 'none';

	MN.TP.maximized = false;
	MN.TP.resizable = false;
	MN.TP.OnResize();
	if (!MN.nonIE) {
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
		MN.TP.OnResize();
	}
};

MN.TP.Minimize = function(evt){
	window.moveTo(0,0);
	window.resizeTo(250, 250);
	
	$('mn_back').style.display = 'none';
	$('mn_rewind').style.display = 'none';
	$('mn_fast_forward').style.display = 'none';
	$('mn_forward').style.display = 'none';
	$('mn_go_live').style.display = 'none';
	
	$('mn_timeline').style.display = 'none';
	$('mn_volume_bar').style.display = 'none';
	
	MN.TP.SetToResizable(evt);

	MN.TP.maximized = false;
	MN.TP.OnResize();
};

//MN.TP.OnResize----------------------------------------------------------------------------//
//This is called whenever the window resizes.												//
//------------------------------------------------------------------------------------------//
MN.TP.OnResize = function(){
	if(!MN.TP.resizable){
		$('mn_player').style.height = '100%';
		$('mn_controller').style.width = '100%';
	}
	else{
  		//unfortunately, the HTML/CSS structure of the page requires us to 
		//calculate the height of the player when in fullscreen
		var border = $('mn_show_title').offsetHeight + $('mn_controller').offsetHeight;
		var windowHeight = MN.GetWindowSize()[1];
		
		$('mn_player').style.height = (windowHeight - border) + 'px';
		$('mn_controller').style.width = '100%';
	}
	var newTlWidth = $('mn_controller').offsetWidth - ($('mn_controller_right').offsetWidth);
	if(newTlWidth > 0)
		$('mn_timeline').style.width = newTlWidth+ 'px';
};

//------------------------------------------------------------------------------------------//
//-----------------------------------  Popout functions  -----------------------------------//
//------------------------------------------------------------------------------------------//
//These functions are used to create and maintain the "popout" (formerly known as			//
//"tearaway") player.																		//
//------------------------------------------------------------------------------------------//

//MN.TP.OpenPopout--------------------------------------------------------------------------//
//Opens the popout player.  The popout player is a seperate, simplified HTML page that		//
//contains only a maximized player.  This page is opened in a new window, and in addition to// 
//the links that exist between a window and its opener, a unique id is created and stored on// 
//both pages, so we know when the specific instance of the opening page is no longer		// 
//available to "pop in" to (either through the browser window closing, or the user 			//
//navigating to a different page).  If the original page still exists when the popout is 	//
//closed, the popout transfers video playback back.  The popout uses this script, so to 	//
//start playback at the appropriate place, we simply use a send-to-friend url.				//
//------------------------------------------------------------------------------------------//
MN.TP.OpenPopout = function(){
	log('open popout');
	var paused = MN.TP.qmp.Paused();
	MN.TP.qmp.Stop();
	var start = MN.TP.qmp.CurrentPosition() || -1;
	var url = MN.TP.qmp.CurrentQVT().PrimaryURL() || MN.TP.curChnl.url;
	var href = 'popout.html';
	
	//create a unique "id" for this page, and pass it to the popout, so that we can correctly
	//close the popout and restore this page (see if the user has remained on this page)
	var date = new Date();
	var id = date.getTime();
	$('mn_page_id').setAttribute('page_id', id);
	
	//open up popout.html with a send-to-friend url.  Also pass the current player volume and
	//the pause state so they are transfered.
	var params = {'popout' : true, 'openerID' : id, 'chnl': MN.TP.curChnl.name, 'url': url, 'start': start, 
					'stop': '-1', 'vol' : MN.TP.qmp.Volume()};
	if(paused)
		params.paused = true;
	if(MN.TP.params.debug)
		params.debug = true;
	var ref = MN.URL.SetParams(href, params);
   	window.open(ref, 'popout', 'width=650,height=664,status=no,scrollbars=yes,resizable=yes,location=no,toolbar=no');
	//window.open('http://www.byu.tv', 'popout', 'width=650,height=664,status=no,scrollbars=yes,resizable=yes,location=no,toolbar=no');
	
	//hide the player on the page
	$('mn_player_container').style.visibility = 'hidden';
};

//MN.TP.ClosePopout-------------------------------------------------------------------------//
//Called by the popout player when it closes, or the user clicks the "pop in" button.  As	//
//mentioned in MN.TP.OpenPopout above, it checks, via a shared unique id, to see if the		//
//original opening document exists; if it does, then it transfers the video back.			//
//------------------------------------------------------------------------------------------//
MN.TP.ClosePopout = function(){
	log('PAGE: closing popout');
	if(window.opener && !window.opener.closed){
		var openerDoc = window.opener.document;
		var popoutID = MN.TP.params.openerID;
		try{
			if(openerDoc.getElementById('mn_page_id')){
				var openerID = openerDoc.getElementById('mn_page_id').getAttribute('page_id');
				if(openerID == popoutID){
					log('PAGE: popout: opening page is still open, returning to it');
					var url = MN.TP.qmp.CurrentQVT().PrimaryURL();
					var pos = MN.TP.qmp.CurrentPosition();
					openerDoc.getElementById('mn_player_container').style.visibility = 'visible';
					
					//transfer status back to the opener
					if(MN.TP.qmp.Paused())
						window.opener.MN.Event.Observe(window.opener.MN.TP.qmp, 'PlayStateChanged', window.opener.MN.TP.TransferPaused);
					window.opener.MN.TP.qmp.Volume(MN.TP.qmp.Volume());
					window.opener.MN.TP.qmp.Play(url, pos);
				}
			}
		}
		//error commonly caused by trying to access openerDoc.getElementById when the user has changed
		//the page in the opener
		catch (e){}
	}
	
	window.close();
};


//MN.TP.InitWidgets-------------------------------------------------------------------------//
//Sets up the "widgets" on the page.  They are the volume bar, playlist, "traditional"		//
//timeline, and "show" timeline.  The timelines are very customized for this page (see		//
//timelines.js), and more information about the playlist can be found in playlist.js.  These//
//widgets use "time format" strings (as detailed in MN.ConvertToTimestamp) and HTML			//	
//"templates" to populate.  A template is an DOM element that is populated with information	//
//based on special classes assigned to the element and its children.						//
//------------------------------------------------------------------------------------------//
MN.TP.InitWidgets = function(){
	log('PAGE: Initializing widgets');
	
	//volume bar
	if($('mn_volume_bar')){
		MN.TP.qmp.volumebar = new MN.PlayerUI.VolumeBar(MN.TP.qmp, 'mn_volume_bar');
	}

	log('PAGE: volume bar');
	//Playlist
	if($('mn_playlist')){
		MN.TP.qmp.playlist = new MN.Playlist(MN.TP.qmp, 'mn_playlist', 'mn_content_dropdown', 'mn_pl_scroller');
		MN.TP.InitPlEvents();
	}
	
	log('PAGE: playlist');

	//"Traditional" timeline
	if($('mn_timeline')){
		var scrubber = $('mn_timeline_scrubber');
		MN.TP.qmp.timeline = new MN.PlayerUI.TraditionalTimeline(MN.TP.qmp, 'mn_timeline', {'posdur':'mn_duration'});
		//this is a workaround to get the position to appear with the timeline scrubber "thumb"
		//a change needs to be made to the SDK to eliminate the need for this workaround
		// var newDiv = document.createElement('div');
		//		newDiv.id = 'mn_scrubber_position';
		//		newDiv.className = 'mn_hidden';
		//		scrubber.appendChild(newDiv);
		//		MN.TP.qmp.timeline.scrubPosID = 'mn_scrubber_position';
		//		MN.Event.Observe($('mn_timeline'), 'mousedown', function(){$('mn_scrubber_position').className = 'mn_visible';});
		//		MN.Event.Observe(document, 'mouseup', function(){$('mn_scrubber_position').className = 'mn_hidden';});
	}

	MN.TP.AdjustWidgets(MN.TP.curChnl);
	log('PAGE: done initializing widgets');
};

MN.TP.AdjustWidgets = function(chnl){
	/*if($('mn_playlist'))
		MN.TP.qmp.playlist._boxTemplate = chnl.playlist.template;*/


	if($('mn_timeline')){
		if(chnl.timeline != 'hidden'){ //quaranteed to be non-null after MN.TP.InitChannels()
			MN.TP.qmp.timeline.posFormat = chnl.timeline.posFormat;
			MN.TP.qmp.timeline.durFormat = chnl.timeline.durFormat;
			MN.TP.qmp.timeline.scrubFormat = chnl.timeline.scrubFormat;
			if(chnl.timeline.type == 'traditional')
				MN.TP.TradTimelineMode(MN.TP.qmp.timeline);
			else
				MN.TP.ClipTimelineMode(MN.TP.qmp.timeline);
		}
	}
};


//MN.TP.InitPlEvents------------------------------------------------------------------------//
//Initializes the events that are associated with the playlist widget (see playlist.js for 	//
//more info).																				//
//------------------------------------------------------------------------------------------//
MN.TP.InitPlEvents = function(){
	MN.Event.Observe(MN.TP.qmp.playlist, 'PageChanged', MN.TP.PlPageChanged);
	MN.Event.Observe(MN.TP.qmp.playlist, 'Populated', MN.TP.PlPopulated);
	
	//hook up the playlist page up and playlist page down buttons
/*	var prevBtns = MN.GetElementsByClassName(document.body, 'mn_pl_prev_page', true);
	for(var i = 0; i < prevBtns.length; i++){
		MN.Event.Observe(prevBtns[i], 'click', MN.TP.qmp.playlist.PageUp);
		prevBtns[i].onclick = function(){return false;}
	}
	var nextBtns = MN.GetElementsByClassName(document.body, 'mn_pl_next_page', true);
	for(var i = 0; i < nextBtns.length; i++){
		MN.Event.Observe(nextBtns[i], 'click', MN.TP.qmp.playlist.PageDown);
		nextBtns[i].onclick = function(){return false;}
	}
*/
	if($('mn_pl_prev_page')){
		MN.Event.Observe($('mn_pl_prev_page'), 'click', MN.TP.qmp.playlist.PageUp);
		$('mn_pl_prev_page').onclick = function(){return false;};
	}
	if($('mn_pl_next_page')){
		MN.Event.Observe($('mn_pl_next_page'), 'click', MN.TP.qmp.playlist.PageDown);
		$('mn_pl_next_page').onclick = function(){return false;};
	}
};

//MN.TP.PlPageChanged-----------------------------------------------------------------------//
//This is called whenever the playlist changes pages (it will automatically page up/down	//	
//when a show changes to one that isn't visible in the playlist area).  The event fired		//
//passes along the page it changed to, and the total number of pages.						//
//------------------------------------------------------------------------------------------//
MN.TP.PlPageChanged = function(curPage, numOfPages){
	//write the playlist page information
/*	var curPageElems = MN.GetElementsByClassName(document.body, 'mn_pl_cur_page', true);
	for(var i = 0; i < curPageElems.length; i++){
		MN.SetInnerText(curPageElems[i], curPage); 
	}
	var numOfPageElems = MN.GetElementsByClassName(document.body, 'mn_pl_total_pages', true);
	for(var i = 0; i < numOfPageElems.length; i++){
		MN.SetInnerText(numOfPageElems[i], numOfPages);
	}
*/
	if($('mn_pl_cur_page'))
		MN.SetInnerText($('mn_pl_cur_page'),curPage);
	if($('mn_pl_total_pages'))
		MN.SetInnerText($('mn_pl_total_pages'),numOfPages);
};

//MN.TP.PlPopulated-------------------------------------------------------------------------//
//Called when the playlist populates with a new timeline.									//
//------------------------------------------------------------------------------------------//
MN.TP.PlPopulated = function(url, text){
	//Call the CustomPlPopulated function in custom.js
	if(window.CustomPlPopulated)
		CustomPlPopulated(url, text);
};

//MN.TP.InitButtons-------------------------------------------------------------------------//
//This cycles through the webpage and looks for elements with the special button classnames //
//and hooks them up to the appropriate functions.  The classes are: 						//
//"mn_back", "mn_rewind", "mn_pause", "mn_play", "mn_forward", "mn_fast_forward", 			//
//"mn_go_live", "mn_maximize", "mn_send_to_friend", "mn_pop_out", and "mn_pop_in".			//	  
//------------------------------------------------------------------------------------------//
MN.TP.InitButtons = function(){
	log('PAGE: Initializing buttons');
	var btns = {'back':MN.TP.Back, 'rewind':MN.TP.Rewind, 'pause':MN.TP.Pause, 'play':MN.TP.Play, 'forward':MN.TP.Forward,
				'fast_forward':MN.TP.FastForward, 'go_live':MN.TP.GoLive, 'fullscreen':MN.TP.Maximize, 'normal_size':MN.TP.NormalSize, 
		 		'pop_out':MN.TP.OpenPopout, 'pop_in':MN.TP.ClosePopout};
	var curBtns;
	for(var item in btns){
/*		curBtns = MN.GetElementsByClassName(document.body, 'mn_' + item, true);
		for(var i = 0; i < curBtns.length; i++){
			MN.Event.Observe(curBtns[i], 'click', btns[item]);
			curBtns[i].onclick = function(){return false};
		}
*/
		curBtn = $('mn_'+item);
		if(curBtn){
			MN.Event.Observe(curBtn, 'click', btns[item]);
			curBtn.onclick = function(){return false;};
		}
	}
	log('PAGE: Done initializing buttons');
};

//MN.TP.StartEPGLoad------------------------------------------------------------------------//
//Starts loading the EPG for a channel.														//
//------------------------------------------------------------------------------------------//
MN.TP.StartEPGLoad = function(chnl){
	log('PAGE: starting load of EPG for ', chnl.name);
	if(typeof chnl.EPG == 'object')
		chnl._epg.Load(chnl.EPG);
	else if(typeof chnl.EPG == 'number')
		chnl._epg.Load({'back': chnl.EPG - 1});
	else
		chnl._epg.Load();
	//MN.Event.Observe(chnl._epg, 'urlLoaded', MN.TP.EPGLoaded);
};

//MN.TP.InitPopups--------------------------------------------------------------------------//
//Parses the page and finds anchor tags with the classname "mn_popup" and modifies them so	//
//that they open up a popup window instead of following the standard anchor tag behavior.	//
//------------------------------------------------------------------------------------------//
MN.TP.InitPopups = function(){
	log('PAGE: Initializing popups from anchor tags');
	var anchors = document.body.getElementsByTagName('a');
	var curAnchor;
	for(var i = 0; i < anchors.length; i++){
		curAnchor = anchors[i];
		if(curAnchor.className == 'mn_popup'){
			var func = MN.MakeBound(curAnchor, MN.TP.PopupWin);
			MN.Event.Observe(curAnchor, 'click', func);
			curAnchor.onclick = function(){return false;};
		}
	}
	log('PAGE: Done with popups');
};

//MN.TP.OnPlayerLoaded----------------------------------------------------------------------//
//Called when the player has finished loading.  This finishes up the rest of the page		//
//initialization, hooking up the player buttons, widgets, and events, and then begins		//
//playing video and loading the EPG for the first channel.									//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayerLoaded = function(player){
	if(player){
		log('PAGE: Player loaded');
		if(window.CustomPlayerLoaded)
			CustomPlayerLoaded(player);
	
		MN.TP.qmp = player;
		
		log('PAGE: Initialize Widevine');
		function initWidevine()
		{
		    // var cmd = 'Widevine.init.WVConfig="so=https://wstfcps005.shibboleth.tv/widevine/cypherpc/v2/cgi-bin;lg=https://wstfcps005.shibboleth.tv/widevine/cypherpc/v2/cgi-bin;em=https://wstfcps005.shibboleth.tv/widevine/cypherpc/v2/cgi-bin;pt=wb;"';
			var cmd = 'Widevine.init.WVConfig="so=https://fcpstage.shibboleth.tv/widevine/cypherpc/cgi-bin;lg=https://fcpstage.shibboleth.tv/widevine/cypherpc/cgi-bin;em=https://fcpstage.shibboleth.tv/widevine/cypherpc/cgi-bin;pt=wb;"';
		    MN.TP.qmp.Get(cmd);
		}
		initWidevine();

		MN.TP.InitCustomStats();
		MN.TP.InitButtons();
		MN.TP.InitEvents();
		MN.TP.InitWidgets();
		
		//MN.TP.AdjustUI(MN.TP.curChnl);
		
		MN.PB.getCategoryClips(MN.TP.curChnl.categoryID);

	   /* //check for send-to-friend url
		if(MN.TP.params.start && MN.TP.params.show && MN.TP.params.stop){
			log('PAGE: have send-to-friend link,attempting to play: ', MN.TP.params.show, ' start: ', MN.TP.params.start, ' stop: ', MN.TP.params.stop);
			//VOD content
			if(MN.TP.params.show.search('byid') != -1 && MN.TP.params.ep){
				//check to make sure that the episode exists
				var qvt = MN.QVT.AcquireQVT(MN.TP.params.show);
				if(qvt.IsLoading())
					MN.Event.Observe(qvt, 'TimelineLoaded', MN.TP.VODLinkLoaded);
				else
					MN.TP.VODLinkLoaded(qvt);
			}
			else{ //Live link
				MN.TP.qmp.Play(MN.TP.params.show, MN.TP.params.start, MN.TP.params.stop);
			}
		}
		else if(MN.TP.curChnl.url){
			log('PAGE: playing: ', MN.TP.curChnl.url);
			MN.TP.qmp.Play(MN.TP.curChnl.url);
		}
		
		//cycle through and start loading the EPGs
		var curChnl;
		for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
			curChnl = MN.TP.CONFIG.channels[i];
			if(curChnl._epg){
				MN.TP.StartEPGLoad(curChnl);
				if(MN.TP.curChnl == curChnl && MN.TP.qmp.playlist)
					MN.TP.qmp.playlist.LoadContent(curChnl._epg);
			}
		}*/
		
	}
};

MN.TP.InitCustomStats = function(){
	if(typeof GEO == 'object')
		MN.TP.qmp.Set('CustomStats.aff', GEO.affiliate); //affiliate is set in affiliateGeo.js, blank if no affiliate detected (but player should not load in that situation)
	else
		setTimeout('MN.TP.InitCustomStats()',250); //should never happen, but better to account for it than encounter random error
};

MN.TP.VODLinkLoaded = function(qvt){
	MN.Event.StopObserving(qvt, 'TimelineLoaded', MN.TP.VODLinkLoaded);
	//search through the qvt to make sure that the clip still exists
	var show = -1;
	for(var i = 0; i < qvt.shows.length; i++){
		if(qvt.shows[i].id == MN.TP.params.ep){
			show = i;
			break;
		}
	}
	if(show >= 0){
		log('show is still valid, calculating start and end times from start: ', MN.TP.params.start, ' and stop: ', MN.TP.params.stop);
		MN.TP.qmp.playlist._boxTemplate = vodTemplate;
		//for VOD content, the start and stop positions are relative to the show, not timeline,
		//so calculate the position relative to the timeline
		var start = parseInt(MN.TP.params.start) + qvt.StartTime(show);
		var stop = MN.TP.params.stop == "-1" ? qvt.StopTime(show) : parseInt(MN.TP.params.stop) + qvt.StartTime(show);
		
		MN.TP.qmp.Play(qvt, start, stop);
	}
	else{
		//TODO: FIX!!!!!!!
		log('couldn\'t find link: ' + MN.TP.curChnl.url);
		// var newQVT = MN.QVT.AcquireQVT(MN.TP.curChnl.url);
		// 	MN.TP.Play(MN.TP.curChnl.url);
		// 	MN.TP.qmp.playlist._Populate(newQVT);
		MN.TP.qmp.Play(MN.TP.curChnl.url);
	}
};



//MN.TP.TransferPaused----------------------------------------------------------------------//
//Because the player cannot be paused before the content has loaded, we need to temporarily	//
//observe the player.PlayStateChanged event and pause when we have started playing.  This is//
//used when we are going to and from the popout player.										//
//------------------------------------------------------------------------------------------//
MN.TP.TransferPaused = function(oldPS, newPS){
	if(newPS == MN.QMP.PS.Playing){
		//we don't want to pause after every time the player starts playing, just this once
		MN.Event.StopObserving(MN.TP.qmp, 'PlayStateChanged', MN.TP.TransferPaused);
		MN.TP.Pause();
	}
};

//MN.TP.InitEvents--------------------------------------------------------------------------//
//Sets up the event observers for the player.												//
//------------------------------------------------------------------------------------------//
MN.TP.InitEvents = function(){
	log('PAGE: initializing events');
	
	MN.Event.Observe(MN.TP.qmp, 'PausedChanged', MN.TP.SwapPausePlay);
	MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.OnPlayStateChanged);
	MN.Event.Observe(MN.TP.qmp, 'TimelineLoaded', MN.TP.OnTimelineLoaded);
	MN.Event.Observe(MN.TP.qmp, 'ShowChanged', MN.TP.OnShowChanged);
	MN.Event.Observe(MN.TP.qmp, 'BitRateChanged', MN.TP.OnBitRateChanged);
	MN.Event.Observe(MN.TP.qmp, "Error", MN.TP.OnPlayerError);
	
	MN.Event.Observe(window, 'resize', MN.TP.OnResize);
	log('PAGE: done initializing events');
};

//MN.TP.OnBitRateChanged--------------------------------------------------------------------//
//Called when the player changes bit rates.  Updates the status message						//
//------------------------------------------------------------------------------------------//
MN.TP.OnBitRateChanged = function(newBR){
	if(MN.TP.scrubRate == 0)
		MN.TP.SetStatusMsg('Playing ' + newBR + 'kbps');
};

//MN.TP.OnPlayerError-----------------------------------------------------------------------//
//Called when the player throws an error.  Updates the status message for some errors.		//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayerError = function(errMsg){
	if(typeof errMsg == "object"){
		if(errMsg.domain && errMsg.domain.toLowerCase().indexOf('widevine') != -1){
			if($('mn_status')){
				MN.SetInnerText($('mn_status'),'SECURITY ERROR ' + errMsg.err + ': ' + errMsg.msg);
				logError('SECURITY ERROR ' + errMsg.err + ': ' + errMsg.msg);
			}
			if($('ad_status')){
				MN.SetInnerText($('ad_status'),'SECURITY ERROR ' + errMsg.err + ': ' + errMsg.msg);
				logError('SECURITY ERROR ' + errMsg.err + ': ' + errMsg.msg);
			}
		}
	}
};

//MN.TP.SwapPausePlay-----------------------------------------------------------------------//
//Swaps the pause and play buttons depending on the state of the player.  When the player is//
//paused or scrubbing, we display the play button to continue playback; otherwise, the pause//
//button is showing.  This function is called both by the PausedChanged event and a few 	//
//other functions.																			//
//------------------------------------------------------------------------------------------//
MN.TP.SwapPausePlay = function(paused){
/*	var pauseBtns = MN.GetElementsByClassName(document.body, 'mn_pause', true);
	for(var i = 0; i < pauseBtns.length; i++){
		pauseBtns[i].style.display = paused ? 'none' : 'block';
	}
	var playBtns = MN.GetElementsByClassName(document.body, 'mn_play', true);
	for(var i = 0; i < playBtns.length; i++){
		playBtns[i].style.display = paused ? 'block' : 'none';
	}
*/
	pauseBtn = $('mn_pause');
	if(pauseBtn)
		pauseBtn.style.display = paused ? 'none' : 'block';

	playBtn = $('mn_play');
	if(playBtn)
		playBtn.style.display = paused ? 'block' : 'none';
};

//MN.TP.OnPlayStateChanged------------------------------------------------------------------//
//Called when the player changes states.  Updates the status message with the state.		//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayStateChanged = function(oldPS, newPS){
	if(!MN.TP.qmp.scrubbing){
		if(newPS == MN.QMP.PS.Playing){
			if(!MN.TP.qmp.Paused())
				MN.TP.SetStatusMsg('Playing ' + MN.TP.qmp.CurrentBitRate() + 'kbps');
			else
				MN.TP.SetStatusMsg('Paused');
			//make sure the pause button is now showing because we are playing
			MN.TP.SwapPausePlay(false);
		}
		else
			MN.TP.SetStatusMsg(MN.QMP.PS[newPS]);
	}
	if(newPS == MN.QMP.PS.MediaEnded)
	{
		var qvt = MN.TP.qmp.CurrentQVT();
		if(qvt.Title(0) && qvt.Title(0) != "_ad")
		{
			MN.PB.forward();
		}
	}
	if($('mn_status'))
		$('mn_status').style.fontWeight = (newPS == MN.QMP.PS.Error) ? 'bold' : 'normal';
	if($('ad_status'))
		$('ad_status').style.fontWeight = (newPS == MN.QMP.PS.Error) ? 'bold' : 'normal';
};

//MN.TP.OnShowChanged-----------------------------------------------------------------------//
//Called when the player starts playing a different show.  Updates the show title area and	//
//adjusts the player height based on the aspect ratio of the content (unless the config		//
//specifies otherwise).																		//
//------------------------------------------------------------------------------------------//
MN.TP.OnShowChanged = function(showNum, title){
	var qvt = MN.TP.qmp.CurrentQVT();
	//sometimes the player throws an invalid show number.  This is a known, filed bug that
	//has not been fixed in the SDK yet
	if(showNum < 0 || showNum >= qvt.ShowCount()){
		showNum = qvt.ShowCount() - 1;
		title = qvt.Title(showNum);
	}
	if(typeof(title) == "string") title = title.replace('[WIDEVINE] ','').replace('[widevine] ','').replace('[Widevine] ','');
	if(window.CustomShowLoaded){
		CustomShowLoaded(showNum, title);
	}
		
	if($('mn_show_title_center'))
	{
		if(title == '_ad')
		{
			console.log(MN.ad.qvt);
			title = MN.ad.qvt.Metadata("name") || MN.ad.qvt.Metadata("title") || "UNDEFINED";
		}
		if(title != "UNDEFINED"){
			$('mn_show_title_center').innerHTML = '<span style="font-weight: bold; font-size: 12px;">Now Playing:&nbsp;&nbsp;</span>' + title;
		}
		else
			$('mn_show_title_center').innerHTML = '';
	}
};

//MN.TP.OnTimelineLoaded--------------------------------------------------------------------//
//Called when the player loads a new timeline/content to play.								//
//------------------------------------------------------------------------------------------//
MN.TP.OnTimelineLoaded = function(qvt){
};

/*MN.TP.AdjustUI = function(chnl){
	if(chnl.name == 'abcnews' || chnl.name == 'abc7' || chnl.name == 'abcVOD'){
		document.body.className = 'abc_page';
	}
	else if(chnl.name == 'foxVOD' || chnl.name == 'foxChicago'){
		document.body.className = 'fox_page';
	}
	else if(chnl.name == 'espn'){
		document.body.className = 'espn_page';
	}
	else if(chnl.name == 'cw'){
		document.body.className = 'cw_page';
	}
	else if(chnl.name == 'ksl'){
		document.body.className = 'nbc_page';
	}
	else if(chnl.name == 'cbs'){
		document.body.className = 'cbs_page';
	}
	else if(chnl.name == 'moveVOD'){
		document.body.className = 'move_page';
	}
};*/

MN.TP.GetPlaylistTemplate = function(){
	var template = MN.Widget.FindNonTextChild($('mn_playlist'));
	if(!template){
		template = document.createElement('table');
		var newRow = template.insertRow(-1);
		var newCell = newRow.insertCell(-1);
		newCell.className = 'mn_show_thumb';
		newCell = newRow.insertCell(-1);
		newCell.className = 'mn_show_startTime';
		newCell = newRow.insertCell(-1);
		newCell.className = 'mn_show_title';
	}

	return template;
};

MN.TP.SwitchToChannel = function(name){
	log('switch to channel');
	var curChnl;
	for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
		curChnl = MN.TP.CONFIG.channels[i];
		if(curChnl.name == name){
			log('switch to channel');
			//MN.TP.AdjustUI(curChnl);
			log('adjust ui');
			MN.TP.AdjustWidgets(curChnl);
			log('adjust widgets');
			MN.TP.curChnl = curChnl;
			log('cur chnl');
			MN.PB.getCategoryClips(MN.TP.curChnl.categoryID);
			/*if(MN.TP.qmp.playlist){
				log('playlist')
				if(curChnl._epg){
					log('epg')
					MN.TP.qmp.playlist.LoadContent(curChnl._epg);
				}
				else{
					log('url')
					MN.TP.qmp.playlist.LoadContent(MN.QVT.AcquireQVT(curChnl.url))
				}
			}
			MN.TP.qmp.Play(curChnl.url);
			 */
			
			return;
		}
	}
};


MN.TP.InitChnlTemplates = function(chnl, plTemplate){
	if(chnl.playlist && chnl.playlist != 'hidden' && chnl.playlist.template){
		var newDiv = document.createElement('div');
		newDiv.innerHTML = chnl.playlist.template;
		chnl.playlist.template = newDiv.firstChild;
		chnl.playlist.template.style.visibility = 'visible';
	}
	else{
		if(!chnl.playlist)
			chnl.playlist = {};
		chnl.playlist.template = plTemplate;
	}
};

MN.TP.GetTimeFormats = function(){
	var timeFormats = {};
	
	if($('mn_duration')){
		var formats = $('mn_duration').innerHTML.split('/');
		timeFormats.tlPos = formats[0] || 'HH:MM:SS';
		timeFormats.tlDur = formats[1] || 'HH:MM:SS';
		$('mn_duration').innerHTML = '';
	}
	if($('mn_timeline')){
		timeFormats.scrubPos = $('mn_timeline_scrubber').innerHTML || 'HH:MM:SS';
		$('mn_timeline_scrubber').innerHTML = '';
	}
	   
	return timeFormats;
};

MN.TP.InitChannels = function(){
	log('PAGE: Initializing channels');
	var curChnl = {};

	if($('mn_playlist'))
		var plTemplate = MN.TP.GetPlaylistTemplate();
	var timeFormats = MN.TP.GetTimeFormats();
	if(MN.TP.CONFIG){
		MN.TP.curChnl = MN.TP.CONFIG.channels[1];
		for(var i = 0; i < MN.TP.CONFIG.channels.length; i++){
			curChnl = MN.TP.CONFIG.channels[i];
			//create a channel name if there isn't one
			if(!curChnl.name)
				curChnl.name = 'chnl_' + i;
			
			log('PAGE: Initializing channel: ', curChnl.name);
			//calculate the url
			MN.TP.InitChnlURL(curChnl);
			//prepare the various templates and timeFormats
			MN.TP.InitChnlTemplates(curChnl, plTemplate);
			MN.TP.InitChnlTimeFormats(curChnl, timeFormats);
			//create an EPG
			if(curChnl.EPG){
				curChnl._epg = new MN.EPG(curChnl.url, curChnl.name, curChnl.contentType);
			}
		
			/*if(MN.TP.params.chnl == curChnl.name) //send-to-a-friend link, need to set MN.TP.curChnl to proper channel
				MN.TP.curChnl = curChnl;*/
			if(MN.TP.params.show == curChnl.name) //custom s2f link (show = chnl), need to set MN.TP.curChnl to proper channel
				MN.TP.curChnl = curChnl;
		
			log('PAGE: Done initializing channel: ', curChnl.name);
		}
		
	}
	else{
		curChnl.name = 'chnl_0';
		
		MN.TP.InitChnlTemplates(curChnl, plTemplate);
		MN.TP.InitChnlTimeFormats(curChnl, timeFormats);
		MN.TP.curChnl = curChnl;
	}
	
	log('PAGE: Done initializing channels');
};

MN.TP.InitChnlTimeFormats = function(chnl, timeFormats){
	if(!chnl.timeline)
		chnl.timeline = {};
		
	if(chnl.timeline != 'hidden'){
		chnl.timeline.posFormat = chnl.timeline.posFormat || timeFormats.tlPos;
		chnl.timeline.durFormat = chnl.timeline.durFormat || timeFormats.tlDur;
		chnl.timeline.scrubFormat = chnl.timeline.scrubFormat || timeFormats.scrubPos;
	}
};

MN.TP.InitChnlURL = function(chnl){
	if(chnl.url){
		//calculate absolute url from relative url if we have one (the player requires a full url to play)
		if(chnl.url.search('http://') == -1){
			chnl.url = MN.URL.Join(window.location.href, chnl.url);
		}
	}
	else if(chnl.baseURL)
		chnl.url = MN.TP.CreateQVTURL(chnl.baseURL, chnl.timezone);
};

MN.TP.CreateQVTURL = function(baseURL, timezone){
	var msInHour = 3600000;
	var url;
	var today_date = new Date();
	var todayInMS = today_date.getTime();
	todayInMS += (timezone || 0) * msInHour;
	today_date.setTime(todayInMS);

	fDate = MN.TP.FormattedDateObject(today_date);
	var year = String(fDate.year).substr(2,2);

	url = (baseURL || '') + year + fDate.month + fDate.day + '.qvt';
	log('PAGE: Created today\'s url: ', url);
	return url;
};

MN.TP.FormattedDateObject = function(date){
	//For day of week 1=Sunday, For month 1=January
	return {year:date.getUTCFullYear(), month:MN.PadDigits(date.getUTCMonth()+1, 2),
			day:MN.PadDigits(date.getUTCDate(), 2), hour:MN.PadDigits(date.getUTCHours(), 2),
			minute:MN.PadDigits(date.getUTCMinutes(), 2), second:MN.PadDigits(date.getUTCSeconds(), 2),
			dow:date.getUTCDay()+1};
};

MN.TP.WindowLoaded = function(){
	// if(MN.nonIE)
	// 	window.resizeTo(1065,790);
	// else
	// 	window.resizeTo(1065,825);
	
	log('PAGE: Add widevine module');
	MN.QMPInstall.AddModule("widevine2");
	// log('PAGE: Widevine added, call MN.QVT.CreatePlayer');

	MN.TP.params = MN.GetPageParams();
	if(MN.TP.params.debug)
		MN.Log.ShowPane(500);
		
	MN.TP.InitPopups();
	MN.TP.CustomizeShowTl();
	MN.TP.InitChannels();
	MN.TP.OnResize();
	
	if(window.CustomWindowLoaded){
		CustomWindowLoaded();	
	}

	log('WINDOW: Loaded, start player creation');
	MN.QVT.CreatePlayer('mn_player', MN.TP.OnPlayerLoaded, '100%', '100%');
};

MN.Event.Observe(window, 'load', MN.TP.WindowLoaded);
// CSS Browser Selector   v0.2.5
// Documentation:		 http://rafael.adm.br/css_browser_selector
// License:			   http://creativecommons.org/licenses/by/2.5/
// Author:				Rafael Lima (http://rafael.adm.br)
// Contributors:		  http://rafael.adm.br/css_browser_selector#contributors
// Modified:               Ben Loveridge 11/26/07 -- target Safari 3
var css_browser_selector = function() {
	var
		ua=navigator.userAgent.toLowerCase(),
		is=function(t){ return ua.indexOf(t) != -1; },
		h=document.getElementsByTagName('html')[0],
		b=(!(/opera|webtv/i.test(ua))&&/msie (\d)/.test(ua))?('ie ie'+RegExp.$1):is('gecko/')? 'gecko':is('opera/9')?'opera opera9':/opera (\d)/.test(ua)?'opera opera'+RegExp.$1:is('konqueror')?'konqueror':is('applewebkit/')?'webkit safari':is('mozilla/')?'gecko':'',
		b=(b.indexOf('safari') != -1 && typeof window.devicePixelRatio != 'undefined')? b+' safari3':b,
		os=(is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';
	var c=b+os+' js';
	h.className += h.className?' '+c:c;
}();