//Move Networks Template Player
//Copyright 2007 Move Networks, Inc.
// @revision 20071217-0955
//  -commented out some lines in PopulateTemplate() -- caused funky timezone display issues

/********************************************************************************************/
//                                                                                          //
//-----------------------------------------  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();
                
				//commented out these 3 lines -- qvt.PosToDatetime() sometimes returns funky values -- where does it get timezone info it bases off of?
                // if(posToDatetime)
                //     ts = MN.ConvertToTimestamp(posToDatetime(showObj[curProp]), timeFormat);
                // else
                    ts = MN.ConvertToTimestamp(showObj[curProp], timeFormat);

				//added these so conference will show duration instead of start time (start time would show wrong anyway)
				if(showObj.durAsTimestamp)
					ts = MN.ConvertToTimestamp(showObj.duration,'H?:MM')
                 
                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){
	if(!element)
		return;
	
    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.firstTl = true;

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);
    }
}

//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;
    window.open(this.getAttribute('href'),'popup','width=' + width + ',height=' + height + ',status=yes,scrollbars=yes,resizable=yes,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);
    }
}

//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(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(){
    log('pause');
    MN.TP.qmp.Paused(true);
    MN.TP.SetStatusMsg('Paused');
}

//MN.TP.Play--------------------------------------------------------------------------------//
//Called after a pause or while scrubbing to start normal playback							//
//------------------------------------------------------------------------------------------//
MN.TP.Play = function(){
    log('play');
	if(MN.TP.qmp.scrubbing)
	{
		MN.TP.qmp.StopScrubbing();
		MN.TP.scrubRate = 0;
	}
	MN.TP.qmp.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(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('Fast 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){
                    MN.TP.qmp.Play(nextURL, 0);
                    return;
                }
            }
        }   
    }
    else{
        MN.TP.qmp.CurrentShow(showNum+ 1);
    }
    
}


//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(true);
        
    }
    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');
    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');
			//show the right day in the date dropdown
			if($('mn_content_dropdown')){
				$('mn_content_dropdown').selectedIndex = 0;
			}
			//play todaysQVT
		    MN.TP.qmp.Play(todaysQVT, todaysQVT.Duration());
			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()
{
	log('maximize');
	var fs = MN.TP.qmp.fullScreenSupported();
	if(typeof fs == 'string' && fs == 1 || typeof fs == 'boolean' && fs || typeof fs == 'number' && fs)
	{
		/*
		if(!MN.TP.fullScreenWarned)
		{
			alert('To exit full screen mode, press the ESC key on your keyboard or click the video with your right mouse button.');
			MN.TP.fullScreenWarned = true;
		}
		*/
		
		MN.TP.qmp.fullScreen(true, "zoom", { speed: 250 });


		// show small controls
		MN.TP.qmp.Set("Control.Frame", "http://byub.org/new/images/player-overlay/overlay-bg.png, 420");
		MN.TP.qmp.Set("UIInactivityDelay", "1500");
		MN.TP.qmp.Set("Control.Pause", "http://byub.org/new/images/player-overlay/pause.png, 20,16");
		MN.TP.qmp.Set("Control.Play", "http://byub.org/new/images/player-overlay/play.png, 20,16");
		MN.TP.qmp.Set("Control.ExitFullScreen", "http://byub.org/new/images/player-overlay/exit-fullscreen.png, 69,14");
		MN.TP.qmp.Set("Control.Open", "1");
		
		/*
		// show small controls
		MN.TP.qmp.Set("Control.Frame", "http://byub.org/new/byuweekly/images/overlay/overlay-bg.png, 100");
		MN.TP.qmp.Set("UIInactivityDelay", "1500");
		MN.TP.qmp.Set("Control.Rewind", "http://byub.org/new/byuweekly/images/overlay/reverse.png, 22,24");
		MN.TP.qmp.Set("Control.Pause", "http://byub.org/new/byuweekly/images/player/pauseB_hover.png, 50,18");
		MN.TP.qmp.Set("Control.Play", "http://byub.org/new/byuweekly/images/player/playB_hover.png, 50,18");
		MN.TP.qmp.Set("Control.FastForward", "http://byub.org/new/byuweekly/images/overlay/forward.png, 87,24");
		MN.TP.qmp.Set("Control.FullScreen", "http://byub.org/new/byuweekly/images/overlay/fullscreen.png, 130,24");
		MN.TP.qmp.Set("Control.ExitFullScreen", "http://byub.org/new/byuweekly/images/overlay/exit-fullscreen.png, 130,24");
		MN.TP.qmp.Set("Control.Open", "1");
		*/
	}
	else
	{
		//swap functionality of the mn_maximize buttons
		var btns = MN.GetElementsByClassName(document.body, 'mn_maximize', true);
		for(var i = 0; i < btns.length; i++)
		{
			log(btns[i].className);
			MN.Event.StopObserving(btns[i], 'click', MN.TP.Maximize);
			MN.Event.Observe(btns[i], 'click', MN.TP.Minimize);
			btns[i].className = 'mn_minimize';
			btns[i].title = 'Minimize';
		}



		var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'copyright'];
		for(var i = 0; i < hiddenArray.length; i++)
		{
			if($(hiddenArray[i]))
				$(hiddenArray[i]).style.display = 'none';
		}

		var maximizeArray = ['mn_container', 'mn_player_container', 'mn_public_page', 'mn_controller'];
		for(var i = 0; i < maximizeArray.length; i++)
		{
			if($(maximizeArray[i]))
				$(maximizeArray[i]).className = 'mn_maximized';
		}

		MN.TP.maximized = true;
		MN.TP.OnResize();
		MN.Event.Observe(window, 'resize', MN.TP.OnResize);
	}
}

