/**
 * Class: MN.Player
 * Main controller class for interaction with the player and player controls
 */
MN.Player = MN.Class(MN.EventSource);
_player = MN.Player.prototype;

/**
  * Function: initialize
  * Initializes the Player object and calls into the JSDK to create the Move Player plugin on the page
  *
  * Parameter:
  * playerId:String - div id for where the plugin should be placed
  * maxbitrate:Number - the maximum bit rate that the player will play content at, set to 0 to use autostreamselect
  * fullmaxbitrate:Number - the maximum bit rate that the player will play content at while in fullscreen
  */
_player.initialize = function(playerId, maxbitrate, fullmaxbitrate){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.p = null; // JSDK player wrapper
	this.posIntvl = 0; // Position interval for tracking the position update
	this.currentUrl = ""; // Url of the currently playing clip
	this.maxbitrate = maxbitrate;
	this.fullmaxbitrate = fullmaxbitrate;
	
	// Setup the Move Player plugin
	MN.QVT.CreatePlayer(playerId, this.onPlayerLoaded, "100%", "100%");
}

/**
  * Function: onPlayerLoaded
  * Callback function for when the Move Player plugin finishes loading
  *
  * Parameter:
  * p:Object - player wrapper object from the SDK
  */
_player.onPlayerLoaded = function(p){
	// Assign the player wrapper to a class property
	this.p = p;
	
	// Set the player to cap the bit rate of the player based on player stage size
	if(this.maxbitrate == 0){
		this.p.autoSelectByStageSize(true);
	}else{
		this.p.Set("MaxBitrate",this.maxbitrate);
	}
	this.p.Set("Commit","");
	
	// Set mouse pointer to disappear after a certain amount of time of inactivity
	this.p.setUIInactivityDelay(5000);
	
	// Set substage styles if they exist
	// The substage is the window that is the actual video inside of the "stage" or plugin space.
	// It can be positioned anywhere in the stage and resized.
	// Note: It is always underneath all other overlays and not affected by z-index
	this.substage = this.p.OverlayManager.getOverlay("substage");
	if(typeof(MN.PlayerStyles.substage) != "undefined"){
		this.substage.update(MN.PlayerStyles.substage);
	}
	
	if(this.p.overlaysSupported()){
		// Create controls object to allow the user to control the playback of the plugin
		this.controls = new MN.Player.Controls(this);
		this.controls.createControls();
	} else {
		var html='<p style="text-align: center; color: white; margin-left: auto; margin-right: auto; margin-top: 100px; width: 250px;">Your computer\'s video card is unable to render the video and controls.</p>';
		MN.$("mn_player").innerHTML=html;
	}
	
	MN.Event.Observe(this.p, "UIStateChanged", this.onUIStateChanged);
	
	this.FireEvent("PlayerLoaded");
}

/**
  * Function: play
  * Plays a QMX/QVT file in the player
  *
  * Parameter:
  * url:String - QMX/QVT url to play
  * start:Number - start time
  * stop:Number - stop time
  */
_player.play = function(url, start, stop){
	// Store current url
	this.currentUrl = url;
	
	// Allow components to know of the new clip
	this.FireEvent("NewClip", url);
	
	MN.Event.Observe(this.p, "TimelineLoaded", this.OnTimelineLoaded);
	
	clearInterval(this.posIntvl);
	this.p.Stop();
	this.p.qvt = null;
	this.p.oldqvt = false;
	// Call into the JSDK to player
	this.p.Play(url, start, stop);
}

/**
  * Function: updatePosition
  * Polls the current position and duration of the player and broadcasts that information
  */
_player.updatePosition = function(){
	if(this.p.qvt && !cwPlayer.playingAd){
		this.FireEvent("CurrentPositionChanged", this.CurrentPosition(), this.Duration());
	}
}

/**
  * Function: onTimelineLoaded
  * 
  */
_player.OnTimelineLoaded = function(qvt)
{
	MN.Event.StopObserving(this.p, "TimelineLoaded", this.OnTimelineLoaded);
	if (cwPlayer.playingAd) return;
	this.controls.container.update({"visibility":"visible"});
	
	// Poll the position and duration
	this.posIntvl = setInterval(this.updatePosition,500);
}

/**
  * Function: Duration
  * Override the default p.Duration to account for live and show based timeline
  */
_player.Duration = function()
{
	// Check that player exists
	if (this.p)
	{
		return this.p.Duration();
	}
}

/**
  * Function: CurrentPosition
  * Override the default p.CurrentPosition to account for live and show based timeline
  */
_player.CurrentPosition = function(value)
{
	// Check that player exists
	if (this.p)
	{
		// Setting
		if (!isNaN(value))
		{
			this.p.CurrentPosition(value);
		}
		else
		{	// Getting
			return this.p.CurrentPosition();
		}
	}
}

/**
  * Function: onUIStateChanged
  * 
  */
_player.onUIStateChanged = function(state)
{
	if (state == "2")
	{	
		if (this.fullmaxbitrate != undefined && this.maxbitrate != 0)
			this.p.Set("MaxBitrate",this.fullmaxbitrate);
	}
	else if (state == "3")
	{
		if (this.maxbitrate != undefined && this.maxbitrate != 0)
			this.p.Set("MaxBitrate",this.maxbitrate);
	}
}
delete _player;

/**
 * Class: MN.Player.Controls
 * Player controls class
 */
MN.Player.Controls = MN.Class(MN.EventSource);
_controls = MN.Player.Controls.prototype;

/**
  * Function: initialize
  * Initializes the Controls object and sets up the controls container
  *
  * Parameter:
  * player:Object - Move Player controller
  */
_controls.initialize = function(player){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	
	// Create Controls Container Overlay - this will contain all of the control overlay objects
	this.container = this.player.p.OverlayManager.createOverlay("", MN.PlayerStyles.controls);
	this.player.p.OverlayManager.addOverlay("controls", this.container);
	
	this.altContainer = this.player.p.OverlayManager.createOverlay("", MN.PlayerStyles.altContainer);
	this.player.p.OverlayManager.addOverlay("altContainer", this.altContainer);
}

/**
  * Function: createControls
  * Creates all available controls as determined by the Styles object
  */
_controls.createControls = function(){
	// Controls backgrounds
	this.controlsDark = this.player.p.OverlayManager.createOverlay("Image", MN.PlayerStyles.controlsDarkBack);
	this.container.addOverlay("controlsDark", this.controlsDark);
	
	// PlayPause Object
	this.playPause = new MN.Player.PlayPause(this.player, this.container);
	
	// Time Display
	this.timedisplay = new MN.Player.TimeDisplay(this.player, this.container);
	
	// Title Display
	this.titledisplay = new MN.Player.TitleDisplay(this.player, this.container);

	// FullScreen Object
	if(this.player.p.fullScreenSupported()){
		this.fullScreen = new MN.Player.FullScreen(this.player, this.container);
	}
	
	// Timeline Object
	this.timeline = new MN.Player.TimelineSlider(this.player, this.container, this.altContainer);
	
	// Thumbnail preview
	this.preview = new MN.Player.Preview(this.player, this.timeline);
	
	// Volume Object
	this.volume = new MN.Player.VolumeSlider(this.player, this.container);
}

_controls.hide = function(hide)
{
	this.container.update({"visibility":((hide)?"hidden":"visible")});
	if (!hide)
	{
		this.altContainer.update({"alpha":0});
	}
	this.altContainer.update({"visibility":((hide)?"hidden":"visible")});
}
delete _controls;

/**
 * Class: MN.Player.Button
 * General button component for Move Overlays
 */
MN.Player.Button = MN.Class(MN.EventSource);
_button= MN.Player.Button.prototype;

/**
  * Function: initialize
  * Initializes a basic button component with styles for four different states; default, hover, down and disabled.
  * The button has a main container that is used for positioning and sizing. 
  * It also provides a mask for cropping if a sprite is used for the states
  * If a null value is passed for any state, that state is not used for the button
  * The default state is required to have a style
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  * name:String - name of the new button overlay
  * clickCallback:Function - function to call when the button is clicked
  * buttonConfig:Array - config for the button
  * defaultConfig:Array - config for normal or default state of the image, required
  * hoverConfig:Array - config for hover state of the image, null or "undefined" = state is not used
  * downConfig:Array - config for down state of the image, null or "undefined" = state is not used
  * disabledConfig:Array - config for disabled state of the image, null or "undefined" = state is not used
  */
