// dvr.js - Convenience class to handle DVR-like controls for a Move player object
// Copyright (c) 2007 Move Networks
// Provides a wrapper interface between the HTML controls on a page to talk to and be
// updated by a QVT player object. This class requires that you use the qvt.js version of
// CreatePlayer() to return the object that DVR uses. The developers responsibility is to
// setup a page of elements with a consistent prefix plus predefined suffixes and then
// initialize this class with the options that tell it what to handle.
//
// The following is a list of all the named parameters that are supported, what controls that
// option handles, the suffix of the element's ID, and additional comments for the feature.
//
// Option "playPause":true
// Play/Pause 		_playpause	(This requires two CSS classes to be defined, "mn_play" and "mn_pause",
//						 		which are applied to the element based on that status of the player.
//								See "playPrefix" option for additional control to class names.)
//
// Option "fwdRwd":true
// Rewind			_rew
// Fast Forward 	_fwd
//
// Option "prevNext":true			
// Previous			_prev
// Next				_next
//
// Option "scrub":true
// Slider Track		_track		(In addition, this element requires a child element that is the thumb
//								control that moves and can be dragged)
//
// Option "position":true
// Position/Length	_position
//
// Option "playState":true
// Player Status	_status
//
// Option "volume":true
// Volume			_volume
//
// Option "size":true
// Sizing			_size		(This requires two classes to be defined, "mn_sizemax" and "mn_sizemin"
//								which are applied to the element based on which mode is active)
//
// Option "live":true
// Go Live			_live
//
// Option "handleScrub":true	(Whether we should change position after user moves timeline scrubber or
//								fire a 'StopScrub' event to be handled outside this object)
//
// Option "playPrefix":"foo"	(When it is necessary to define a different class name for the playPause button,
//								setting this option makes the code look for "fooplay" and "foopause" instead)
//
// An example variable to be passed to the class on creation to enable every feature would look like this:
// var options = {"playState":true,"position":true,"bitrate":true,"playPause":true,"fwdRwd":true,"prevNext":true,
//				  "scrub":true,"volume":true,"size":true,"live":true,"handleScrub":true,"playPrefix":"foo"};

MN.Widget.DVR = MN.Class(MN.EventSource);
_mndvr = MN.Widget.DVR.prototype;