//MN.TP.Minimize----------------------------------------------------------------------------//
//Returns the page from fullscreen mode.													//
//------------------------------------------------------------------------------------------//
MN.TP.Minimize = function()
{
	log('minimize');
	MN.Event.StopObserving(window, 'resize', MN.TP.OnResize);

	var btns = MN.GetElementsByClassName(document.body, 'mn_minimize', true);
	for(var i = 0; i < btns.length; i++)
	{
		MN.Event.StopObserving(btns[i], 'click', MN.TP.Minimize);
		MN.Event.Observe(btns[i], 'click', MN.TP.Maximize);
		btns[i].className = 'mn_maximize';
		btns[i].title = 'Maximize';
	}

	var hiddenArray = ['mn_playlist_container', 'mn_categories', 'mn_link', 'mn_header', 'copyright'];
	for(var i = 0; i < hiddenArray.length; i++)
	{
		if($(hiddenArray[i]))
			$(hiddenArray[i]).style.display = 'block';
	}

	var minimizeArray = ['mn_container', 'mn_player_container', 'mn_public_page', 'mn_controller'];
	for(var i = 0; i < minimizeArray.length; i++)
	{
		if($(minimizeArray[i]))
			$(minimizeArray[i]).className = '';
	}

	$('mn_timeline').style.width = '406px';
	MN.TP.qmp.Height(390);
	$('mn_player').style.height = '392px';

	MN.TP.maximized = false;


	// hide controls
	MN.TP.qmp.Set("Control.Frame", "http://byub.org/new/byuweekly/images/overlay/overlay-bg.png, -200");
	//MN.TP.qmp.Set('Control.Close', '1');
	//MN.TP.qmp.Set("Control.Open", "0");
}

//MN.TP.OnResize----------------------------------------------------------------------------//
//This is called whenever the window resizes.												//
//------------------------------------------------------------------------------------------//
MN.TP.OnResize = function()
{
	log('resize');
	
    //because of the player design and HTML structure for the page, we need to calculate the 
	//timeline width every time the window resizes.
    var newTlWidth = $('mn_controller').offsetWidth - $('mn_controller_right').offsetWidth;
    if(newTlWidth > 0)
        $('mn_timeline').style.width = newTlWidth+ 'px';


  	//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.TP.qmp.Height(windowHeight - border);
	$('mn_player').style.height = (windowHeight - border) + '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(){
    //volume bar
    if($('mn_volume_bar')){
        MN.TP.qmp.volumebar = new MN.PlayerUI.VolumeBar(MN.TP.qmp, 'mn_volume_bar');
    }

    //Playlist
    if($('mn_playlist')){
        MN.TP.qmp.playlist = new MN.Playlist(MN.TP.qmp, 'mn_playlist', 'mn_content_dropdown', 'mn_playlist_scroller');
        MN.TP.qmp.playlist._boxTemplate = MN.TP.curChnl.playlist.template;
        MN.TP.InitPlEvents();
    }
    
    //Show timeline
    if($('mn_show_timeline')){
        MN.TP.qmp.showTimeline = new MN.PlayerUI.Timeline(MN.TP.qmp, 'mn_shows', 'mn_show_timeline_scrubtrack', {'pos' : 'mn_current_show_time'});
        if(MN.TP.curChnl.ShowTimeline != 'hidden'){ //MN.TP.curChnl.showTimeline is quaranteed to be non-null after MN.TP.InitChannels();
            MN.TP.qmp.showTimeline.template = MN.TP.curChnl.showTimeline.template;
            MN.TP.qmp.showTimeline.posFormat = MN.TP.curChnl.showTimeline.posFormat;
            MN.TP.qmp.showTimeline.durFormat = MN.TP.curChnl.showTimeline.durFormat;
        }
    }
        
    //"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'});
        if(MN.TP.curChnl.timeline != 'hidden'){ //quaranteed to be non-null after MN.TP.InitChannels()
            MN.TP.qmp.timeline.posFormat = MN.TP.curChnl.timeline.posFormat;
            MN.TP.qmp.timeline.durFormat = MN.TP.curChnl.timeline.durFormat;
            MN.TP.qmp.timeline.scrubFormat = MN.TP.curChnl.timeline.scrubFormat;
            if(MN.TP.qmp.timeline.type == 'traditional')
                MN.TP.TradTimelineMode(MN.TP.qmp.timeline);
            else
                MN.TP.ClipTimelineMode(MN.TP.qmp.timeline);
        }
        //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.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;}
    }
}