_button.initialize = function(player, parentContainer, name, clickCallback, buttonConfig, defaultConfig, hoverConfig, downConfig, disabledConfig){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.hoverState = false; // Flag if the state is active or not
	this.downState = false; // Flag if the state is active or not
	this.disabledState = false; // Flag if the state is active or not
	this.clickFunc = clickCallback; // Store the click callback
	
	// Set style configurations for the states
	this.defaultStyle = defaultConfig;
	this.hoverStyle = hoverConfig;
	if(typeof(this.hoverStyle) == "undefined"){
		this.hoverStyle = null;	
	}
	this.downStyle = downConfig;
	if(typeof(this.downStyle) == "undefined"){
		this.downStyle = null;	
	}
	this.disabledStyle = disabledConfig;
	if(typeof(this.disabledStyle) == "undefined"){
		this.disabledStyle = null;	
	}
	
	// Overlays
	this.button = this.player.p.OverlayManager.createOverlay("", buttonConfig);
	parentContainer.addOverlay(name, this.button);
	
	if(this.button != null){ // Overlay was added successfully
		// Image overlay that will change states based on the passed styles
		this.buttonImage = this.player.p.OverlayManager.createOverlay("Image", defaultConfig);
		this.button.addOverlay("background", this.buttonImage);
	
		// Events
		MN.Event.Observe(this.button,"rollover",this.rollOver);
		MN.Event.Observe(this.button,"rollout",this.rollOut);
		
		// Down
		MN.Event.Observe(this.button,"mousedown",this.mouseDown);
		MN.Event.Observe(this.button,"click",this.mouseClick);
	}
}

/**
  * Function: disableButton
  * Disable the button, sets the disabled state if available and prevents rollovers and clicks
  */
_button.disableButton = function(){
	this.disabledState = true;
	this.hoverState = false;
	this.downState = false;
	if(this.disabledStyle != null){
		this.buttonImage.set(new Array(this.defaultStyle, this.disabledStyle));
	}
}

/**
  * Function: enableButton
  * Enable the button, allows rollovers and clicks
  */
_button.enableButton = function(){
	this.disabledState = false;
	this.buttonImage.set(this.defaultStyle);
}

/**
  * Function: rollOver
  * Change to hover state if available
  */
_button.rollOver = function(){
	if(!this.disabledState && this.hoverStyle != null){
		this.hoverState = true;
		this.buttonImage.set(new Array(this.defaultStyle, this.hoverStyle));
	}
}

/**
  * Function: rollOut
  * Change to normal state if available and not disabled
  */
_button.rollOut = function(){
	if(!this.disabledState && this.hoverState){
		this.hoverState = false;
		this.downState = false;
		this.buttonImage.set(this.defaultStyle);
	}
}

/**
  * Function: mouseDown
  * Change to down state if available
  */
_button.mouseDown = function(){
	if(!this.disabledState && this.downStyle != null){
		this.downState = true;
		this.hoverState = false;
		this.buttonImage.set(new Array(this.defaultStyle, this.downStyle));
	}
}

/**
  * Function: mouseClick
  * Call click callback and change back to hover if available or normal if not
  */
_button.mouseClick = function(){
	if(!this.disabledState){
		this.downState = false;
		if(this.hoverStyle != null){
			this.hoverState = true;
			this.buttonImage.set(new Array(this.defaultStyle, this.hoverStyle));
		}else{
			this.buttonImage.set(this.defaultStyle);
		}
		if(this.clickFunc != null){
			this.clickFunc();
		}
	}
}

/**
  * Function: update
  * Updates the button container overlay
  *
  * Parameter:
  * configs:Array|Object - config attributes
  */
_button.update = function(configs){
	this.button.update(configs);
}
delete _button;

/**
 * Class: MN.Player.Slider
 * General horizontal slider component for Move Overlays
 */
MN.Player.Slider = MN.Class(MN.EventSource);
_slider= MN.Player.Slider.prototype;

/**
  * Function: initialize
  * Initializes a basic button component with styles for four different states; default, hover, down and disabled.
  * The button has a main container that is used for positioning and sizing. 
  * It also provides a mask for cropping if a sprite is used for the states
  * If a null value is passed for any state, that state is not used for the button
  * The default state is required to have a style
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  * name:String - name of the new slider overlay
  * areaConfig:Array - config for slider overlay
  * leadConfig:Array - config for the lead overlay (normally the background)
  * trailConfig:Array - config for the trail overlay (follows the scrubber)
  * leftEdgeConfig:Array - config for the left edge of the timeline
  * rightEdgeConfig:Array - config for the right edge of the timeline
  * scrubberConfig:Array - config for the scrubber box
  * scrubberNormalConfig:Array - config for the normal state of the scrubber
  * scrubberHoverConfig:Array - config for the hover state of the scrubber
  * scrubberDownConfig:Array - config for the down state of the scrubber
  */
_slider.initialize = function(player, parentContainer, name, areaConfig, leadConfig, trailConfig, leftEdgeConfig, rightEdgeConfig, scrubberConfig, scrubberNormalConfig, scrubberHoverConfig, scrubberDownConfig){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.scrubbing = false;
	
	// Overlays
	this.hitArea = this.player.p.OverlayManager.createOverlay("", areaConfig);
	parentContainer.addOverlay(name, this.hitArea);
	
	if(this.hitArea != null){ // Overlay was added successfully
		this.lead = this.player.p.OverlayManager.createOverlay("Image", leadConfig);
		this.hitArea.addOverlay("lead", this.lead);
		
		this.trail = this.player.p.OverlayManager.createOverlay("Image", trailConfig);
		this.hitArea.addOverlay("trail", this.trail);
		
		// Edges
		this.leftEdge = this.player.p.OverlayManager.createOverlay("Image", new Array(leftEdgeConfig, {visibility:"hidden"}));
		this.hitArea.addOverlay("ledge", this.leftEdge);
		this.rightEdge = this.player.p.OverlayManager.createOverlay("Image", new Array(rightEdgeConfig, {visibility:"hidden"}));
		this.hitArea.addOverlay("redge", this.rightEdge);
		
		this.scrubber = new MN.Player.Button(this.player, this.hitArea, "scrubber", null, scrubberConfig, scrubberNormalConfig, scrubberHoverConfig, scrubberDownConfig, null);
				
		// Reference to stage
		this.stage = this.player.p.OverlayManager.getOverlay("stage");
		
		// Drag-n-drop
		MN.Event.Observe(this.scrubber.button,"mousedown",this.startDrag);
	
		// Slider Click
		MN.Event.Observe(this.hitArea,"click",this.sliderClick);
		
		// Updates
		MN.Event.Observe(this.hitArea, "update", this.updateSliderUI);
		MN.Event.Observe(this.scrubber.button, "update", this.updateSliderUI);
		MN.Event.Observe(this.lead, "update", this.updateLeadUI);
	}
}

/**
  * Function: updateSliderUI
  * Updates the slider UI when a dependent overlay changes size or position
  */
_slider.updateSliderUI = function(){
	// Update slider lead width if slider width changes
	// Second condition avoids a jumping of the button when the overlays are first initialized
	if(this.hitArea.width - this.scrubber.button.width != this.lead.width && this.scrubber.button.width != 0){
		this.lead.update({width: (this.hitArea.width - this.scrubber.button.width) + "px"});
		// Show edges
		this.leftEdge.update({visibility:"visible"});
		this.rightEdge.update({visibility:"visible"});
	}	
	// Update lead and trail position if scrubber width changes
	if(Math.floor(this.scrubber.button.width/2) != this.lead.left){
		this.lead.update({left: Math.floor(this.scrubber.button.width/2) + "px"});
		this.trail.update({left: Math.floor(this.scrubber.button.width/2) + "px"});
	}
}

/**
  * Function: updateLeadUI
  * Fires event when lead changes
  */
_slider.updateLeadUI = function(){
	this.FireEvent("SliderLeadUIChanged");
}

/**
  * Function: startDrag
  * Called when the user begins dragging the slider's scrubber
  *
  * Parameter:
  * overlay:Object - scrubber object
  * j:Number - j relative position
  * k:Number - k relative position
  * x:Number - x absolute position
  * y:Number - y absolute position
  */