// Creates the object and sets up all the listeners and intervals for the DVR controls
// prefix - The string that precedes every element's ID e.g. "plyr1" for "plyr1_playpause"
// player - QVT wrapper object retrieved after calling CreatePlayer()
// url - Optional absolute address to QMX or QVT Move stream, pass null if you want to set later
// options - JSON object of which features to enable as described above
// autoplay - Optional true/false of whether or not to start playback immediately, default = false
// playfrom - Optional number value of seconds into clip to start playback from, requires autoplay be true
_mndvr.initialize = function(prefix, player, url, options, autoplay, playfrom)
{
	MN.EventSource.prototype.initialize.apply(this);
	this._qmp = player;
	this.player = player;
	this._slider = null;
	this._sliderSteps = 300;
	this._scrubbing = false;
	this._scrubbingvolume = false;
	this._seeking = false;
	this._scrubspeed = 0;
	this._curpos = 0;
	this._playstate = 0;
	this._bitrate = 0;
	this._options = options;
	this._prefix = prefix;
	this._url = url;
	this._posIntvl = null;
	this._seekIntvl = null;
	this._volIntvl = null;
	this._sizemax = false;
	
	if (autoplay==null) autoplay = false;
	
	if (url!=null)
	{
		if (autoplay && playfrom) this._qmp.Play(url,playfrom);
		else if (autoplay) this._qmp.Play(url);
		else this._qmp.Load(url);
	}
	
	MN.Event.Observe(this._qmp,"PlayStateChanged",this.OnPSChanged);

	if (this._options.position)
	{
		this._posIntvl = setInterval(this.UpdatePosition,1000);
	}
	MN.Event.Observe(this._qmp,"BitRateChanged",this.OnBRChanged);
	if (this._options.playPause)
	{
		MN.Event.Observe(this._qmp,"PausedChanged", this.TogglePlayPause);
		MN.Event.Observe(this._prefix + "_playpause","click",this.OnClickPlayPause);
	}
	if (this._options.fwdRwd) 
	{
		MN.Event.Observe(this._prefix + "_rew","click",this.OnClickRewind);
		MN.Event.Observe(this._prefix + "_fwd","click",this.OnClickForward);
	}
	if (this._options.scrub) 
	{
		this._slider = new MN.Widget.HSlider(this._prefix + "_track",0,this._sliderSteps);
		MN.Event.Observe(this._slider,"ValueChanged",this.OnMoveScrubber);
		MN.Event.Observe(this._slider,"SlideStart",this.OnStartScrub);
		MN.Event.Observe(this._slider,"SlideStop",this.OnStopScrub);
		this._seekIntvl = setInterval(this.UpdateScrubber,500);
	}
	if (this._options.prevNext) 
	{
		MN.Event.Observe(this._prefix + "_prev","click",this.OnClickPrevious);
		MN.Event.Observe(this._prefix + "_next","click",this.OnClickNext);
	}
	// Keep track of volume no matter what in case option is activated later
	this._volumeVal = this._qmp.Volume();
	if (this._options.volume) 
	{
		this._volume = new MN.Widget.HSlider(this._prefix + "_volume",1,100);
		MN.Event.Observe(this._volume,"ValueChanged",this.OnVolumeChange);
		MN.Event.Observe(this._volume,"SlideStart",this.OnStartVolumeScrub);
		MN.Event.Observe(this._volume,"SlideStop",this.OnStopVolumeScrub);
		this._volume.Value(this._volumeVal);
	}
	this._volIntvl = setInterval(this.UpdateVolumeScrubber,1000);
	if (this._options.size)
	{
		MN.Event.Observe(this._prefix + "_size","click",this.OnClickSize);
	}
	if (this._options.live)
	{
		MN.Event.Observe(this._prefix + "_live","click",this.OnClickLive);
	}
}
// Function attempts to unlink and clean up all of the intervals created should you want to kill
// the DVR object. 
_mndvr.Destroy = function()
{
	MN.Event.StopObserving(this._qmp,"PlayStateChanged",this.OnPSChanged);

	if (this._options.position)
	{
		clearInterval(this._posIntvl);
	}
	if (this._options.bitrate)
	{
		MN.Event.StopObserving(this._qmp,"BitRateChanged",this.OnBRChanged);
	}
	if (this._options.playPause)
	{
		MN.Event.StopObserving(this._qmp,"PausedChanged", this.TogglePlayPause);
		MN.Event.StopObserving(this._prefix + "_playpause","click",this.OnClickPlayPause);
	}
	if (this._options.fwdRwd) 
	{
		MN.Event.StopObserving(this._prefix + "_rew","click",this.OnClickRewind);
		MN.Event.StopObserving(this._prefix + "_fwd","click",this.OnClickForward);
	}
	if (this._options.scrub) 
	{
		MN.Event.StopObserving(this._slider,"ValueChanged",this.OnMoveScrubber);
		MN.Event.StopObserving(this._slider,"SlideStart",this.OnStartScrub);
		MN.Event.StopObserving(this._slider,"SlideStop",this.OnStopScrub);
		clearInterval(this._seekIntvl);
		// Currently this Destroy call invokes the function declared in this file, yet still does not completely
		// stop the mouse handlers correctly from the objects they are watching. Results are erratic until this can be fixed
		this._slider.Destroy();
		this._slider = null;
	}
	if (this._options.prevNext) 
	{
		MN.Event.StopObserving(this._prefix + "_prev","click",this.OnClickPrevious);
		MN.Event.StopObserving(this._prefix + "_next","click",this.OnClickNext);
	}
	clearInterval(this._volIntvl);
	if (this._options.volume) 
	{
		MN.Event.StopObserving(this._volume,"ValueChanged",this.OnVolumeChange);
		MN.Event.StopObserving(this._volume,"SlideStart",this.OnStartVolumeScrub);
		MN.Event.StopObserving(this._volume,"SlideStop",this.OnStopVolumeScrub);
		// Currently this Destroy call invokes the function declared in this file, yet still does not completely
		// stop the mouse handlers correctly from the objects they are watching. Results are erratic until this can be fixed
		this._volume.Destroy();
		this._volume = null;
	}
	if (this._options.size)
	{
		MN.Event.StopObserving(this._prefix + "_size","click",this.OnClickSize);
	}
	if (this._options.live)
	{
		MN.Event.StopObserving(this._prefix + "_live","click",this.OnClickLive);
	}
	
	this._qmp = null;
	this.player = null;
}
// Wrapper to _qmp.Play function so we can keep track of current playing URL and check if it is already loaded. Calls Play
// normally if it isn't, but just sets the current position otherwise to avoid reloading stream
_mndvr.Play = function(url,start,end)
{
	if (url!=null)
	{
		this._url = url;
	}
	if (this._qmp.CurrentQVT() == null || this._url != this._qmp.CurrentQVT().qvtURL)
	{
		this._qmp.Play(url,start,end);
	}
	else
	{
		if (this._playstate!=MN.QMP.PS.PLAYING)
			this._qmp.Play();
		if (start != null)
			this._qmp.CurrentPosition(start);
	}
}
// Moves scubber thumb on HSlider widget's track based on position
_mndvr.UpdateScrubber = function()
{
	if (this._qmp.Paused()) return;
	var pct = (this._qmp.CurrentPosition()/this._qmp.Duration()) * this._sliderSteps;
	if (!isNaN(pct)) this._slider.Value(pct);
}
// Updates text in position element to reflect current position and length of media
_mndvr.UpdatePosition = function()
{
	if (this._seeking)
	{
		var pos = parseInt(this._qmp.CurrentPosition(),10);
		if (pos==0)
		{
			this._qmp.StopScrubbing();
			this._scrubspeed = 0;
			this._seeking = false;
			this.UpdatePSDisplay();
		}
	}
	if (!this._scrubbing)
	{
		var val = MN.ConvertToTimestamp(this._qmp.CurrentPosition()) + " / " + MN.ConvertToTimestamp(this._qmp.Duration());
		MN.SetInnerText($(this._prefix + "_position"),val);
	}
	this._curpos = this._qmp.CurrentPosition();
}
// Displays the position of the seek time while moving the timeline scrubber
_mndvr.OnMoveScrubber = function(value)
{
	if (this._options.position)
	{
		var pos = this._qmp.Duration() * (value/this._sliderSteps);
		MN.SetInnerText($(this._prefix + "_position"),MN.ConvertToTimestamp(pos) + " / " + MN.ConvertToTimestamp(this._qmp.Duration()));
	}
}
_mndvr.OnStartScrub = function()
{
	this._qmp.Paused(true);
	this._scrubbing = true;
}
// When the user lets go of scrubber, gets the position they requested and either sets the
// player's position or fires the 'StopScrub' event to be handled outside of the object
// to do things such as display advertisments or other abitrary code and then set the position later
_mndvr.OnStopScrub = function()
{
	this._qmp.Paused(false);
	this._scrubbing = false;
	var pos = this._qmp.Duration() * (this._slider.Value()/this._sliderSteps);
	if (this._options.handleScrub==null||this._options.handleScrub)
	{
		this._qmp.CurrentPosition(pos);
	}
	else
	{
		this.FireEvent("StopScrub", pos);
	}
}
_mndvr.UpdateVolumeScrubber = function()
{
	if (!this._scrubbingvolume)
	{
		this._volumeVal = this._qmp.Volume();
		if (this._options.volume) 
		{
			this._volume.Value(this._volumeVal);
		}
	}
}
_mndvr.OnStartVolumeScrub = function()
{
	this._scrubbingvolume = true;
}
_mndvr.OnStopVolumeScrub = function()
{
	this._scrubbingvolume = false;
}
_mndvr.OnPSChanged = function(oldS, newS)
{
	this._playstate = newS;
	this.UpdatePSDisplay();
}
// Look up the _playstate value in the global PS string table and display to user and update the
// Play/Pause button to reflect the correct function
_mndvr.UpdatePSDisplay = function()
{
	if (this._options.playState) 
	{
		var stateStr = MN.QMP.PS[this._playstate];
		MN.SetInnerText($(this._prefix + "_status"),stateStr);
	}
	if (this._options.playPause)
	{
		// Commented out because was told button should not display Play when stream is stalled
		// if (this._playstate != MN.QMP.PS.PLAYING) this.TogglePlayPause(true);
		// else 
		this.TogglePlayPause();
	}
}
_mndvr.OnBRChanged = function(newBR)
{
	this._bitrate = newBR;
	this.UpdateBRDisplay();
}
_mndvr.UpdateBRDisplay = function()
{
	if (this._options.bitrate && !isNaN(this._bitrate))
	{
		MN.SetInnerText($(this._prefix + "_bitrate"),this._bitrate + "Kbps");
	}
}
_mndvr.Pause = function(b)
{
	if (this._playstate==MN.QMP.PS.PLAYING)
	{
		var paused = (b) ? !b : this._qmp.Paused();
		this._qmp.Paused(!paused);
		if (this._options.playState) 
		{
			MN.SetInnerText($(this._prefix + "_status"),(paused)?"Playing":"Paused");
		}
	}
}
// Handler for "*_playpause" element to either put or bring the media into a paused state. If the user was seeking,
// cancel and begin playing again.
_mndvr.OnClickPlayPause = function()
{
	if (this._playstate==MN.QMP.PS.PLAYING)
	{
		if (this._seeking)
		{
			this._qmp.StopScrubbing();
			this._scrubspeed = 0;
			this._seeking = false;
			this.TogglePlayPause(false);
			if (this._options.playState) 
			{
				MN.SetInnerText($(this._prefix + "_status"),MN.QMP.PS[3]);
			}
			return;
		}
		this.Pause();
	}
	else
	{
		var pos = (Math.ceil(this._curpos)==this._qmp.Duration())?0:this._curpos;
		this._qmp.CurrentPosition(pos);
	}
}
// This function takes an optional boolean parameter to change the class of the "*_playpause" element to either
// the play or pause display state. If you don't pass a value, it checks the current Paused() value of the player.
// If paused equals true, the "mn_pause" CSS class is applied to the element, otherwise the "mn_play" is.
// When the playPrefix option is set, the "mn_" part is replaced by the defined string.
_mndvr.TogglePlayPause = function(paused)
{
	if (paused==null)
	{
		paused = this._qmp.Paused();
	}
	if (this._options.playPause)
	{
		var playClass = (this._options.playPrefix)?"%splay".format(this._options.playPrefix):"mn_play";
		var pauseClass = (this._options.playPrefix)?"%spause".format(this._options.playPrefix):"mn_pause";
		MN.CSS.RemoveClass(this._prefix + "_playpause",(!paused)?playClass:pauseClass);
		MN.CSS.AddClass(this._prefix + "_playpause",(!paused)?pauseClass:playClass);
	}
}
// These next three functions are the handlers for the seeking functionality
_mndvr.OnClickRewind = function()
{
	this.DoSeek(false);
}
_mndvr.OnClickForward = function()
{
	if (this._qmp.Paused())
	{
		this._qmp.SingleStep();
		return;
	}
	this.DoSeek(true);
}
_mndvr.DoSeek = function(fwd)
{
	if ((this._scrubspeed < 0 && fwd) || (this._scrubspeed > 0 && !fwd)) this._scrubspeed = 0;
	if (this._scrubspeed==0)
		this._scrubspeed = 2;
	else if (Math.abs(this._scrubspeed)==16)
		return;
	else
		this._scrubspeed *= 2;
	this._scrubspeed = (fwd||this._scrubspeed<0)?this._scrubspeed:this._scrubspeed*-1;
	if (this._qmp.Scrub(this._scrubspeed))
	{
		this._seeking = true;
		this.TogglePlayPause(true);
		if (this._options.playState) 
		{
			var stateStr = "Seeking " + this._scrubspeed + "X";
			MN.SetInnerText($(this._prefix + "_status"),stateStr);
		}
	}
	else
	{
		this._qmp.StopScrubbing();
		this.TogglePlayPause(false);
		this._scrubspeed = 0;
	}
}
// These two functions are the handlers for skipping to the previous or next show in a QVT timeline.
// Nothing happens if there is not a show to skip to.
_mndvr.OnClickPrevious = function()
{
	var qvt = this._qmp.CurrentQVT();
	var showcount = qvt.ShowCount();
	if (showcount > 1)
	{
		var shownum = this._qmp.CurrentShow();
		if (shownum > 0)
		{
			var gotoShow = shownum;
			if ((this._qmp.CurrentPosition() - qvt.StartTime(shownum)) <= 2)
			{
				gotoShow--;
			}
			this._qmp.CurrentShow(gotoShow);
		}
	}
}
_mndvr.OnClickNext = function()
{
	var qvt = this._qmp.CurrentQVT();
	var showcount = qvt.ShowCount();
	if (showcount > 1)
	{
		var shownum = this._qmp.CurrentShow();
		if (shownum < showcount-1)
		{
			this._qmp.CurrentShow(shownum+1);
		}
	}
}
_mndvr.OnVolumeChange = function(value)
{
	this._volumeVal = value;
	if (value==0)
	{
		this._qmp.Muted(true);
	}
	else
	{
		this._qmp.Muted(false);
		this._qmp.Volume(value);
	}
}
// Handle the click of the sizing button, change the class of the control, and fire an event that can
// be caught by an outside handler to move and/or size the player container
_mndvr.OnClickSize = function()
{
	MN.CSS.RemoveClass(this._prefix + "_size",(!this._sizemax)?"mn_sizemax":"mn_sizemin");
	MN.CSS.AddClass(this._prefix + "_size",(!this._sizemax)?"mn_sizemin":"mn_sizemax");
	this._sizemax = (!this._sizemax);
	this.FireEvent("PlayerSized", this._sizemax);
}
// Handle the click of the live button and set the current position to -1, which when the stream is live,
// moves to the latest available streamlet
_mndvr.OnClickLive = function()
{
	this._qmp.CurrentPosition(-1);
}
_mndvr.CurrentURL = function() { return this._url; }
_mndvr.Player = function() { return this._qmp; }

delete _mndvr;

// These functions are an attempt to extend the MN classes to remove the handlers watching the mouse events on a widget.
// Currently does not work and needs to be improved. This is to stop the sliders from incorrectly handling the thumb
// element after the DVR object is destroyed and a new one tries to watch the same slider.
MN.Widget.RemoveMouseHandlers = function(elOrID)
{
	wrappedMoveHandler = function(e){}
	wrappedUpHandler = function(e){}
	wrappedDownHandler = function(e){}
	MN.Event.StopObserving(document, 'mousemove', wrappedMoveHandler);
	MN.Event.StopObserving(document, 'mouseup', wrappedUpHandler);
	MN.Event.StopObserving($(elOrID), 'mousedown', wrappedDownHandler);
}
MN.Widget.HSlider.prototype.Destroy = function()
{
	MN.Widget.RemoveMouseHandlers(this.track);
}