//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);
    }
}

//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, 'maximize':MN.TP.Maximize, 
                'send_to_friend':MN.TP.OpenSendBox, '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};
        }
    }

    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.									//
//------------------------------------------------------------------------------------------//
var preroll = false;
var player_loaded_callback_exists;
MN.TP.OnPlayerLoaded = function(player) {
    if (player) {
        log('PAGE: Player loaded');
        if (window.CustomPlayerLoaded)
            CustomPlayerLoaded(player);

        MN.TP.qmp = player;


        MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.OnPlayStateChanged);
        //check for send-to-friend url
        if (MN.TP.params.start && MN.TP.params.ep && MN.TP.params.stop) {
            MN.TP.InitButtons();
            MN.TP.InitEvents();
            MN.TP.InitWidgets();
            log('PAGE: have send-to-friend link, playing: ', MN.TP.params.ep, ' start: ', MN.TP.params.start, ' stop: ', MN.TP.params.stop);
            MN.TP.qmp.Play(MN.TP.params.ep, Number(MN.TP.params.start), Number(MN.TP.params.stop));
        }
        else if (MN.TP.curChnl.url) {
            if (MN.TP.curChnl.preroll) {
                preroll = true;
                log('PAGE: playing: ', MN.TP.curChnl.preroll);
                MN.TP.qmp.Play(MN.TP.curChnl.preroll);
                MN.Event.Observe(MN.TP.qmp, 'Error', MN.TP.PrerollDone);
                MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.PrerollPS);
            }
            else
                MN.TP.PrerollDone();
        }

        //the popout uses gets passed the volume and pause state from the
        //opener
        if (MN.TP.params.paused)
        //a call to pause the player before it has loaded the content doesn't work,
        //so temporarily observe the player for the "Playing" playstate, then pause.
            MN.Event.Observe(MN.TP.qmp, 'PlayStateChanged', MN.TP.TransferPaused);
        if (MN.TP.params.vol)
            MN.TP.qmp.Volume(MN.TP.params.vol);

        if (MN.TP.curChnl._epg) {
            MN.TP.StartEPGLoad(MN.TP.curChnl);
            if (MN.TP.qmp.playlist && !MN.TP.curChnl.preroll)
                MN.TP.qmp.playlist.LoadContent(MN.TP.curChnl._epg);
        }

        if (player_loaded_callback_exists) player_loaded_callback();
    }
}

MN.TP.PrerollPS = function(oldPS, newPS){	
	if(newPS == MN.QMP.PS.MediaEnded){
		MN.TP.PrerollDone();
	}
}

MN.TP.PrerollDone = function(){
	MN.Event.StopObserving(MN.TP.qmp, 'Error', MN.TP.PrerollDone);
	MN.Event.StopObserving(MN.TP.qmp, 'PlayStateChanged', MN.TP.PrerollPS);
	
	MN.TP.InitButtons();
    MN.TP.InitEvents();
    MN.TP.InitWidgets();

	preroll = false;

	MN.TP.qmp.Play(MN.TP.curChnl.url);
	if(MN.TP.qmp.playlist)
		MN.TP.qmp.playlist.LoadContent(MN.TP.curChnl._epg);
}

//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(){
    MN.Event.Observe(MN.TP.qmp, 'PausedChanged', MN.TP.SwapPausePlay);
    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.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.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';
    }
}

//MN.TP.OnPlayStateChanged------------------------------------------------------------------//
//Called when the player changes states.  Updates the status message with the state.		//
//------------------------------------------------------------------------------------------//
MN.TP.OnPlayStateChanged = function(oldPS, newPS){
    if(newPS == MN.QMP.PS.Playing){
        MN.TP.SetStatusMsg('Playing ' + MN.TP.qmp.CurrentBitRate() + 'kbps');       
        //make sure the pause button is now showing because we are playing
	    MN.TP.SwapPausePlay(false);
    }
    else
        MN.TP.SetStatusMsg(MN.QMP.PS[newPS]);
}