_slider.startDrag = function(overlay, j, k, x, y){
	this.scrubbing = true;
	this.FireEvent("ScrubbingChanged", this.scrubbing);
	MN.Event.Observe(this.stage,"mousemove",this.overlayMouseMove);
	MN.Event.Observe(this.stage,"mouseup",this.overlayMouseUp);
	MN.Event.Observe(this.stage,"rollout",this.overlayMouseUp);
	// Update the scrubber to the position of the mouse
	// This fixes an issue where the user mouses down and mouses up on the scrubber without moving. 
	// It doesn't register the click on the timeline because it believed the user was scrubbing
	this.overlayMouseMove(this.player.p.OverlayManager.getOverlay("stage"), x, y);
}

/**
  * Function: overlayMouseMove
  * Handler for mouse move event during dragging. Updates the scrubber and trail positions.
  *
  * Parameter:
  * overlay:Object - stage overlay object
  * x:Number - x relative position
  * y:Number - y relative position
  */
_slider.overlayMouseMove = function(overlay, x, y){
	var value = Math.floor(x) - this.lead.absoluteLeft; // Use abs here since x is based on stage
	if(value < 0){
		value = 0;	
	}
	if(value > this.lead.width){
		value = this.lead.width;	
	}
	
	this.trail.update({width: value + "px"});
	this.scrubber.update({left: value + "px"});
	this.FireEvent("ScrubberValue", value, "move");
}

/**
  * Function: overlayMouseUp
  * Called when the user is done dragging the scrubber
  */
_slider.overlayMouseUp = function(){
	this.scrubbing = false;
	this.FireEvent("ScrubbingChanged", this.scrubbing);
	MN.Event.StopObserving(this.stage,"mousemove",this.overlayMouseMove);
	MN.Event.StopObserving(this.stage,"mouseup",this.overlayMouseUp);
}

/**
  * Function: sliderClick
  * Called when the user clicks on the slider
  *
  * Parameter:
  * overlay:Object - overlay where the click occured
  * x:Number - x relative position of the click
  */
_slider.sliderClick = function(overlay, x){
	var value = Math.floor(x) - this.lead.left;
	if(value < 0){
		value = 0;	
	}
	if(value > this.lead.width){
		value = this.lead.width;	
	}
	this.trail.update({width: value + "px"});
	this.scrubber.update({left: value + "px"});
	this.FireEvent("ScrubberValue", value, "click");
}

/**
  * Function: getValue
  * Returns current value of trail width
  */
_slider.getValue = function(){
	return this.trail.width;
}

/**
  * Function: setValue
  * Updates the trail and scrubber to new value
  */
_slider.setValue = function(value){
	this.trail.update({width: value + "px"});
	this.scrubber.update({left: value + "px"});
	this.FireEvent("ScrubberValue", value, "set");
}
delete _slider;

/**
 * Class: MN.Player.TimelineSlider
 * Timeline component for native overlays.
 */
MN.Player.TimelineSlider = MN.Class(MN.EventSource);
_tSlider= MN.Player.TimelineSlider.prototype;

/**
  * Function: initialize
  * Initializes a timeline slider
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_tSlider.initialize = function(player, parentContainer, miniContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.parentContainer = parentContainer;
	this.miniContainer = miniContainer;
	this.scrubIntvl = 0; // Interval for tracking position
	this.seeking = false; // Flag when player is seeking
	this.scrubbing = false; // Slider is scrubbing
	this.scrubberChanged = false; // Flag set after click or mouse move for a second to prevent scrubber from updating on the wrong time
	this.markerCount = 0;
	this.adMarkers = [];
	this.controlsVisible = true;
	this.miniAdMarkers = [];
	this.miniVisible = false;
	this.miniFadeStarted = false;
	this.miniFadeInterval = null;
	
	// Configuration parameters for the native slider
	var area = MN.PlayerStyles.timeline;
	var lead = MN.PlayerStyles.timelineLead;
	var trail = MN.PlayerStyles.timelineTrail;
	var leftEdge = MN.PlayerStyles.timelineLeftEdge;
	var rightEdge = MN.PlayerStyles.timelineRightEdge;
	var scrubber = MN.PlayerStyles.timelineScrubber;
	var scrubberNormal = MN.PlayerStyles.timelineScrubberDefault;
	var scrubberHover = MN.PlayerStyles.timelineScrubberHover;
	
	// Native slider
	this.slider = new MN.Player.Slider(player, parentContainer, "timeline", area, lead, trail, leftEdge, rightEdge, scrubber, scrubberNormal, scrubberHover);
	
	var miniarea = MN.PlayerStyles.minitimeline;
	this.minislider = new MN.Player.Slider(player, miniContainer, "minitimeline", miniarea, lead, trail, leftEdge, rightEdge, scrubber, scrubberNormal, scrubberHover);
	
	// Events
	MN.Event.Observe(this.player.p,"PlayStateChanged", this.onPlayStateChanged);
	this.scrubIntvl = setInterval(this.updateScrubber,500);
	MN.Event.Observe(this.slider,"ScrubbingChanged", this.onScrubberChanged);
	MN.Event.Observe(this.slider,"ScrubberValue", this.onScrubberValue);
	MN.Event.Observe(this.player,"SeekingChanged", this.onSeekingChanged);
	MN.Event.Observe(this.slider,"SliderLeadUIChanged", this.setSliderValue);
	
	MN.Event.Observe(this.player.p, "TimelineLoaded", this.onTimelineLoaded);
	MN.Event.Observe(this.player.p, "UIStateChanged", this.onUIStateChanged);
	
	// Setup handlers in the player for events fired in this component
	MN.Event.Observe(this, "stopSeeking", MN.MakeBound(this.player, function(){
		this.FireEvent("stopSeeking");
	}));
	
	MN.Event.Observe(parentContainer, "update", this.onControlsUpdate);
}

/**
  * Function: onPlayStateChanged
  * Called when the player changes play states
  *
  * Parameter:
  * oldS:Number - Old play state
  * newS:Number - New play state
  */
_tSlider.onPlayStateChanged = function(oldS, newS){
	this.playState = newS;
}

/**
  * Function: onSeekingChanged
  * Called when the player starts/stops seeking (registered to player by ff/rw object)
  *
  * Parameter:
  * seek:Boolean - Flag if player is seeking
  */
_tSlider.onSeekingChanged = function(seek){
	this.seeking = seek;
}

/**
  * Function: onScrubberValue
  * Slider value updated
  *
  * Parameter:
  * value:Number -  New value of slider (trail width)
  * type:String - type of scrubber value change
  */
_tSlider.onScrubberValue = function(value, type){
	// Only change the value if the change was because of a user clicking on the timeline
	if(type == "click"){
		// Prevent scrubber from returning to old position until the current position is set in the player
		this.scrubberChanged = true;
		setTimeout(MN.MakeBound(this, function(){this.scrubberChanged = false;}), 1000);
		
		// Determine the position of the playback
		var pos = this.player.Duration() * (value/this.slider.lead.width);
		if(pos < 0){
			pos = 0;	
		}
		else if(pos >= this.player.Duration()-1){ //Player refuses to set position to duration, this avoids that
			pos = this.player.Duration()-1;
		}
		this.setCurrentPosition(pos);
	}	
}

/**
  * Function: onScrubberChanged
  * Toggle scrubbing
  *
  * Parameter:
  * scrub:Boolean - Scrubbing or not
  */
_tSlider.onScrubberChanged = function(scrub){
	if(this.scrubbing && !scrub){ // Update position of the value 
		// Prevent scrubber from returning to old position until the current position is set in the player
		this.scrubberChanged = true;
		setTimeout(MN.MakeBound(this, function(){this.scrubberChanged = false;}), 1000);
		
		// Determine the position of the playback
		var value = this.slider.getValue();
		var pos = this.player.Duration() * (value/this.slider.lead.width);
		if(pos < 0){
			pos = 0;	
		}
		else if(pos >= this.player.Duration()-1){ //Player refuses to set position to duration, this avoids that
			pos = this.player.Duration()-1;
		}
		this.setCurrentPosition(pos);	
	}
	this.scrubbing = scrub;
}

/**
  * Function: updateScrubber
  * Updates the timeline scrubber based on the current position of the video
  */
_tSlider.updateScrubber = function(){
	if (this.player.p.Paused()|| this.scrubbing || this.scrubberChanged || !this.player.p.qvt || cwPlayer.playingAd) return; //If the video is paused, scrubbing or no video is in the player don't update the scrubber
	
	// Stop seeking if the video reaches the beginning
	if (this.seeking)
	{
		var pos = parseInt(this.player.CurrentPosition(),10);
		if (pos==0)
		{
			this.FireEvent("stopSeeking");
		}
	}
	this.setSliderValue();
}

/**
  * Function: setSliderValue
  * Sets the slider's value based on the current position of the video out of the total duration
  */
_tSlider.setSliderValue = function(){
	var curPos = this.player.CurrentPosition();
	var dur = this.player.Duration();
	if (this.playState==MN.QMP.PS.MediaEnded){
		curPos = dur;	
	}
	var pct = (curPos / dur) * this.slider.lead.width;
	if (!isNaN(pct) && (this.playState == MN.QMP.PS.Playing || this.playState == MN.QMP.PS.MediaEnded)){
		var value = Math.floor(pct);
		if(value >= 0 && value <= this.slider.lead.width){
			this.slider.setValue(value);
			this.minislider.setValue(Math.floor((curPos / dur) * this.minislider.lead.width));
		}
	}
}

/**
  * Function: setCurrentPosition
  * Sets the player to a new position in the video
  *
  * Parameter:
  * newPos:Number - New position for the video in seconds
  */
_tSlider.setCurrentPosition = function(newPos){
	if(newPos >= 0 && newPos < this.player.Duration()){
		if(this.seeking){
			this.FireEvent("stopSeeking");	
		}
		this.player.CurrentPosition(newPos);
	}
}

_tSlider.addAdMarker = function(index, time){
	var duration = this.player.Duration();
	var step = (this.slider.lead.width) / duration;
	var pos = Math.floor(time * step) + 2;
	
	var ministep = (this.minislider.lead.width) / duration;
	var minipos = Math.floor(time * ministep) + 2;
	
	if (index < this.adMarkers.length && this.adMarkers[index]!=undefined)
	{
		var marker = this.adMarkers[index];
		marker.update({"left":pos+"px","visibility":"visible"});
		
		var minimarker = this.miniAdMarkers[index];
		minimarker.update({"left":minipos+"px","visibility":"visible"});
	}
	else
	{
		var opts = MN.PlayerStyles.adMarker;
		opts.left = pos + "px";
		var marker = this.player.p.OverlayManager.createOverlay("Image", opts);
		this.slider.hitArea.addOverlay("marker" + (this.markerCount++), marker);
		
		opts = MN.PlayerStyles.miniAdMarker;
		opts.left = minipos + "px";
		var minimarker = this.player.p.OverlayManager.createOverlay("Image", opts);
		this.minislider.hitArea.addOverlay("minimarker" + (this.markerCount++), minimarker);
		
		this.adMarkers[index] = marker;
		this.miniAdMarkers[index] = minimarker;
	}
}

_tSlider.clearAdMarkers = function()
{
	for (var x=0; x < this.adMarkers.length; x++)
	{
		this.adMarkers[x].update({"visibility":"hidden"});
		this.miniAdMarkers[x].update({"visibility":"hidden"});
	}
}

/**
  * Function: onTimelineLoaded
  * 
  */
_tSlider.onTimelineLoaded = function()
{
	this.clearAdMarkers();
	this.drawAdBreaks();
}

/**
  * Function: onUIStateChanged
  * 
  */
_tSlider.onUIStateChanged = function(state)
{
	if (state == "0")
	{
		this.controlsVisible = false;
		setTimeout(this.drawAdBreaks, 100);
	}
	else if (state == "1")
	{
		clearInterval(this.miniFadeInterval);
		this.controlsVisible = true;
		this.miniVisible = false;
		this.miniFadeStarted = false;
		this.miniContainer.update({"alpha":0});
		setTimeout(this.drawAdBreaks, 100);
	}
	else if (state == "2" || state == "3")
	{
		this.clearAdMarkers();
		setTimeout(this.drawAdBreaks, this.player.p.Paused()?700:500);
	}
}

/**
  * Function: drawAdBreaks
  * 
  */
_tSlider.drawAdBreaks = function()
{
	var qvt = this.player.p.qvt;
	if (qvt != null && qvt.meta && qvt.meta.clips)
	{
		var clips = qvt.meta.clips;
		
		var adIndex = 0;
		for (var x=0; x < clips.length; x++)
		{
			var clip = clips[x];
			if (clip && clip.isGap)
			{
				this.addAdMarker(adIndex++, clip.tlStartTime);
			}
		}
	}
}

_tSlider.onControlsUpdate = function(overlay, left, top, width, height, alpha, isdone)
{
	if (this.parentContainer.curOptions.visibility == "visible" && alpha < 150 && !this.miniFadeStarted && !this.controlsVisible)
	{
		this.miniFadeStarted = true;
		var tween = new MN.Player.Tween(this.miniContainer, "alpha", 0, 255, 500, 0, 25, this.miniFadeDone);
		this.miniFadeInterval = tween.tracker;
	}
}

_tSlider.miniFadeDone = function()
{
	this.miniVisible = true;
	this.miniFadeStarted = false;
}
delete _tSlider;

/**
 * Class: MN.Player.VolumeSlider
 * Volume slider component for native overlays.
 */
MN.Player.VolumeSlider = MN.Class(MN.EventSource);
_vSlider= MN.Player.VolumeSlider.prototype;

/**
  * Function: initialize
  * Initializes a volume slider
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_vSlider.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.scrubbing = false; // Flag when user is scrubbing
	this.curVol = this.player.p.Volume(); // Current volume
	this.muteVol = 0; // Saved volume when the mute is pressed
	
	// Configuration parameters for the native slider
	var area = MN.PlayerStyles.volume;
	var lead = MN.PlayerStyles.volumeLead;
	var trail = MN.PlayerStyles.volumeTrail;
	var leftEdge = MN.PlayerStyles.volumeLeftEdge;
	var rightEdge = MN.PlayerStyles.volumeRightEdge;
	var scrubber = MN.PlayerStyles.volumeScrubber;
	var scrubberNormal = MN.PlayerStyles.volumeScrubberDefault;
	var scrubberHover = MN.PlayerStyles.volumeScrubberHover;
	var iconConfig = MN.PlayerStyles.volumeIcon;
	var iconNormal = MN.PlayerStyles.volumeIconNormal;
	var iconHover = MN.PlayerStyles.volumeIconHover;
	
	// Native slider
	this.slider = new MN.Player.Slider(player, parentContainer, "volume", area, lead, trail, leftEdge, rightEdge, scrubber, scrubberNormal, scrubberHover);
	
	// Volume icon
	this.icon = new MN.Player.Button(player, parentContainer, "scrubber", null, iconConfig, iconNormal, iconHover, null, null);
	
	// Set volume
	//this.setVolume();
	
	// Event handlers
	MN.Event.Observe(this.slider,"ScrubbingChanged", this.onScrubberChanged);
	MN.Event.Observe(this.slider,"ScrubberValue", this.onScrubberValue);
	MN.Event.Observe(this.slider,"SliderLeadUIChanged", this.onSliderLeadUIChanged);
}

/**
  * Function: setVolume
  * Sets the volume of the Move player, scale is 0 to 100 (percent), based on watching the volume slider
  *
  * Parameter:
  * newVol:Number - new volume setting
  */
_vSlider.setVolume = function(newVol){
	// Determine actual new volume
	if(newVol == 0){
		this.player.p.Muted(true);
	}
	else if(newVol > 0 && newVol <= 100){
		this.player.p.Muted(false);
	}
	else{
		newVol = this.curVol;	
	}
	
	// Save new volume
	this.player.p.Volume(newVol);
	this.curVol = newVol;
	
	//Update scrubber
	if(!this.scrubbing){
		var value = Math.floor(this.curVol*(this.slider.lead.width/100));
		if(value >= 0 && value <= this.slider.lead.width){
			this.slider.setValue(value);
		}
	}
}

/**
  * Function: onScrubberValue
  * Slider value updated
  *
  * Parameter:
  * value:Number -  New value of slider (trail width)
  * type:String - type of scrubber value change
  */
_vSlider.onScrubberValue = function(value, type){
	if(type != "set"){ 
		value = Math.floor(value * (100/this.slider.lead.width));
		this.setVolume(value);
	}
}

/**
  * Function: onScrubberChanged
  * Toggle scrubbing
  *
  * Parameter:
  * scrub:Boolean - Scrubbing or not
  */
_vSlider.onScrubberChanged = function(scrub){
	this.scrubbing = scrub;
}

/**
  * Function: onSliderLeadUIChanged
  * Update scrubber position for the current position
  */
_vSlider.onSliderLeadUIChanged = function(){
	this.setVolume();
}

/**
  * Function: muteToggle
  * Volume toggle function - not used currently
  */