//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);
    }
        
    MN.SetInnerText($('mn_show_title_center'), title);
}

//MN.TP.OnTimelineLoaded--------------------------------------------------------------------//
//Called when the player loads a new timeline/content to play.								//
//------------------------------------------------------------------------------------------//
MN.TP.OnTimelineLoaded = function(qvt){
	if(firstTl){
		//allow popout
	}

}

//MN.TP.GetShowTLTemplate-------------------------------------------------------------------//
//Gets the default template to use for the show timeline from the HTML.						//
//------------------------------------------------------------------------------------------//
MN.TP.GetShowTlTemplate = function(){
    var template = MN.Widget.FindNonTextChild($('mn_shows'));
    if(!template){
        template = document.createElement('div');
        template.className = 'mn_show';
        var newDiv = document.createElement('div');
        newDiv.className = 'mn_title';
        template.appendChild(newDiv);
        newDiv = document.createElement('div');
        newDiv.className = 'mn_startTime';
        template.appendChild(newDiv);
    }
    
    return template;
}

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.InitChnlTemplates = function(chnl, showTlTemplate, plTemplate){
    if(chnl.showTimeline && chnl.showTimeline != 'hidden' && chnl.showTimeline.template){
        //the template comes as html code, need to convert it to a DOM object
        var newDiv = document.createElement('div');
        newDiv.innerHTML = chnl.showTimeline.template;
        chnl.showTimeline.template = newDiv.firstChild;
    }
    else{
        if(!chnl.showTimeline)
            chnl.showTimeline = {};
        if(chnl.showTimeline != 'hidden')
            chnl.showTimeline.template = showTlTemplate;
    }
    
    if(chnl.playlist && chnl.playlist != 'hidden' && chnl.playlist.template){
        var newDiv = document.createElement('div');
        newDiv.innerHTML = chnl.playlist.template;
        chnl.playlist.template = newDiv.firstChild;
    }
    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 = '';
    }
    if($('mn_current_show_time')){
        formats = $('mn_current_show_time').innerHTML.split('/');
        timeFormats.showTlPos = formats[0] || 'HH:MM:SS';
        timeFormats.showTlDur = formats[1] || 'HH:MM:SS';
        $('mn_current_show_time').innerHTML = '';
    }
	if($('mn_start')){
		timeFormats.send = $('mn_start').innerHTML || 'HH:MM:SS';
	}
       
    return timeFormats;
}

MN.TP.InitChannels = function(){
    log('PAGE: Initializing channels');
    var curChnl = {};
    
    if($('mn_show_timeline'))
        var showTlTemplate = MN.TP.GetShowTlTemplate();
    if($('mn_playlist'))
        var plTemplate = MN.TP.GetPlaylistTemplate();
    var timeFormats = MN.TP.GetTimeFormats();
    if(MN.TP.CONFIG){
        MN.TP.curChnl = MN.TP.CONFIG.channels[0];
        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, showTlTemplate, 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;
        
            log('PAGE: Done initializing channel: ', curChnl.name);
        }
    }
    else{
        curChnl.name = 'chnl_0';
        
        MN.TP.InitChnlTemplates(curChnl, showTlTemplate, 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;
    }

    //chnl.showTimeline is guaranteed to exist after InitChnlTemplates
    if(chnl.showTimeline != 'hidden'){
        chnl.showTimeline.posFormat = chnl.showTimeline.posFormat || timeFormats.showTlPos;
        chnl.showTimeline.durFormat = chnl.showTimeline.durFormat || timeFormats.showTlDur;
    }

	if(!chnl.send2Friend)
		chnl.send2Friend = {};
		
	if(chnl.send2Friend != 'hidden'){
		chnl.send2Friend.timeFormat = chnl.send2Friend.timeFormat || timeFormats.send;
	}
}

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(){
    MN.TP.params = MN.GetPageParams();
    if(MN.TP.params.debug)
        MN.Log.ShowPane(500);
        
    MN.TP.CustomizeShowTl();
    MN.TP.InitChannels();
	
	$('mn_player').style.height = '100%';
		
    if(MN.TP.params.popout){
        MN.TP.Maximize();
        MN.Event.Observe(window, 'unload', MN.TP.ClosePopout);
    }        

	if(window.CustomWindowLoaded){
        CustomWindowLoaded();    
    }

    log('PAGE: all good, call MN.QVT.CreatePlayer');
	MN.QVT.CreatePlayer('mn_player', MN.TP.OnPlayerLoaded, '100%', 360);
}

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
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':'',
		os=(is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';
	var c=b+os+' js';
	h.className += h.className?' '+c:c;
}();