_vSlider.muteToggle = function(){
	if(this.player.p.Volume() != 0){
		this.muteVol = this.player.p.Volume();
		this.setVolume(0);
	}
	else{
		if(this.muteVol != null){
			this.setVolume(this.muteVol);
		}
	}
}
delete _vSlider;

/**
 * Class: MN.Player.PlayPause
 * Button to play and pause the playback of the video. Reflects the current paused/un-paused state of the player.
 */
MN.Player.PlayPause = MN.Class(MN.EventSource);
_playPause= MN.Player.PlayPause.prototype;

/**
  * Function: initialize
  * Initializes a play/pause toggle button
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_playPause.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.playState = 0; // Current play state of the player
	this.curPos = 0; // Last known current position
	this.playPauseHover = false; // Flag if the user is currently hovering over the button
	this.seeking = false; // Flag if the player is seeking (ff/rw)
	
	// Overlays
	this.play = new MN.Player.Button(this.player, parentContainer, "play", this.onClick, MN.PlayerStyles.playPause, MN.PlayerStyles.playDefault, MN.PlayerStyles.playHover, null, null);
	this.pause = new MN.Player.Button(this.player, parentContainer, "pause", this.onClick, new Array(MN.PlayerStyles.playPause, {visibility:"hidden"}), MN.PlayerStyles.pauseDefault, MN.PlayerStyles.pauseHover, null, null);
	
	// Events Handlers
	MN.Event.Observe(this.player.p,"PlayStateChanged", this.onPlayStateChanged);
	MN.Event.Observe(this.player.p,"PausedChanged", this.checkPlayPause);
	MN.Event.Observe(this.player, "CurrentPositionChanged", this.updateCurrentPosition);
	MN.Event.Observe(this.player, "SeekingChanged", this.onSeekingChanged);
	
	// Setup handlers in the player for events fired in this component
	MN.Event.Observe(this, "stopSeeking", MN.MakeBound(this.player, function(){
		this.FireEvent("stopSeeking");
	}));
}

/**
  * Function: onPlayStateChanged
  * Save the new play state and check overlays to match it
  *
  * Parameter:
  * oldS:Number - Old play state
  * newS:Number - New play state
  */
_playPause.onPlayStateChanged = function(oldS, newS){
	this.playState = newS;
	this.checkPlayPause();
}

/**
  * Function: updateCurrentPosition
  * Save the latest current position
  *
  * Parameter:
  * newPos:Number - Current position
  */
_playPause.updateCurrentPosition = function(newPos){
	this.curPos = newPos;
}

/**
  * Function: onSeekingChanged
  * Verify the play state based on the seeking status of the player
  *
  * Parameter:
  * seek:Boolean - flag for seeking
  */
_playPause.onSeekingChanged = function(seek){
	this.seeking = seek;
	this.checkPlayPause(this.seeking);
}

/**
  * Function: onClick
  * Toggles the video between playing and paused
  */
_playPause.onClick = function(){
	// Video is currently in the PLAYING state
	if (this.playState == MN.QMP.PS.PLAYING)
	{
		// Video is seeking (ff/rw)
		if (this.seeking)
		{
			// Stop seeking if the user clicks "Play"
			this.FireEvent("stopSeeking");
			return;
		}
		this.togglePlayPause();
	}
	else if(this.playState == MN.QMP.PS.MediaEnded){
		// Video is ended, so restart
		this.player.CurrentPosition(-1);
	}
	else
	{
		//Not playing, setting the current position to the last known position will cause the player to play
		this.player.CurrentPosition(this.curPos);
	}
}

/**
  * Function: checkPlayPause
  * Called when the play state has changed or the video has change from/to a paused state.
  * This function will check the states and show the correct buttons
  *
  * Parameter:
  * paused:Boolean - flag if the player is paused or not
  */
_playPause.checkPlayPause = function(paused){
	
	// Set paused value if not already set
	if(typeof(paused) == "undefined" || paused == null){
		paused = this.player.p.Paused();
	}

	// Set the value of the button. Remember that if the video is paused, the user should see "Play" as an option and vice versa.
	if(this.playState == MN.QMP.PS.MediaEnded)
	{
		// Display the Play button if the user is at the end
		this.play.update({visibility:"visible"});
		this.pause.update({visibility:"hidden"});
	}else{
		if(paused){
			// Video is paused, display the play button
			this.play.update({visibility:"visible"});
			this.pause.update({visibility:"hidden"});
		}
		else{
			// Video is playing, display the pause button
			this.pause.update({visibility:"visible"});
			this.play.update({visibility:"hidden"});
		}
	}
}

/**
  * Function: togglePlayPause
  * Toggle the play/paused state of the player
  *
  * Parameter:
  * flag:Boolean - If not null, sets the Paused value to flag
  */
_playPause.togglePlayPause = function(flag){
	if (this.playState == MN.QMP.PS.PLAYING)
	{
		var paused = (flag) ? !flag : this.player.p.Paused();
		this.player.p.Paused(!paused);
	}
}
delete _playPause;

/**
 * Class: MN.Player.Status
 * Video playback status display
 */
MN.Player.Status = MN.Class(MN.EventSource);
_status = MN.Player.Status.prototype;

/**
  * Function: initialize
  * Initializes the video status display field
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_status.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.playState = 0; // Current play state of the player
	
	// Overlays
	this.txtField = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.txt, MN.PlayerStyles.txtstatus));
	parentContainer.addOverlay("status", this.txtField);

	// Events
	MN.Event.Observe(this.player.p,"PlayStateChanged", this.onPlayStateChanged); 
	MN.Event.Observe(this.player.p,"PausedChanged", this.checkPlayPause); 
	MN.Event.Observe(this.player,"SeekingChanged", this.onSeekingChanged);
}

/**
  * Function: onPlayStateChanged
  * Called when the player changes play states
  *
  * Parameter:
  * oldS:Number - Old play state
  * newS:Number - New play state
  */
_status.onPlayStateChanged = function(oldS, newS){
	this.playState = newS;
	var stateStr = MN.QMP.PS[this.playState];
	var displayTxt = "video " + stateStr;
	this.txtField.setText(displayTxt.toLowerCase());
}

/**
  * Function: checkPlayPause
  * Called when the paused stated has changed
  *
  * Parameter:
  * paused:Boolean - flag if the player is paused or not
  */
_status.checkPlayPause = function(paused){
	
	// Set paused value
	if(paused == null){
		paused = this.player.p.Paused();
	}

	if (this.playState==MN.QMP.PS.PLAYING)
	{
		if(!paused){
			this.txtField.setText("video playing");
		}
		else{
			this.txtField.setText("video paused");
		}
	}
}

/**
  * Function: onSeekingChanged
  * Update seeking text based on seek status
  *
  * Parameter:
  * seek:Boolean - Flag for seeking
  * seekType:String - Rewind or Fast Forward
  * scrubSpeed:Number - 2,4,8,16 - speed of the scrub
  */
_status.onSeekingChanged = function (seek, seekType, scrubSpeed){
	if(!seek){
		var stateStr = MN.QMP.PS[this.playState];
		var displayTxt = "video " + stateStr;
		this.txtField.setText(displayTxt.toLowerCase());
	}
	else{
		var displayTxt = "video " + seekType + Math.abs(scrubSpeed) + "x";
		this.txtField.setText(displayTxt.toLowerCase());
	}
}


/**
 * Class: MN.Player.BitRate
 * Video bitrate display
 */
MN.Player.BitRate = MN.Class(MN.EventSource);
_bitrate = MN.Player.BitRate.prototype;

/**
  * Function: initialize
  * Initializes the video bitrate display field
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_bitrate.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.bitrate = 0; // Current bit rate of the player

	// Overlay
	this.txtField = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.txt, MN.PlayerStyles.txtbitrate));
	parentContainer.addOverlay("bitrate", this.txtField);
	
	// Event Handler
	MN.Event.Observe(this.player.p,"BitRateChanged", this.onBitRateChanged);
}

/**
  * Function: onBitRateChanged
  * Called when the player changes bit rate
  *
  * Parameter:
  * newBR:Number - New bit rate
  */
_bitrate.onBitRateChanged = function(newBR){
	this.bitrate = newBR;
	if (!isNaN(this.bitrate)){
		this.txtField.setText(this.bitrate + " kbps");
	}
}

/**
 * Class: MN.Player.TimeDisplay
 * Video bitrate display
 */
MN.Player.TimeDisplay = MN.Class(MN.EventSource);
_timedisplay = MN.Player.TimeDisplay.prototype;

/**
  * Function: initialize
  * Video time display for current position out of duration
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_timedisplay.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;

	// Overlays
	this.txtField = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.txt, MN.PlayerStyles.txttimedisplay));
	parentContainer.addOverlay("timedisplay", this.txtField);
	
	// Event Handlers
	MN.Event.Observe(this.player, "CurrentPositionChanged", this.update);
}

/**
  * Function: update
  * Updates the time display to reflect the position of the video out of the total duration
  *
  * Parameter:
  * pos:Number - current position in video playback
  * total:Number - total time in video playback
  */
_timedisplay.update = function (pos, total){
	var val = MN.ConvertToTimestamp(pos) + " / " + MN.ConvertToTimestamp(total);
	if(this.txtField.getText() != val){
		this.txtField.setText(val);
	}
}


/**
 * Class: MN.Player.VideoInfo
 * Information regarding video playback, time display and bitrate all in one text field
 */
MN.Player.VideoInfo = MN.Class(MN.EventSource);
_vidinfo = MN.Player.VideoInfo.prototype;

/**
  * Function: initialize
  * Initializes the video status display field
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_vidinfo.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	this.playState = 0; // Current play state of the player
	this.bitrate = ""; // Current bit rate of the player
	this.status = ""; // Current status 
	this.time = ""; // Current time
	
	// Overlays
	this.txtField = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.txt, MN.PlayerStyles.txtvideoinfo));
	parentContainer.addOverlay("vidinfo", this.txtField);

	// Events
	MN.Event.Observe(this.player.p,"PlayStateChanged", this.onPlayStateChanged); 
	MN.Event.Observe(this.player.p,"PausedChanged", this.checkPlayPause); 
	MN.Event.Observe(this.player,"SeekingChanged", this.onSeekingChanged);
	MN.Event.Observe(this.player.p,"BitRateChanged", this.onBitRateChanged);
	MN.Event.Observe(this.player, "CurrentPositionChanged", this.update);
}

/**
  * Function: onPlayStateChanged
  * Called when the player changes play states
  *
  * Parameter:
  * oldS:Number - Old play state
  * newS:Number - New play state
  */
_vidinfo.onPlayStateChanged = function(oldS, newS){
	this.playState = newS;
	var stateStr = MN.QMP.PS[this.playState];
	this.status = stateStr.toLowerCase();
	this.createString(this.time, this.status, this.bitrate);
}

/**
  * Function: checkPlayPause
  * Called when the paused stated has changed
  *
  * Parameter:
  * paused:Boolean - flag if the player is paused or not
  */
_vidinfo.checkPlayPause = function(paused){
	
	// Set paused value
	if(paused == null){
		paused = this.player.p.Paused();
	}

	if (this.playState==MN.QMP.PS.PLAYING)
	{
		if(!paused){
			this.status = "playing";
		}
		else{
			this.status = "paused";
		}
		this.createString(this.time, this.status, this.bitrate);
	}
}

/**
  * Function: onSeekingChanged
  * Update seeking text based on seek status
  *
  * Parameter:
  * seek:Boolean - Flag for seeking
  * seekType:String - Rewind or Fast Forward
  * scrubSpeed:Number - 2,4,8,16 - speed of the scrub
  */
_vidinfo.onSeekingChanged = function (seek, seekType, scrubSpeed){
	if(!seek){
		var stateStr = MN.QMP.PS[this.playState];
		this.status = stateStr.toLowerCase();
		this.createString(this.time, this.status, this.bitrate);
	}
	else{
		var displayTxt = seekType + Math.abs(scrubSpeed) + "x";
		this.status = displayTxt.toLowerCase();
		this.createString(this.time, this.status, this.bitrate);
	}
}

/**
  * Function: onBitRateChanged
  * Called when the player changes bit rate
  *
  * Parameter:
  * newBR:Number - New bit rate
  */
_vidinfo.onBitRateChanged = function(newBR){
	if (!isNaN(newBR)){
		this.bitrate = newBR + " kbps";
		this.createString(this.time, this.status, this.bitrate);
	}
}

/**
  * Function: update
  * Updates the time display to reflect the position of the video out of the total duration
  *
  * Parameter:
  * pos:Number - current position in video playback
  * total:Number - total time in video playback
  */
_vidinfo.update = function (pos, total){
	var val = MN.ConvertToTimestamp(pos) + " / " + MN.ConvertToTimestamp(total);
	if(this.time != val){
		this.time = val;
		this.createString(this.time, this.status, this.bitrate);
	}
}

/**
  * Function: createString
  * Build new video info string based on the current time, status and bitrate
  *
  * Parameter:
  * time:String - current position out of duration in video playback
  * status:String - current status of the player
  * bitrate:String - current bitrate of the player
  */
_vidinfo.createString = function(time, status, bitrate){
	var val = time + "     " + status + "     " + bitrate;
	this.txtField.setText(val);
}

/**
 * Class: MN.Player.TitleDisplay
 * Title display
 */
MN.Player.TitleDisplay = MN.Class(MN.EventSource);
_titledisplay = MN.Player.TitleDisplay.prototype;

/**
  * Function: initialize
  * Episode title and series display
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_titledisplay.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;

	// Overlays
	this.txtField = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.txt, MN.PlayerStyles.txttitledisplay));
	parentContainer.addOverlay("titledisplay", this.txtField);
	
	MN.Event.Observe(this.player.p, "UIStateChanged", this.onUIStateChanged);
}

/**
  * Function: update
  * 
  *
  * Parameter:
  * str:String - Text to display
  */
_titledisplay.update = function (str){
	if(this.txtField.getText() != str){
		this.txtField.setText(str);
	}
}

/**
  * Function: update
  * 
  *
  * Parameter:
  * str:String - Text to display
  */
_titledisplay.onUIStateChanged = function (state){
	if (state == "2")
	{
		this.txtField.update(MN.PlayerStyles.txttitledisplayfull)
	}
	else if (state == "3")
	{
		this.txtField.update(MN.PlayerStyles.txttitledisplaynormal);
	}
}
delete _titledisplay;

/**
 * Class: MN.Player.FullScreen
 * Creates fullscreen and normal screen overlay buttons to allow the user to toggle between the two screen modes
 */
MN.Player.FullScreen= MN.Class(MN.EventSource);
_fs = MN.Player.FullScreen.prototype;

/**
  * Function: initialize
  * Initializes a fullscreen/normalscreen toggle button
  *
  * Parameter:
  * player:Object - Move Player controller
  * parentContainer:Object - parent Overlay Object
  */
_fs.initialize = function(player, parentContainer){
	MN.EventSource.prototype.initialize.apply(this);
	
	// Properties
	this.player = player;
	
	// Overlays
	this.full = new MN.Player.Button(this.player, parentContainer, "fullscreen", this.onClickFullScreen, MN.PlayerStyles.full, MN.PlayerStyles.fullDefault, MN.PlayerStyles.fullHover, null, null);
	this.normal = new MN.Player.Button(this.player, parentContainer, "normalscreen", this.onClickNormalScreen, MN.PlayerStyles.normal, MN.PlayerStyles.normalDefault, MN.PlayerStyles.normalHover, null, null);
}

// Click to go to full screen
_fs.onClickFullScreen = function(){
	this.player.p.fullScreen(true, "Zoom", {speed:250});
}

// Click to go to normal screen
_fs.onClickNormalScreen = function(){
	this.player.p.fullScreen(false, "Zoom", {speed:250});
}
delete _fs;

/**
 * Class: MN.Player.Preview
 * Thumbnail preview component that hooks to a MN.Player.TimelineSlider component for native overlays.
 */
MN.Player.Preview = MN.Class(MN.EventSource);
_preview= MN.Player.Preview.prototype;

/**
  * Function: initialize
  * Initializes a thumbnail preview for a timeline slider
  *
  * Parameter:
  * player:Object - Move Player controller
  * timeline:Object - TimelineSlider object
  */
_preview.initialize = function(player, timeline){
	MN.EventSource.prototype.initialize.apply(this);

	// Properties
	this.player = player;
	this.timeline = timeline; // Timeline object
	this.url = ""; // Current url of the video
	this.durationInt = 0; // Tracker for fade animation
	this.referenceArray = new Array(); // References every thumbnail available in the current video
	this.timelineArray = new Array(); // References the currently loaded thumbnails for the timeline
	this.loadArray = new Array(); // Thumbnails that are being loaded
	this.tmpArray = new Array(); // Temp array to duplicate the loadArray
	this.interval = 2; // Number of seconds for unique thumbnail in QMX
	this.pixelWidth = 2; // Approximate space for each unique thumbnail on the timeline
	this.maxLength = 0; // Maximum number of thumbnails calculated by the pixelWidth and width of the actual timeline
	this.activeThumbUrl = ""; // Thumbnail url being displayed
	this.thumbsOn = false; // Flag if video is thumbnail preview enabled
	this.loaders = new Array(); // Array of preloading overlays
	this.loadLimit = 1; // Max number of preloading overlays

	// Preview container
	this.container = this.player.p.OverlayManager.createOverlay("", MN.PlayerStyles.preview);
	this.player.p.OverlayManager.addOverlay("preview", this.container);
	this.stage = this.player.p.OverlayManager.getOverlay("stage");

	if(this.container != null){
		
		// Preview background
		this.previewBack = this.player.p.OverlayManager.createOverlay("Image", new Array(MN.PlayerStyles.previewBack, {visibility:"hidden"}));
		this.container.addOverlay("previewBack", this.previewBack);
		
		// Thumbnail
		this.thumb = this.player.p.OverlayManager.createOverlay("Image", new Array(MN.PlayerStyles.previewThumb, {visibility:"hidden"}));
		this.container.addOverlay("thumbnail", this.thumb);
		
		// Thumbnail loaders - never shown
		for(var i=0; i<this.loadLimit; i++){
			var tmp = this.player.p.OverlayManager.createOverlay("Image", new Array(MN.PlayerStyles.previewThumb, {visibility:"hidden"}));
			this.container.addOverlay("thumbnailLoader"+i, tmp);
			this.loaders.push(tmp);
		}
		
		// No Preview background
		this.noPreviewBack = this.player.p.OverlayManager.createOverlay("Image", MN.PlayerStyles.noPreviewBack);
		this.container.addOverlay("noPreviewBack", this.noPreviewBack);

		// Time
		this.time = this.player.p.OverlayManager.createOverlay("Text", new Array(MN.PlayerStyles.noPreviewTime, MN.PlayerStyles.noPreviewTimeShort));
		this.container.addOverlay("time", this.time);

		// Tracker
		this.tracker = this.player.p.OverlayManager.createOverlay("Image", MN.PlayerStyles.previewTracker);
		this.container.addOverlay("tracker", this.tracker);

		// Events
		MN.Event.Observe(this.player, "NewClip", this.checkNewUrl); //Playing new clip event
		MN.Event.Observe(this.timeline.slider.hitArea,"rollover",this.timelineRollOver);
		MN.Event.Observe(this.timeline.slider.hitArea,"rollout",this.timelineRollOut);
		
		// Check and see if a video has already loaded
		//this.checkNewUrl(this.player.currentUrl);
	}
}

/**
  * Function: timelineRollOver
  * Mouse over the timeline, display thumbnail preview
  */
_preview.timelineRollOver = function(){
	this.fadeThumb(true);
	MN.Event.Observe(this.stage,"mousemove",this.timelineMouseMove);
}

/**
  * Function: timelineRollOut
  * Mouse out of the timeline, hide thumbnail preview
  */
_preview.timelineRollOut = function(){
	this.fadeThumb(false);
	MN.Event.StopObserving(this.stage,"mousemove",this.timelineMouseMove);
}

/**
  * Function: timelineMouseMove
  * Display thumbnail preview at the correct position
  *
  * Parameter:
  * overlay:Object - stage
  * x:Number - absolute x position
  * y:Number - absolute y position
  */
_preview.timelineMouseMove = function(overlay, x, y){
	
	var value = Math.floor(x) - this.timeline.slider.lead.absoluteLeft;
	if(value < 0){
		value = 0;	
	}
	if(value > this.timeline.slider.lead.width){
		value = this.timeline.slider.lead.width;	
	}
	// Position preview
	var previewX = Math.floor(x);
	if(previewX < this.timeline.slider.lead.absoluteLeft){
		previewX = this.timeline.slider.lead.absoluteLeft;
	}else if(previewX > this.timeline.slider.lead.absoluteLeft + this.timeline.slider.lead.width){
		previewX = this.timeline.slider.lead.absoluteLeft + this.timeline.slider.lead.width;
	}
	this.container.update({left: (previewX - (this.container.width/2)) + "px"});
	
	// Display correct thumbnail
	var pos = this.player.Duration() * (value/this.timeline.slider.lead.width);
	this.showThumbnail(pos);
}

/**
  * Function: showThumbnail
  * Show the thumbnail for the correct position
  *
  * Parameter:
  * curPos:Number - position of the video thumbnail
  */
_preview.showThumbnail = function(curPos){
	if(this.player.p && this.player.Duration() >= curPos){
		// Set text and adjust left value to center
		var val = MN.ConvertToTimestamp(curPos);
		if(val.length > 5){
			if(this.thumbsOn){
				this.time.update([{caption:val}, MN.PlayerStyles.previewTimeLong]);
			}else{
				this.time.update([{caption:val}, MN.PlayerStyles.noPreviewTimeLong]);
			}
		}else{
			if(this.thumbsOn){
				this.time.update([{caption:val}, MN.PlayerStyles.previewTimeShort]);
			}else{
				this.time.update([{caption:val}, MN.PlayerStyles.noPreviewTimeShort]);
			}
		}
		

		var index = Math.floor(this.timelineArray.length * (curPos/this.player.Duration()));
		var newUrl = this.timelineArray[index].url;
		if(newUrl != this.activeThumbUrl){
			this.activeThumbUrl = newUrl;
			this.thumb.update({url:this.activeThumbUrl});
		}
	}
}

/**
  * Function: checkNewUrl
  * Grab the new video url, check if its thumbnail preview enabled, load new thumbnails if necessary
  *
  * Parameter:
  * newUrl:String - new video url
  */
_preview.checkNewUrl = function(newUrl){
	if(newUrl != ""){
		this.hideThumb();

		// New clip and thumbnail to load
		//if(this.url != newUrl){
			this.url = newUrl;
		
			//Create new one, first by checking for duration (or waiting for it)
			MN.Event.Observe(this.player.p, "TimelineLoaded", MN.MakeBound(this,this.onTimelineLoaded));
		//}
	}
}

/**
  * Function: onTimelineLoaded
  * Get the duration for the new video clip and load thumbnails if it has thumbnail preview enabled
  */
_preview.onTimelineLoaded = function(){
	MN.Event.StopObserving(this.player.p, "TimelineLoaded", MN.MakeBound(this,this.onTimelineLoaded));
	this.createThumbs(this.player.Duration());
}

/**
  * Function: createThumbs
  * Created the array of thumbnail overlays
  *
  * Parameter:
  * duration:Number - total duration of the current video
  */
_preview.createThumbs = function(duration){

	// Create a new referenceArray
	this.referenceArray = new Array();

	// Loop through player duration, increment by interval
	for(var i=0; i<duration; i+=this.interval){
		// Creat thumb Obj with pos, url and loaded flag
		var thumbObj = {};
		thumbObj.pos = this.referenceArray.length;
		thumbObj.url = this.getQMXThumbUrl(i);
		this.referenceArray.push(thumbObj);
	}

	// Create a new timelineArray
	this.timelineArray = new Array();

	// Get real max length of the timelineArray (meaning the number of unique thumbnails which will be shown)
	// max length = width of the timeline / pixelWidth
	// real max length = closest to 2^n + 1 && < timeline width && < duration / interval
	var maxLen = Math.floor(this.timeline.slider.lead.width / this.pixelWidth);
	var prev = 0;
	for(var n = 1; n < 20; n++){	 // Upper limit of 20 for sanity check
		var val = Math.pow(2, n) + 1;
		var lowerDif = Math.abs(maxLen - prev);
		var upperDif = Math.abs(maxLen - val);
		if(lowerDif < upperDif || val >= this.timeline.slider.width || val >= (duration/this.interval)){
			// prev is closest value
			this.maxLength = prev;
			break;
		}
		prev = val;
	}

	// Create three new references in Timeline array that point to the first, mid and last obj in the reference array
	this.loadArray = new Array();
	this.loadArray.push(this.referenceArray[0]); // First
	this.loadArray.push(this.referenceArray[Math.floor(this.referenceArray.length/2)]); // Mid
	this.loadArray.push(this.referenceArray[this.referenceArray.length-1]); // Last

	// Reset values
	this.thumbsOn = false;
	this.thumb.update({visibility:"hidden"});
	this.previewBack.update({visibility:"hidden"});
	this.noPreviewBack.update({visibility:"visible"});
	this.time.update(MN.PlayerStyles.noPreviewTime);

	// Load images in the load array
	this.loadThumbs();
}

/**
  * Function: loadThumbs
  * Loads all the unloaded thumbnails from the load array
  */
_preview.loadThumbs = function(){
	// Create a duplicate of the load array
	this.tmpArray = this.loadArray.slice(0);
	
	// Loop through loaders and assign it a thumbnail to load by popping off the duplicate array
	// Give the loader a value of "true" for loading if it gets a thumbnail, 
	// "false" if the duplicate array empties before all the loaders are assigned
	for(var i=0; i<this.loaders.length; i++){
		var thumbLoader = this.loaders[i];
		if(this.tmpArray.length > 0){
			var thumb = this.tmpArray.pop();
			thumbLoader.loading = true;
			MN.Event.Observe(thumbLoader, "imageloaded", this.onImageLoaded);
			thumbLoader.update({url:thumb.url});
		}else{
			thumbLoader.loading = false;
		}
	}
}

/**
  * Function: onImageLoaded
  * Handles image loaded event for the thumbLoader overlay
  * Used to track the preloading of thumbnails
  *
  * Parameter:
  * thumbLoader:Object - overlay that threw the event
  * success:Boolean - if image was loaded or not
  * status:String - http status codes
  * imgUrl:String - url of image that was loaded
  */
_preview.onImageLoaded = function(thumbLoader, success, status, imgUrl){
	if(success){
		MN.Event.StopObserving(thumbLoader, "imageloaded", this.onImageLoaded);
		thumbLoader.loading = false;
		log("image done loading: " + imgUrl);
		setTimeout(MN.MakeBound(this, function(){ 
			this.loadNextImage(thumbLoader);
		}), 250);
		
	}
}

/**
  * Function: loadNextImage
  * Get the next available image to load
  * 
  *
  * Parameter:
  * thumbLoader:Object - thumbnail loader overlay
  */
_preview.loadNextImage = function(thumbLoader){
	//Check for next available image
	if(this.tmpArray.length > 0){
		var thumb = this.tmpArray.pop();
		thumbLoader.loading = true;
		MN.Event.Observe(thumbLoader, "imageloaded", this.onImageLoaded);
		thumbLoader.update({url:thumb.url});
	}else{
		//If no new images, check all loaders to see if all images have loaded
		var completedLoading = true;
		for(var i=0; i<this.loaders.length; i++){
			if(this.loaders[i].loading){
				completedLoading = false;
				break;
			}
		}
		// If all loaders are complete, loadArray is loaded
		if(completedLoading){
			this.loadArrayLoaded();
		}
	}
}

/**
  * Function: loadArrayLoaded
  * Integrate load array into the timeline array and then set new load
  */
_preview.loadArrayLoaded = function(){
	if(this.timelineArray.length > 0){ // Not do this the first time
		// Insert load array into timeline array
		var offset = 1;
		for(var i=0; i<this.loadArray.length; i++){
			this.timelineArray.splice(i+offset,0,this.loadArray[i]);
			offset++; // Increment to adjust for the new element
		}
		this.thumbsOn = true;
		this.thumb.update({visibility:"visible"});
		this.previewBack.update({visibility:"visible"});
		this.noPreviewBack.update({visibility:"hidden"});
		this.time.update(MN.PlayerStyles.previewTime);
	}else{
		this.timelineArray = this.loadArray;
	}

	this.loadArray = new Array();

	// Still have thumbnails to load
	if(this.timelineArray.length < this.maxLength){
		for(i=0; i<this.timelineArray.length-1; i++){
			var index = Math.ceil(((this.timelineArray[i+1].pos - this.timelineArray[i].pos)/2) + this.timelineArray[i].pos);
			this.loadArray.push(this.referenceArray[index]);
		}
		this.loadThumbs();
	}
}

/**
  * Function: getQMXThumbUrl
  * Returns the current clip's QMX url for thumbnail preview
  *
  * Parameter:
  * pos:Number - QVT position
  * interval:Number - interval of thumbnails
  */
_preview.getQMXThumbUrl = function(pos){
	if (cwPlayer.playingAd) return "";
	
	var clip = this.player.p.qvt.TimelineToClip(pos);

	// Get the correct thumbnail for this position
	var index = clip.startPos //startPos is pos + previous clips duration
	var fileHex = this.convertTimeToThumb(index);

	// Return the correct thumb url
	if(clip.url){
		var url = clip.url.substring(0, clip.url.indexOf("output.qmx"));
		return url + "thumbs/" + fileHex + ".jpg";
	}
	else{
		return "";	
	}
}

/**
  * Function: convertTimeToThumb
  * Converts passed time and interval to hex value for thumbnail file name
  *
  * Parameter:
  * n:Number - time position
  */
_preview.convertTimeToThumb = function(n){
	var interval = this.interval;
	// given n seconds in QMX time, return the thumbnail filename (does not include ".jpg"). Interval is frequency of thumbs available
	n = parseInt(n/interval)+1;
	var num = (n).toString(16).toUpperCase();
	var zeros = "";
	for (var i = 0; i < 8 - num.length; i++){
		zeros += "0";
	}
	num = zeros + num;    
	return num;
}

/**
  * Function: fadeThumb
  * Fade in or out the thumbnail preview
  *
  * Parameter:
  * show:Boolean - flag to fade in or fade out
  */
_preview.fadeThumb = function(show){
	//clearInterval(this.durationInt);
	if(show){
		this.container.update({visibility:"visible"});
		this.container.update({alpha:255});
		//var tween = new MN.Player.Tween(this.container, "alpha", 0, 255, 500, 0, 25, null);
		//this.durationInt = tween;
	}
	else{
		this.container.update({alpha:0});
		//var tween = new MN.Player.Tween(this.container, "alpha", 255, 0, 500, 0, 25, this.finishFade);
		//this.durationInt = tween;
	}
}

/**
  * Function: finishFade
  * Called at end of fade animation for thumbnail preview
  */
_preview.finishFade = function(){
	this.container.update({visibility:"hidden"});
}

/**
  * Function: hideThumb
  * Hide thumb preview immediately
  */
_preview.hideThumb = function(){
	this.container.update({visibility:"hidden", alpha:0});	
}
delete _preview;

/**
 * Class: MN.Player.Tween
 * Tween utility class for tweening overlays
 */
MN.Player.Tween = MN.Class(MN.EventSource);
_tween = MN.Player.Tween.prototype;

/**
  * Function: initialize
  * Initializes a Move Overlay tween
  *
  * Parameter:
  * overlay:Object - overlay object to apply the tween to
  * property:String - property that the tween it applied to
  * start:Number - start value
  * end:Number - end value
  * duration:Number - duration of the tween
  * suffix:String|Number - string to add onto the value (px, %) or 0 for no suffix
  * interval:Number - how often the value is updated during the animation
  * callback:Function - function to call when the tween finishes
  */
_tween.initialize = function(overlay, property, start, end, duration, suffix, interval, callback){
	MN.EventSource.prototype.initialize.apply(this);
	
	this.overlay = overlay;
	this.start = start;
	this.counter = start;
	this.end = end;
	this.suffix = suffix;
	this.callback = callback;
	this.property = property;
	this.step = Math.ceil(Math.abs(end-start)/(duration/interval));
	this.tracker = setInterval(this.updateTween, interval);
}

/**
  * Function: updateTween
  * Update the overlay tween
  */
_tween.updateTween = function(){
	// Increment/Decrement counter
	var prop = {};
	if(this.start > this.end){
		this.counter -= this.step;
		if(this.counter <= this.end){
			clearInterval(this.tracker);
			this.counter = this.end;
			prop[this.property] = this.counter + this.suffix;
			this.overlay.update(prop);
			if(this.callback != null){
				this.callback();
			}
		}
		else{
			prop[this.property] = this.counter + this.suffix;
			this.overlay.update(prop);
		}
		
	}else{
		this.counter += this.step;
		if(this.counter >= this.end){
			clearInterval(this.tracker);
			this.counter = this.end;
			prop[this.property] = this.counter + this.suffix;
			this.overlay.update(prop);
			if(this.callback != null){
				this.callback();
			}
		}
		else{
			prop[this.property] = this.counter + this.suffix;
			this.overlay.update(prop);
		}
	}
}
delete _tween;