// @ts-check
// Fusion Research Driver - Music Module - Queue Management
//
// Copyright 2013-2021 Fusion Research, Inc.
// by Alexa Jade Fanti
//
// Comments:
//   This contains objects and data structures to support player queue management.
//
// Depends: 
//   fusion_stringutils.js
//   fusion_misc.js
//   fusion_debugging.js
//   fusion_sys_config.js
//   fusion_http.js
//   fusion_url.js
//   fusion_server.js
//
//System.Print("Fusion Driver - Music Module - Queue Management loaded...\r\n");


/******************************************************************
 * START Queue Entry Object
 ******************************************************************/
function QueueEntry(index, 
					id, 
					track, 
					artist, 
					album, 
					genre, 
					duration, 
					art, 
					path, 
					remote,
					can_rate)
{
	this.Initialize();

	if (index    !== undefined) try { this.index    = parseInt(index, 10);     } catch(err) { }
	if (id       !== undefined) try { this.id       = index.toString();        } catch(err) { }
		
//	if (track    !== undefined) try { this.track    = track.toString();        } catch(err) { }
//	if (artist   !== undefined) try { this.artist   = artist.toString();       } catch(err) { }
//	if (album    !== undefined) try { this.album    = album.toString();        } catch(err) { }
//	if (genre    !== undefined) try { this.genre    = genre.toString();        } catch(err) { }
	if (track    !== undefined) try { this.track    = System.ConvertFromUTF8(track.toString());        } catch(err) { }
	if (artist   !== undefined) try { this.artist   = System.ConvertFromUTF8(artist.toString());       } catch(err) { }
	if (album    !== undefined) try { this.album    = System.ConvertFromUTF8(album.toString());        } catch(err) { }
	if (genre    !== undefined) try { this.genre    = System.ConvertFromUTF8(genre.toString());        } catch(err) { }

	if (duration !== undefined) try { this.duration = parseInt(duration, 10);  } catch(err) { }
	if (art      !== undefined) try { this.art      = art.toString();          } catch(err) { }
//	if (path     !== undefined) try { this.path     = path.toString();         } catch(err) { }
	if (path     !== undefined) try { this.path     = System.ConvertFromUTF8(path.toString());         } catch(err) { }

	if (remote   !== undefined) try { this.remote   = parseBool(remote);       } catch(err) { }
	if (can_rate !== undefined) try { this.can_rate = parseBool(can_rate);     } catch(err) { }
}

QueueEntry.prototype.Initialize = function()
{
	this.index     = -1;
    this.id        = "";
	this.track     = "";
	this.artist    = "";
	this.album     = "";
	this.genre     = "";
	this.duration  = 0;
    this.art       = "";
	this.path      = "";
	this.remote    = false;
	this.can_rate  = false;
};

QueueEntry.prototype.sameAs = function(another)
{
    if ((another === undefined) || (another == null)) return false;
    if (!(another instanceof QueueEntry)) return false;
    
    if (this.index    != another.index) return false;
    if (this.id       != another.id) return false;
    if (this.track    != another.track) return false;
    if (this.artist   != another.artist) return false;
    if (this.album    != another.album) return false;
    if (this.genre    != another.genre) return false;
    if (this.duration != another.duration) return false;
    if (this.art      != another.art) return false;
    if (this.path     != another.path) return false;
    if (this.remote   != another.remote) return false;
    if (this.can_rate != another.can_rate) return false;
    
    return true;
};
/******************************************************************
 * END   Queue Entry Object
 ******************************************************************/


/******************************************************************
 * START Queue Object
 ******************************************************************/
function Queue(owningServer, playerMac, playerIndex)
{
	this._owner = owningServer;
	this._playerMac = playerMac;
	this._playerIndex = parseInt(playerIndex, 10); // This is the zero based index of the player in the integrator's configuration


	// if (!isNaN(this._playerIndex)) {
	// 	// Set up the list variable!
	// 	this._rti_list = new SystemVarsList("SysVar_Player" + this._playerIndex + "_L_Queue_Entries");
	// } else this._rti_list = undefined;

	this._rti_lists = new Array();
	// I need an array of list for every view...
	// This is NOT a real array, but a sparse array more like a map.
	if (!isNaN(this._playerIndex)) {
		// Set up the list variable!
		var viewIDs = this._owner.view_player_map.GetAllViews();
		for (var x = 0; x < viewIDs.length; x++) {
			this._rti_lists[parseInt(viewIDs[x], 10)] = new SystemVarsList("SysVar_Player" + this._playerIndex + "_L_Queue_Entries%" + viewIDs[x]);
		}
		DebugQueue(this.getID() + " has " + this._rti_lists.length + " lists", g_DEBUG_LEVEL_TRACE);
	}

    this._timer_fetch = new Timer(); // Used to delay fetching for a little bit!

	this.Initialize(); // Other stuff has to be set up first!
}

Queue.prototype.Initialize = function()
{
	this.recordPath(); // Always report this first as other things depend on it!
	this.recordIndex();
	this.recordCount();
	this.recordTrack();
	this.recordArtist();
	this.recordAlbum();
	this.recordGenre();
	//this.recordTitle();
	this.recordPositionAndDuration();
	this.recordArt();
	this.recordSource();
	this.recordStation();
	this.recordPower();
	this.recordTransport();
	this.recordVolume();
	this.recordMute();
	this.recordShuffle();
	this.recordRepeat();
	this.recordEntries();
    this.recordCanRate();
	this.entries = new Array();
};

Queue.prototype.getID = function()
{
	return "Queue (" + this._playerIndex + ":" + this._playerMac + ")";
}

Queue.prototype.toString = function()
{
	return this._playerMac + " [" + this._playerIndex + "] @ " + OperationalServerIP() + this.transport + " (" + this.index + "/" + this.count + ")";
}

// Return true if we are currently listening to Airplay!
Queue.prototype.IsNow_Airplay = function()
{
	return this.path.startsWith('airplay://');
}


//==============> START  Reporting Methods
// Reporting methods are responsible for updating the RTI System Variables and Events 
// as things change for the Queue object. They report what has already been set elsewhere.
// This means we need to "record" things BEFORE "reporting" them.
// These handle reporting for the specific player/queue (by index) as well 
// as reporting to ANY RTI System Views the player/queue may be selected for.

Queue.prototype.ReportIndex_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportIndex_ToView : Index = " + this.index + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);

		SysVar_WriteInt("SysVar_PlayerS_I_Queue_Index%" + viewID, this.index);

		if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
			list = this._owner.GetQueueListForView(viewID);
			if (list !== undefined) {
				try {
					list.Open();
					list.SetMarked(this.index);
					list.Close();
				} catch (err) {
					DebugQueue(this.getID() + " ReportIndex_ToView failed to update list with error = " + err, g_DEBUG_LEVEL_ERROR);
				}
			}
		}
	} else {
		DebugQueue(this.getID() + " ReportIndex_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportIndex = function()
{
	DebugQueue(this.getID() + " ReportIndex : Index = " + this.index + " (main)", g_DEBUG_LEVEL_TRACE);

	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Queue_Index", this.index);
	try {
 		var viewIDs = this._owner.view_player_map.GetAllViews();
	 	for (var y = 0; y < viewIDs.length; y++) {
			var list = this._owner.GetQueueListForView(viewIDs[y]);
			list.Open();
			list.SetMarked(this.index);
			list.Close();
		}
	} catch (err) {
		DebugQueue(this.getID() + " ReportIndex failed to update list with error = " + err, g_DEBUG_LEVEL_ERROR);
	}

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportIndex_ToView(viewIDs[x]);
};

Queue.prototype.ReportCount_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportCount_ToView : Count = " + this.count + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteInt("SysVar_PlayerS_I_Queue_Count%" + viewID, this.count);
		var can_prev = this.count > 1;
		var can_next = this.count > 1;

		// Because we need to allow Pandora (if it's the current thing) to next!
		var isPandora = false;
		if ((this.path !== undefined) && (this.path != null)) {
			isPandora = this.path.startsWith('frpandora');
		}
		can_next = ((this.count > 1) || (isPandora));

		SysVar_WriteBool("SysVar_PlayerS_B_Transport_CanPrev%" + viewID, can_prev);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_CanNext%" + viewID, can_next);
	} else {
		DebugQueue(this.getID() + " ReportCount_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportCount = function()
{
	DebugQueue(this.getID() + " ReportCount : Count = " + this.count + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Queue_Count", this.count);

	var can_prev = this.count > 1;
	var can_next = this.count > 1;

	// Because we need to allow Pandora (if it's the current thing) to next!
	var isPandora = false;
	if ((this.path !== undefined) && (this.path != null)) {
		isPandora = this.path.startsWith('frpandora');
	}
	can_next = ((this.count > 1) || (isPandora));

	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_CanPrev", can_prev);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_CanNext", can_next);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportCount_ToView(viewIDs[x]);
};

// Test these -\/-
Queue.prototype.ReportTrack_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportTrack_ToView : Track = " + this.track + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Track%" + viewID, this.track);
	} else {
		DebugQueue(this.getID() + " ReportTrack_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportTrack = function()
{
	DebugQueue(this.getID() + " ReportTrack : Track = " + this.track + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Track", this.track);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportTrack_ToView(viewIDs[x]);
};

Queue.prototype.ReportArtist_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportArtist_ToView : Artist = " + this.artist + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Artist%" + viewID, this.artist);
	} else {
		DebugQueue(this.getID() + " ReportArtist_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportArtist = function()
{
	DebugQueue(this.getID() + " ReportArtist : Artist = " + this.artist + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Artist", this.artist);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportArtist_ToView(viewIDs[x]);
};

Queue.prototype.ReportAlbum_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportAlbum_ToView : Album = " + this.album + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Album%" + viewID, this.album);
	} else {
		DebugQueue(this.getID() + " ReportAlbum_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportAlbum = function()
{
	DebugQueue(this.getID() + " ReportAlbum : Album = " + this.album + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Album", this.album);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportAlbum_ToView(viewIDs[x]);
};

Queue.prototype.ReportGenre_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportGenre_ToView : Genre = " + this.genre + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Genre%" + viewID, this.genre);
	} else {
		DebugQueue(this.getID() + " ReportGenre_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportGenre = function()
{
	DebugQueue(this.getID() + " ReportGenre : Genre = " + this.genre + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Genre", this.genre);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportGenre_ToView(viewIDs[x]);
};

Queue.prototype.ReportPath_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportPath_ToView : Path = " + this.path + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Path%" + viewID, this.path);
	} else {
		DebugQueue(this.getID() + " ReportPath_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportPath = function()
{
	DebugQueue(this.getID() + " ReportPath : Path = " + this.path + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Path", this.path);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportPath_ToView(viewIDs[x]);
};

Queue.prototype.ReportSource_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportSource_ToView : Source = " + this.source + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Source%" + viewID, this.source);
		SysEvent_Signal_Player("SourceChange", this._playerIndex);

// This shit gets deleted after Neal doesn't need it any more!!! 2022-12-29
		var sourceId = Library_TopLevelServiceID(this.source);
		switch (sourceId) {
			case g_LIBRARY_SERVICE_ID_MY_MUSIC:	      SysEvent_Signal_Player("SourceChange_MyMusic", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_INTERNET_RADIO: SysEvent_Signal_Player("SourceChange_InternetRadio", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_FAVORITES: 	  SysEvent_Signal_Player("SourceChange_Favorites", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_AIRPLAY_2: 	  SysEvent_Signal_Player("SourceChange_Airplay2", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_BBC_SOUNDS:	  SysEvent_Signal_Player("SourceChange_BBCSounds", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_IHEARTRADIO:    SysEvent_Signal_Player("SourceChange_iHeartRadio", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_PANDORA: 	      SysEvent_Signal_Player("SourceChange_Pandora", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_QOBUZ:  	      SysEvent_Signal_Player("SourceChange_Qobuz", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_SIRIUSXM: 	  SysEvent_Signal_Player("SourceChange_SiriusXM", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_SOUNDMACHINE:   SysEvent_Signal_Player("SourceChange_SoundMachine", this._playerIndex);
			case g_LIBRARY_SERVICE_ID_SPOTIFY:		  SysEvent_Signal_Player("SourceChange_Spotify", this._playerIndex);
		}
// End of shit!
		
	} else {
		DebugQueue(this.getID() + " ReportSource_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportSource = function()
{
	DebugQueue(this.getID() + " ReportSource : Source = " + this.source + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Source", this.source);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportSource_ToView(viewIDs[x]);
};

Queue.prototype.ReportStation_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportStation_ToView : Station = " + this.station + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_Write("SysVar_PlayerS_S_NowPlaying_Station%" + viewID, this.station);
	} else {
		DebugQueue(this.getID() + " ReportStation_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportStation = function()
{
	DebugQueue(this.getID() + " ReportStation : Station = " + this.station + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_NowPlaying_Station", this.station);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportStation_ToView(viewIDs[x]);
};

Queue.prototype.ReportPosition_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueueProgress(this.getID() + " ReportPosition_ToView : Position = " + this.position + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteInt("SysVar_PlayerS_I_Transport_Position%" + viewID, this.position);
		SysVar_Write("SysVar_PlayerS_S_Transport_Position_AsString%" + viewID, SecondsToPrettyString_HMMSS(this.position));

		this.ReportProgress_ToView(viewID);
	} else {
		DebugQueue(this.getID() + " ReportPosition_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportPosition = function()
{
	DebugQueueProgress(this.getID() + " ReportPosition : Position = " + this.position + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Position", this.position);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_Transport_Position_AsString", SecondsToPrettyString_HMMSS(this.position));

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportPosition_ToView(viewIDs[x]);

	this.ReportProgress();
};

Queue.prototype.ReportDuration_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueueProgress(this.getID() + " ReportDuration_ToView : Duration = " + this.duration + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteInt("SysVar_PlayerS_I_Transport_Duration%" + viewID, this.duration);
		SysVar_Write("SysVar_PlayerS_S_Transport_Duration_AsString%" + viewID, SecondsToPrettyString_HMMSS(this.duration));

		this.ReportProgress_ToView(viewID);
	} else {
		DebugQueue(this.getID() + " ReportDuration_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportDuration = function()
{
	DebugQueueProgress(this.getID() + " ReportDuration : Duration = " + this.duration + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Duration", this.duration);
	SysVar_Write("SysVar_Player" + this._playerIndex + "_S_Transport_Duration_AsString", SecondsToPrettyString_HMMSS(this.duration));

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportDuration_ToView(viewIDs[x]);

	this.ReportProgress();
};

Queue.prototype.CalculateProgress = function()
{
	var progress = 0;

	try { 
		if (this.position <= this.duration) {
			progress = Math.floor((this.position * 100) / this.duration);
		}
	} catch(err) {
		DebugQueueProgress("CalculateProgress Error = " + err, g_DEBUG_LEVEL_ERROR);
	}

	// Ensure the value is reasonable...
	if ((progress === undefined) || (progress === null)) progress = 0;
	if (isNaN(progress)) progress = 0;
	if (progress == Infinity) progress = 0;
	if (progress == -Infinity) progress = 0;

	return progress;
}

Queue.prototype.ReportProgress_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueueProgress(this.getID() + " ReportProgress_ToView : Progress = " + this.CalculateProgress() + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteInt("SysVar_PlayerS_I_Transport_Progress%" + viewID, this.CalculateProgress());
	} else {
		DebugQueueProgress(this.getID() + " ReportProgress_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportProgress = function()
{
	DebugQueueProgress(this.getID() + " ReportProgress : Progress = " + this.CalculateProgress() + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Progress", this.CalculateProgress());

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportProgress_ToView(viewIDs[x]);
};


Queue.prototype.ReportArt_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportArt_ToView : Art = " + this.art + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteArt("SysVar_PlayerS_A_NowPlaying_Art%" + viewID, this.art);
		SysVar_WriteArt("SysVar_PlayerS_A_NowPlaying_Art2%" + viewID, this.art);
	} else {
		DebugQueue(this.getID() + " ReportArt_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportArt = function()
{
	DebugQueue(this.getID() + " ReportArt : Progress = " + this.art + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteArt("SysVar_Player" + this._playerIndex + "_A_NowPlaying_Art", this.art);
	SysVar_WriteArt("SysVar_Player" + this._playerIndex + "_A_NowPlaying_Art2", this.art);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportArt_ToView(viewIDs[x]);
};

Queue.prototype.ReportCanRate_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportCanRate_ToView : CanRate = " + this.can_rate + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteBool("SysVar_PlayerS_B_NowPlaying_CanRate%" + viewID, this.can_rate);
		SysVar_WriteBool("SysVar_PlayerS_B_NowPlaying_CanRatePositive%" + viewID, this.can_rate);
		SysVar_WriteBool("SysVar_PlayerS_B_NowPlaying_CanRateNegative%" + viewID, this.can_rate);
	} else {
		DebugQueue(this.getID() + " ReportCanRate_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportCanRate = function()
{
	DebugQueue(this.getID() + " ReportCanRate : CanRate = " + this.can_rate + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_NowPlaying_CanRate", this.can_rate);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_NowPlaying_CanRatePositive", this.can_rate);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_NowPlaying_CanRateNegative", this.can_rate);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportCanRate_ToView(viewIDs[x]);
};

Queue.prototype.ReportPower_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportPower_ToView : Power = " + this.power + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteBool("SysVar_PlayerS_B_Power%" + viewID, this.power);
	} else {
		DebugQueue(this.getID() + " ReportPower_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportPower = function()
{
	DebugQueue(this.getID() + " ReportPower : Power = " + this.power + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Power", this.power);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportPower_ToView(viewIDs[x]);
};


// Report Transport Status...
Queue.prototype.ReportTransport_ToView = function(viewID)
{ // Format: L:0:Stop:1:Pause:2:Play
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportTransport_ToView : Transport = " + this.transport + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		var is_playing = false;
		var is_paused  = false;
		var is_stopped = false;
		if (this.transport == "play") {
			is_playing = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport%" + viewID, 2);
		} else
		if (this.transport == "pause") {
			is_paused = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport%" + viewID, 1);
		} else
		if (this.transport == "stop") {
			is_stopped = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport%" + viewID, 0);
		} else {
			is_stopped = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport%" + viewID, 0);
		}
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Playing%" + viewID,    is_playing);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_NotPlaying%" + viewID, !is_playing);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Paused%" + viewID,     is_paused);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Stopped%" + viewID,    is_stopped);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_CanPlay%" + viewID,    !is_playing);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_CanPause%" + viewID,   !is_paused);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_CanStop%" + viewID,    !is_stopped);
	} else {
		DebugQueue(this.getID() + " ReportTransport_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportTransport = function()
{ // Format: L:0:Stop:1:Pause:2:Play
	DebugQueue(this.getID() + " ReportTransport : Transport = " + this.transport + " (main)", g_DEBUG_LEVEL_TRACE);
	var is_playing = false;
	var is_paused  = false;
	var is_stopped = false;
	if (this.transport == "play") {
		is_playing = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport", 2);
		SysEvent_Signal_Player("TransportPlay", this._playerIndex);
	} else
	if (this.transport == "pause") {
		is_paused = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport", 1);
		SysEvent_Signal_Player("TransportPause", this._playerIndex);
	} else
	if (this.transport == "stop") {
		is_stopped = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport", 0);
		SysEvent_Signal_Player("TransportStop", this._playerIndex);
	} else {
		is_stopped = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport", 0);
		SysEvent_Signal_Player("TransportStop", this._playerIndex);
	}
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Playing",    is_playing);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_NotPlaying", !is_playing);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Paused",     is_paused);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Stopped",    is_stopped);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_CanPlay",    !is_playing);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_CanPause",   !is_paused);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_CanStop",    !is_stopped);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportTransport_ToView(viewIDs[x]);
};

Queue.prototype.ReportVolume_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportVolume_ToView : Volume = " + this.volume + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteInt("SysVar_PlayerS_I_Transport_Volume%" + viewID, this.volume);
	} else {
		DebugQueue(this.getID() + " ReportVolume_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportVolume = function()
{
	DebugQueue(this.getID() + " ReportVolume : Volume = " + this.volume + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Volume", this.volume);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportVolume_ToView(viewIDs[x]);

	this.ReportProgress();
};

Queue.prototype.ReportMute_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportMute_ToView : Mute = " + this.mute + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Mute%" + viewID, this.mute);
	} else {
		DebugQueue(this.getID() + " ReportMute_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportMute = function()
{
	DebugQueue(this.getID() + " ReportMute : Mute = " + this.mute + " (main)", g_DEBUG_LEVEL_TRACE);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Mute", this.mute);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportMute_ToView(viewIDs[x]);
};

Queue.prototype.ReportRepeat_ToView = function(viewID)
{ // Format: L:0:None:1:Song:2:Playlist
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportRepeat_ToView : Repeat = " + this.repeat + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);
		
		var repeat_none = false;
		var repeat_song = false;
		var repeat_playlist = false;
		if (this.repeat == "none") {
			repeat_none = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Repeat%" + viewID, 0);
		} else
		if (this.repeat == "song") {
			repeat_song = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Repeat%" + viewID, 1);
		} else
		if (this.repeat == "playlist") {
			repeat_playlist = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Repeat%" + viewID, 2);
		} else {
			repeat_none = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Repeat%" + viewID, 0);
		}
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Repeat_None%" + viewID, repeat_none);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Repeat_Song%" + viewID, repeat_song);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Repeat_Playlist%" + viewID, repeat_playlist);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Repeat_Any%" + viewID, !repeat_none);
	} else {
		DebugQueue(this.getID() + " ReportTransport_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportRepeat = function()
{ // Format: L:0:None:1:Song:2:Playlist
	DebugQueue(this.getID() + " ReportRepeat : Repeat = " + this.repeat + " (main)", g_DEBUG_LEVEL_TRACE);
	
	var repeat_none = false;
	var repeat_song = false;
	var repeat_playlist = false;
	if (this.repeat == "none") {
		repeat_none = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Repeat", 0);
		SysEvent_Signal_Player("RepeatNone", this._playerIndex);
	} else
	if (this.repeat == "song") {
		repeat_song = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Repeat", 1);
		SysEvent_Signal_Player("RepeatSong", this._playerIndex);
	} else
	if (this.repeat == "playlist") {
		repeat_playlist = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Repeat", 2);
		SysEvent_Signal_Player("RepeatPlaylist", this._playerIndex);
	} else {
		repeat_none = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Repeat", 0);
		SysEvent_Signal_Player("RepeatNone", this._playerIndex);
	}
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Repeat_None", repeat_none);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Repeat_Song", repeat_song);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Repeat_Playlist", repeat_playlist);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Repeat_Any", !repeat_none);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportRepeat_ToView(viewIDs[x]);
};

Queue.prototype.ReportShuffle_ToView = function(viewID)
{ // Format: L:0:None:1:Song:2:Album
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportShuffle_ToView : Shuffle = " + this.shuffle + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);

		var shuffle_none = false;
		var shuffle_song = false;
		var shuffle_album = false;
		if (this.shuffle == "none") {
			shuffle_none = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Shuffle%" + viewID, 0);
		} else
		if (this.shuffle == "song") {
			shuffle_song = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Shuffle%" + viewID, 1);
		} else
		if (this.shuffle == "album") {
			shuffle_album = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Shuffle%" + viewID, 2);
		} else {
			shuffle_none = true;
			SysVar_WriteInt("SysVar_PlayerS_I_Transport_Shuffle%" + viewID, 0);
		}
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Shuffle_None%" + viewID, shuffle_none);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Shuffle_Song%" + viewID, shuffle_song);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Shuffle_Album%" + viewID, shuffle_album);
		SysVar_WriteBool("SysVar_PlayerS_B_Transport_Shuffle_Any%" + viewID, !shuffle_none);
	} else {
		DebugQueue(this.getID() + " ReportTransport_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportShuffle = function()
{ // Format: L:0:None:1:Song:2:Album
	DebugQueue(this.getID() + " ReportShuffle : Shuffle = " + this.shuffle + " (main)", g_DEBUG_LEVEL_TRACE);

	var shuffle_none = false;
	var shuffle_song = false;
	var shuffle_album = false;
	if (this.shuffle == "none") {
		shuffle_none = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Shuffle", 0);
		SysEvent_Signal_Player("ShuffleNone", this._playerIndex);
	} else
	if (this.shuffle == "song") {
		shuffle_song = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Shuffle", 1);
		SysEvent_Signal_Player("ShuffleSong", this._playerIndex);
	} else
	if (this.shuffle == "album") {
		shuffle_album = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Shuffle", 2);
		SysEvent_Signal_Player("ShuffleAlbum", this._playerIndex);
	} else {
		shuffle_none = true;
		SysVar_WriteInt("SysVar_Player" + this._playerIndex + "_I_Transport_Shuffle", 0);
		SysEvent_Signal_Player("ShuffleNone", this._playerIndex);
	}
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Shuffle_None", shuffle_none);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Shuffle_Song", shuffle_song);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Shuffle_Album", shuffle_album);
	SysVar_WriteBool("SysVar_Player" + this._playerIndex + "_B_Transport_Shuffle_Any", !shuffle_none);

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportShuffle_ToView(viewIDs[x]);
};

Queue.prototype.ReportEntries_ToList = function(list, viewID)
{
	if ((list !== undefined) && (list !== null)) {
		try {
			list.Open();
			list.RemoveAll();
			for (var x = 0; x < this.entries.length; x++) {
				var trackName = this.entries[x].track;
	 			var imageUrl  = this.entries[x].art;
 				if ((this._owner.view_player_map.IsViewImageCapable(viewID)) && (imageUrl != '') && this._owner.CanDisplayListArt()) {
 					DebugQueue('List item "' + trackName + '" art = ' + imageUrl, g_DEBUG_LEVEL_TRACE);
 					imageUrl = GetURL_CompleteArt(imageUrl);
 					DebugQueue('List item "' + trackName + '" art is NOW = ' + imageUrl, g_DEBUG_LEVEL_TRACE);
	 				DebugArt("Inserting list art URL = '" + imageUrl + "' for item = " + trackName, g_DEBUG_LEVEL_TRACE);
 					list.InsertWithImage(trackName, imageUrl);
 				} else {
 					list.Insert(trackName);
	 			}
 			}
 			list.SetMarked(this.index);
 			list.Close();
		} catch (err) {
 			DebugQueue(this.getID() + " ReportEntries_ToList Failed to update view " + viewID + " list with error = " + err, g_DEBUG_LEVEL_ERROR);
		}
 	} else {
		DebugQueue(this.getID() + " ReportEntries_ToList failed because the list is undefined or null", g_DEBUG_LEVEL_TRACE);
 	}
};


Queue.prototype.ReportEntries_ToView = function(viewID)
{
	viewID = parseInt(viewID, 10);
	if (!isNaN(viewID)) {
		DebugQueue(this.getID() + " ReportEntries_ToView : Entries = " + this.entries.length + " (view = " + viewID + ")", g_DEBUG_LEVEL_TRACE);

		if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
	 		this.ReportEntries_ToList(this._owner.GetQueueListForView(viewID), viewID);
		} else {
			DebugQueue(this.getID() + " ReportEntries_ToView Failed to update list because we have no owner", g_DEBUG_LEVEL_ERROR);
		}
	} else {
		DebugQueue(this.getID() + " ReportEntries_ToView parameter 'viewID' is invalid!", g_DEBUG_LEVEL_ERROR);
	}
};

Queue.prototype.ReportEntries = function()
{
	DebugQueue(this.getID() + " ReportEntries : Entries = " + this.entries.length + " (main)", g_DEBUG_LEVEL_TRACE);

 	var viewIDs = this._owner.view_player_map.GetAllViews();
 	for (var y = 0; y < viewIDs.length; y++) {
 		this.ReportEntries_ToList(this._rti_lists[viewIDs[y]], viewIDs[y]);
 	}

	var viewIDs = new Array();
	if ((this._owner !== undefined) && (this._owner !== null) && (typeof(this._owner) == 'object')) {
		viewIDs = this._owner.GetArrayOfViewsForIndex(this._playerIndex);
	}
	for (var x = 0; x < viewIDs.length; x++) this.ReportEntries_ToView(viewIDs[x]);
};
//==============> END    Reporting Methods


//==============> START  Setter Methods
Queue.prototype.recordIndex = function(index)
{
	if ((index === undefined) || (index === null)) index = -1;
	index = parseInt(index, 10);
	if (isNaN(index)) index = -1;

	if (this.index == index) {
		DebugQueue(this.getID() + " recordIndex Index is already '" + this.index + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.index = index;

	DebugQueue(this.getID() + " recordIndex = " + this.index, g_DEBUG_LEVEL_TRACE);
	this.ReportIndex();
};

Queue.prototype.recordCount = function(count)
{
	if ((count === undefined) || (count === null)) count = 0;
	count = parseInt(count, 10);
	if (isNaN(count)) count = 0;

	// Note: We ALWAYS record the count as it may alter the CanPrev/CanNext!
	this.count = count;
	if (this.count == 0) this.recordEntries(); 

	DebugQueue(this.getID() + " recordCount = " + this.count, g_DEBUG_LEVEL_TRACE);
	this.ReportCount();
};

Queue.prototype.recordTrack = function(track)
{
	if ((track === undefined) || (track === null)) track = "";

	if (this.track == track) {
		DebugQueue(this.getID() + " recordTrack Track is already '" + this.track + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.track = track;

	DebugQueue(this.getID() + " recordTrack = " + this.track, g_DEBUG_LEVEL_TRACE);
	this.ReportTrack();
};

Queue.prototype.recordArtist = function(artist)
{
	if ((artist === undefined) || (artist === null)) artist = "";

	if (this.artist == artist) {
		DebugQueue(this.getID() + " recordArtist Artist is already '" + this.artist + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.artist = artist;

	DebugQueue(this.getID() + " recordArtist = " + this.artist, g_DEBUG_LEVEL_TRACE);
	this.ReportArtist();
};

Queue.prototype.recordAlbum = function(album)
{
	if ((album === undefined) || (album === null)) album = "";

	if (this.album == album) {
		DebugQueue(this.getID() + " recordAlbum Album is already '" + this.album + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.album = album;

	DebugQueue(this.getID() + " recordAlbum = " + this.album, g_DEBUG_LEVEL_TRACE);
	this.ReportAlbum();
};

Queue.prototype.recordGenre = function(genre)
{
	if ((genre === undefined) || (genre === null)) genre = "";

	if (this.genre == genre) {
		DebugQueue(this.getID() + " recordGenre Genre is already '" + this.genre + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.genre = genre;

	DebugQueue(this.getID() + " recordGenre = " + this.genre, g_DEBUG_LEVEL_TRACE);
	this.ReportGenre();
};

Queue.prototype.recordSource = function(source)
{
	if ((source === undefined) || (source === null)) source = "";

	if (this.source == source) {
		DebugQueue(this.getID() + " recordSource Source is already '" + this.source + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.source = source;

	DebugQueue(this.getID() + " recordSource = " + this.source, g_DEBUG_LEVEL_TRACE);
	this.ReportSource();
};

Queue.prototype.recordStation = function(station)
{
	if ((station === undefined) || (station === null)) station = "";

	if (this.station == station) {
		DebugQueue(this.getID() + " recordStation Station is already '" + this.station + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.station = station;

	DebugQueue(this.getID() + " recordStation = " + this.station, g_DEBUG_LEVEL_TRACE);
	this.ReportStation();
};

Queue.prototype.recordPosition = function(position)
{
	if ((position === undefined) || (position === null)) position = 0;
	position = parseInt(position, 10);
	if (isNaN(position)) position = 0;

	if (this.position == position) {
		DebugQueueProgress(this.getID() + " recordPosition Position is already '" + this.position + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.position = position;

	DebugQueueProgress(this.getID() + " recordPosition = " + this.position, g_DEBUG_LEVEL_TRACE);
	this.ReportPosition();
};

Queue.prototype.recordPositionPlusOne = function()
{
	var position = this.position;
	if ((position === undefined) || (position === null)) position = 0;
	position = parseInt(position, 10);
	if (isNaN(position)) position = 0;

	this.position = position +1;

	DebugQueueProgress(this.getID() + " recordPosition = " + this.position + " (+1)", g_DEBUG_LEVEL_TRACE);
	this.ReportPosition();
};

Queue.prototype.recordDuration = function(duration)
{
	if ((duration === undefined) || (duration === null)) duration = 0;
	duration  = parseInt(duration, 10);
	if (isNaN(duration)) duration = 0;

	if (this.duration == duration) {
		DebugQueueProgress(this.getID() + " recordDuration Duration is already '" + this.duration + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.duration = duration;

	DebugQueueProgress(this.getID() + " recordDuration = " + this.duration, g_DEBUG_LEVEL_TRACE);
	this.ReportDuration();
};

Queue.prototype.recordPositionAndDuration = function(position, duration)
{
	if ((position === undefined) || (position === null)) position = 0;
	position = parseInt(position, 10);
	if (isNaN(position)) position = 0;

	if ((duration === undefined) || (duration === null)) duration = 0;
	duration  = parseInt(duration, 10);
	if (isNaN(duration)) duration = 0;

	if ((this.position == position) && (this.duration == duration)) {
		DebugQueueProgress(this.getID() + " recordPositionAndDuration Position is already '" + this.position + "' AND Duration is already '" + this.duration + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}

	this.position = position;
	this.duration = duration;

	DebugQueueProgress(this.getID() + " recordPositionAndDuration Position = " + this.position + " and Duration = " + this.duration, g_DEBUG_LEVEL_TRACE);
	this.ReportPosition();
	this.ReportDuration();
};

Queue.prototype.recordArt = function(art)
{
	if ((art === undefined) || (art === null)) art = "";
	this.art = art;

	DebugQueue(this.getID() + " recordArt Art = " + this.art, g_DEBUG_LEVEL_TRACE);
	this.ReportArt();
};

Queue.prototype.recordPath = function(path)
{
	if ((path === undefined) || (path === null)) path = "";

	if (this.path == path) {
		DebugQueue(this.getID() + " recordPath Path is already '" + this.path + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.path = path;

	DebugQueue(this.getID() + " recordPath Path = " + this.path, g_DEBUG_LEVEL_TRACE);
	this.ReportPath();
};

Queue.prototype.recordCanRate = function(can_rate)
{
	can_rate = parseBool(can_rate);
	if (this.can_rate == can_rate) {
		DebugQueue(this.getID() + " recordCanRate Can Rate is already '" + this.can_rate + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.can_rate = can_rate;


	DebugQueue(this.getID() + " recordCanRate Can Rate = " + this.can_rate, g_DEBUG_LEVEL_TRACE);
	this.ReportCanRate();
}

Queue.prototype.recordPower = function(state)
{
	if ((state === undefined) || (state === null)) state = false;
	if (state == 0) state = false;
	if (state == 1) state = true;
	
	state = parseBool(state);
	if (this.state == state) {
		DebugQueue(this.getID() + " recordPower Power is already '" + this.state + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.state = state;

	DebugQueue(this.getID() + " recordPower Power = " + this.power, g_DEBUG_LEVEL_TRACE);
	this.ReportPower();
};

Queue.prototype.recordTransport = function(transport)
{
	if ((transport === undefined) || (transport === null)) transport = "";
	transport = transport.toLowerCase();
	if (this.transport == transport) {
		DebugQueue(this.getID() + " recordTransport Transport is already '" + this.transport + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.transport = transport;
	
	// If we stop, reset the position to Zero!!!
	if (this.transport == "stop") this.recordPosition(0);

	DebugQueue(this.getID() + " recordTransport Transport = " + this.transport, g_DEBUG_LEVEL_TRACE);
	this.ReportTransport();
};

Queue.prototype.recordVolume = function(volume)
{
	if ((volume === undefined) || (volume === null)) volume = 0;
	volume = parseInt(volume, 10);
	if (isNaN(volume)) volume = 0;
	if (volume < 0) volume = 0;

	if (this.volume == volume) {
		DebugQueue(this.getID() + " recordVolume Volume is already '" + this.volume + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.volume = volume;


	DebugQueue(this.getID() + " recordVolume Volume = " + this.volume, g_DEBUG_LEVEL_TRACE);
	this.ReportVolume();
};

Queue.prototype.recordMute = function(mute)
{
	if ((mute === undefined) || (mute === null)) mute = false;
	if (mute == 0) mute = false;
	if (mute == 1) mute = true;

	mute = parseBool(mute);
	if (this.mute == mute) {
		DebugQueue(this.getID() + " recordMute Mute is already '" + this.mute + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.mute = mute;


	DebugQueue(this.getID() + " recordMute Mute = " + this.mute, g_DEBUG_LEVEL_TRACE);
	this.ReportMute();
};

Queue.prototype.recordRepeat = function(repeat)
{
	if ((repeat === undefined) || (repeat === null)) repeat = "none";
	if (repeat == '') { // Don't record invalid value!
		DebugQueue(this.getID() + " recordRepeat We do not report unknown values... leaving repeat at '" + this.repeat + "'", g_DEBUG_LEVEL_INFO);
		return;
	}
	if (repeat == 0) repeat = "none";
	if (repeat == 1) repeat = "song";
	if (repeat == 2) repeat = "playlist";

	repeat = repeat.toLowerCase();
	if (this.repeat == repeat) {
		DebugQueue(this.getID() + " recordRepeat Repeat is already '" + this.repeat + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.repeat = repeat;

	DebugQueue(this.getID() + " recordRepeat Repeat = " + this.repeat, g_DEBUG_LEVEL_TRACE);
	this.ReportRepeat();
};

Queue.prototype.recordShuffle = function(shuffle)
{
	if ((shuffle === undefined) || (shuffle === null)) shuffle = "none";
	if (shuffle == '') { // Don't record invalid value!
		DebugQueue(this.getID() + " recordShuffle We do not report unknown values... leaving shuffle at '" + this.shuffle + "'", g_DEBUG_LEVEL_INFO);
		return;
	}
	if (shuffle == 0) shuffle = "none";
	if (shuffle == 1) shuffle = "song";
	if (shuffle == 2) shuffle = "album";

	shuffle = shuffle.toLowerCase();
	if (this.shuffle == shuffle) {
		DebugQueue(this.getID() + " recordShuffle Shuffle is already '" + this.shuffle + "'", g_DEBUG_LEVEL_TRACE);
		return;
	}
	this.shuffle = shuffle;

	DebugQueue(this.getID() + " recordShuffle Shuffle = " + this.shuffle, g_DEBUG_LEVEL_TRACE);
	this.ReportShuffle();
};

Queue.prototype.recordEntries = function(entries)
{
	if (!(entries instanceof Array)) entries = new Array();

    var identical = false; // Assume different
    if ((this.entries != null) && (this.entries != undefined) && (this.entries.length == entries.length)) {
        identical = true; // Assume same
        for (var x = 0; x < this.entries.length; x++) {
            if (!(this.entries[x].sameAs(entries[x]))) identical = false;
        }
    }

	this.entries = entries;

//    if ((!identical) && (this._rti_list !== undefined)) {
    if ((!identical) && (this._rti_lists.length > 0)) {
		DebugQueue(this.getID() + " recordEntries Entries = " + this.entries.length, g_DEBUG_LEVEL_TRACE);
    	this.ReportEntries();
    } else {
		DebugQueue(this.getID() + " recordEntries not happening... the queue looks identical", g_DEBUG_LEVEL_TRACE);
    }

	DebugQueue(this.getID() + " Status: Entries (Count) = " + this.entries.length, g_DEBUG_LEVEL_TRACE);

};
//==============> END    Setter Methods


Queue.prototype.ParseStatus = function(xml)
{
	if ((xml === undefined) || (xml === null)) {
		DebugQueue(this.getID() + " ParseStatus: XML is null or undefined", g_DEBUG_LEVEL_ERROR);
	}
	DebugPerformance("Queue (" + this._playerMac + ") parsing status...", g_DEBUG_LEVEL_INFO);

	try { this.recordPath(xml.queue.@path.toString());           } catch(err) { this.recordPath(); } // Always report this first as other things depend on it!

	// Special call-out for airplay metadata rewriting for 
	if (this.IsNow_Airplay()) {
		// I'm not sure what RTI wants here... but just in case they want something!
		xml.queue.@track  = 'Streaming from Airplay';
		xml.queue.@artist = 'Use device for full control';
		xml.queue.@album  = '';
		xml.queue.@art    = 'music/0/cover_300x300_p.png';
		//xml.queue.@art    = 'plugins/AppGallery/html/images/icon_300x300.png';
	}

	try { this.recordIndex(parseInt(xml.queue.@index));          } catch(err) { this.recordIndex(); }
	try { this.recordCount(parseInt(xml.queue.@count));          } catch(err) { this.recordCount(); }

//	try { this.recordTrack(xml.queue.@track.toString());         } catch(err) { this.recordTrack(); }
//	try { this.recordArtist(xml.queue.@artist.toString());       } catch(err) { this.recordArtist(); }
//	try { this.recordAlbum(xml.queue.@album.toString());         } catch(err) { this.recordAlbum(); }
//	try { this.recordGenre(xml.queue.@genre.toString());         } catch(err) { this.recordGenre(); }

	try { this.recordTrack(System.ConvertFromUTF8(xml.queue.@track.toString()));         } catch(err) { this.recordTrack(); }
	try { this.recordArtist(System.ConvertFromUTF8(xml.queue.@artist.toString()));       } catch(err) { this.recordArtist(); }
	try { this.recordAlbum(System.ConvertFromUTF8(xml.queue.@album.toString()));         } catch(err) { this.recordAlbum(); }
	try { this.recordGenre(System.ConvertFromUTF8(xml.queue.@genre.toString()));         } catch(err) { this.recordGenre(); }

	var pos = 0;
	var dur = 0;
	try { pos = parseInt(xml.queue.@position); } catch(err) { pos = 0; }
	try { dur = parseInt(xml.queue.@duration); } catch(err) { dur = 0; }
	this.recordPositionAndDuration(pos, dur);

	try { this.recordArt(xml.queue.@art.toString());             } catch(err) { this.recordArt(); }

//	try { this.recordSource(xml.queue.@source.toString());       } catch(err) { this.recordSource(); }
//	try { this.recordStation(xml.queue.@station.toString());     } catch(err) { this.recordStation(); }

	try { this.recordSource(System.ConvertFromUTF8(xml.queue.@source.toString()));       } catch(err) { this.recordSource(); }
	try { this.recordStation(System.ConvertFromUTF8(xml.queue.@station.toString()));     } catch(err) { this.recordStation(); }

	try { this.recordCanRate(xml.queue.@can_rate.toString());    } catch(err) { this.recordCanRate(); }

	try { this.recordVolume(parseInt(xml.queue.@volume));        } catch(err) { this.recordVolume(); }
	try { this.recordMute(parseBool(xml.queue.@mute));           } catch(err) { this.recordMute(); }
	try { this.recordTransport(xml.queue.@transport.toString()); } catch(err) { this.recordTransport(); }
	try { this.recordShuffle(xml.queue.@shuffle.toString());     } catch(err) { this.recordShuffle(); }
	try { this.recordRepeat(xml.queue.@repeat.toString());       } catch(err) { this.recordRepeat(); }
	
	DebugPerformance("Queue (" + this._playerMac + ") status parsing complete.", g_DEBUG_LEVEL_INFO);
};

Queue.prototype.ParseTime = function(xml)
{
	if ((xml === undefined) || (xml === null)) {
		DebugQueueProgress(this.getID() + " ParseTime: XML is null or undefined", g_DEBUG_LEVEL_ERROR);
	}
	DebugPerformance("Queue (" + this._playerMac + ") parsing time...", g_DEBUG_LEVEL_TRACE);

	var pos = 0;
	var dur = 0;
	try { pos = parseInt(xml.queue.@position); } catch(err) { pos = 0; }
	try { dur = parseInt(xml.queue.@duration); } catch(err) { dur = 0; }
	this.recordPositionAndDuration(pos, dur);
	DebugPerformance("Queue (" + this._playerMac + ") time parsing complete.", g_DEBUG_LEVEL_TRACE);
};

Queue.prototype.ParseVolume = function(xml)
{
	if ((xml === undefined) || (xml === null)) {
		DebugQueue(this.getID() + " ParseVolume: XML is null or undefined", g_DEBUG_LEVEL_ERROR);
	}
	try { this.recordVolume(parseInt(xml.queue.@volume));  } catch(err) { this.recordVolume(); }
	try { this.recordMute(parseBool(xml.queue.@mute));     } catch(err) { this.recordMute(); }
};

Queue.prototype.ParsePower = function(xml)
{
	if ((xml === undefined) || (xml === null)) {
		DebugQueue(this.getID() + " ParsePower: XML is null or undefined", g_DEBUG_LEVEL_ERROR);
	}
	//try { this.recordPower(parseBool(xml.players.player.@power)); } catch(err) { this.recordPower(); }
};

Queue.prototype.ParseEntries = function(xml)
{
	if ((xml == undefined) || (xml == null)) {
		DebugQueue(this.getID() + " ParseEntries: XML is null or undefined", g_DEBUG_LEVEL_ERROR);
	}
	DebugPerformance("Queue (" + this._playerMac + ") parsing entries...", g_DEBUG_LEVEL_TRACE);

	var entries = new Array();
	try {
		var items = xml.queue.entry;
		for (var x = 0; x < items.length(); x++) {
			entries.push(
				new QueueEntry(
					items[x].@index,
					items[x].@id,
					items[x].@track,
					items[x].@artist,
					items[x].@album,
					items[x].@genre,
					items[x].@duration,
                    items[x].@art,
					items[x].@path,
					items[x].@remote,
                    items[x].@can_rate)
			);
		}

		// Special call-out for airplay metadata rewriting for 
		if (this.IsNow_Airplay() && (entries.length == 1)) {
			entries[0].track  = 'Streaming from Airplay';
			entries[0].artist = 'Use device for full control';
			entries[0].album  = '';
			entries[0].art    = 'music/0/cover_300x300_p.png';
		}

		this.recordEntries(entries);
	} catch(err) {
		this.recordEntries();
		DebugQueue(this.getID() + " ParseEntries: Error = " + err, g_DEBUG_LEVEL_ERROR);
	}
	DebugPerformance("Queue (" + this._playerMac + ") entry parsing complete.", g_DEBUG_LEVEL_TRACE);
};


// We want to support several types of "fetching". Sometimes we may need the Queue 
// Status or Entries (or both) as soon as possible, and at others (because we'll
// be asked to fetch the same thing multiple times in a row) we want to hold off a 
// little bit before making thew request. This is to try to reduce network, processor 
// and server load. So... if the delay is 0, we'll do it immediately, but if not, we'll
// set a timer and operation and perform the operation after the timer expires. 
// If another request comes in in the mean time, we'll reset the timer and start waiting 
// all over again. 
// Question: Do we need a limit to the nimber of times we reset the timer?


// This fetches both the queue's status and entries together. It operates on a delay by default.
// If nothing is specified we use the setting from the configuration. Set to zero (0) for none.
Queue.prototype.Fetch = function(delay_in_msec)
{	
    delay_in_msec = parseInt(delay_in_msec, 10); // Parse the delay to a number
    
    if ((!isNaN(delay_in_msec)) && (delay_in_msec <= 0)) {
        DebugQueue(this.getID() + " Fetch : Immediately (non-blocking)..!", g_DEBUG_LEVEL_TRACE);
		this.InternalFetch("status");
		this.InternalFetch("entries");
		return;
    }
    // We're going to delay in case there are a bunch of fetch requests right after each other!
    
    // If delay was invalid, fall back to the smaller of the defaults (since we're gonna do both)...
    if (isNaN(delay_in_msec)) delay_in_msec = Math.min(ReadConfig_AsInt("FetchDelay_QueueStatus_InMSec", 1000, 0, 5000), ReadConfig_AsInt("FetchDelay_QueueEntries_InMSec", 2000, 0, 10000));

	this.StartFetchTimer(delay_in_msec, 'both');
}

// This fetches just the queue's status. It operates on a delay by default.
// If nothing is specified we use the setting from the configuration. Set to zero (0) for none.
Queue.prototype.FetchStatus = function(delay_in_msec)
{	
    delay_in_msec = parseInt(delay_in_msec, 10); // Parse the delay to a number
    
    if ((!isNaN(delay_in_msec)) && (delay_in_msec <= 0)) {
        DebugQueue(this.getID() + " Fetch : Immediately (non-blocking)..!", g_DEBUG_LEVEL_TRACE);
		this.InternalFetch("status");
		return;
    }
    // We're going to delay in case there are a bunch of fetch requests right after each other!

    // If delay was invalid, fall back to the fetch status default...
    if (isNaN(delay_in_msec)) delay_in_msec = ReadConfig_AsInt("FetchDelay_QueueStatus_InMSec", 1000, 0, 5000);

	this.StartFetchTimer(delay_in_msec, 'status');
}

// This fetches just the queue's entries. It operates on a delay by default.
// If nothing is specified we use the setting from the configuration. Set to zero (0) for none.
Queue.prototype.FetchEntries = function(delay_in_msec)
{	
    delay_in_msec = parseInt(delay_in_msec, 10); // Parse the delay to a number
    
    if ((!isNaN(delay_in_msec)) && (delay_in_msec <= 0)) {
        DebugQueue(this.getID() + " Fetch : Immediately (non-blocking)..!", g_DEBUG_LEVEL_TRACE);
		this.InternalFetch("entries");
		return;
    }
    // We're going to delay in case there are a bunch of fetch requests right after each other!

    // If delay was invalid, fall back to the fetch entries default...
    if (isNaN(delay_in_msec)) delay_in_msec = ReadConfig_AsInt("FetchDelay_QueueEntries_InMSec", 2000, 0, 10000);

	this.StartFetchTimer(delay_in_msec, 'entries');
}


// For fetching time (because it's time sensitive) we'll always do this immediately!
Queue.prototype.FetchTime = function()
{	
	this.InternalFetch("time");
}

Queue.prototype.StartFetchTimer = function(delay_in_msec, preferred_fetch_kind)
{
	if (this._timer_fetch.State == 1) {
		DebugQueue(this.getID() + " StartFetchTimer : Timer is already running... stopping!", g_DEBUG_LEVEL_TRACE);
		DebugQueue(this.getID() + " ------------------------ Previous Fetch '" + this._fetch_kind + "' aborted!!! (that's a good thing)", g_DEBUG_LEVEL_TRACE);
		this._timer_fetch.Stop();
	}

	switch(preferred_fetch_kind) {
		case 'both'    : this._fetch_kind = "both";	break;
		case 'status'  : {
				if ((this._fetch_kind == 'entries') || (this._fetch_kind == 'both')) this._fetch_kind = "both"; 
					else this._fetch_kind = "status"; 
			}
			break;
		case 'entries' : {
			if ((this._fetch_kind == 'status') || (this._fetch_kind == 'both')) this._fetch_kind = "both"; 
				else this._fetch_kind = "entries"; 
			}
			break;
	}

	DebugQueue(this.getID() + " StartFetchTimer : Starting timer to fetch '" + this._fetch_kind + "'' for " + delay_in_msec + " milliseconds...", g_DEBUG_LEVEL_TRACE);
	this._timer_fetch.Start(Queue_OnTimer_InternalFetch, delay_in_msec, this);
}

// This global function is called by our timer(s) and just calls the InternalFetch for the instance!
function Queue_OnTimer_InternalFetch(instance)
{
    DebugQueue(instance.getID() + " Queue_OnTimer_InternalFetch (" + instance._fetch_kind + ")", g_DEBUG_LEVEL_TRACE);

	switch(instance._fetch_kind) {
		case 'both'    : instance.InternalFetch('status');	instance.InternalFetch('entries');	break;
		case 'status'  : instance.InternalFetch('status');	break;
		case 'entries' : instance.InternalFetch('entries');	break;
	}
}

Queue.prototype.InternalFetch = function(kind)
{
//	DebugQueue(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE); // Moved for debugging!

	if ((this.kind == 'status') || (this.kind == 'entries')) {
		DebugQueue(this.getID() + " InternalFetch: Let's clear the _fetch_kind because we're fetching '" + kind + "'", g_DEBUG_LEVEL_INFO);
		this._fetch_kind = ''; // Clear this for the next endeavor!
	}	

    var mac = this._playerMac;
	if (mac == '') {
		DebugQueue(this.getID() + " InternalFetch: Cannot proceed without player MAC address", g_DEBUG_LEVEL_ERROR);
		return;
	}

	if (kind == "status") {
		DebugPerformance("Queue (" + this._playerMac + ") fetching '" + kind + "'...", g_DEBUG_LEVEL_INFO);
		DebugQueue(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
		try {
			var url = GetURL_ServerRequestEx("get_queue_status", mac, "", "");
            var httpReq = new HttpRequest(url);
            httpReq.SetTag(this);
            httpReq.SetCallback(function(httpRequest, httpResponse) {
                var me = httpRequest.GetTag();
                me.ParseStatus(httpResponse.GetBody_AsXML());
            });
            httpReq.Fetch();
			
			var url = GetURL_ServerRequestEx("get_player_power", mac, "", "");
            var httpReq = new HttpRequest(url);
            httpReq.SetTag(this);
            httpReq.SetCallback(function(httpRequest, httpResponse) {
                var me = httpRequest.GetTag();
                me.ParsePower(httpResponse.GetBody_AsXML());
            });
            httpReq.Fetch();
		} catch(err) {
			DebugQueue(this.getID() + " InternalFetch: Status failed with error = " + err, g_DEBUG_LEVEL_ERROR);
		}
	} else
	if (kind == "time") {
		DebugPerformance("Queue (" + this._playerMac + ") fetching '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
		DebugQueueProgress(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
		try {
			var url = GetURL_ServerRequestEx("get_queue_status", mac, "", "time");
            var httpReq = new HttpRequest(url);
            httpReq.SetTag(this);
            httpReq.SetCallback(function(httpRequest, httpResponse) {
                var me = httpRequest.GetTag();
                me.ParseTime(httpResponse.GetBody_AsXML());
            });
            httpReq.Fetch();
		} catch(err) {
			DebugQueueProgress(this.getID() + " InternalFetch: Time failed with error = " + err, g_DEBUG_LEVEL_ERROR);
		}
	} else
	if (kind == "volume") {
		DebugPerformance("Queue (" + this._playerMac + ") fetching '" + kind + "'...", g_DEBUG_LEVEL_INFO);
		DebugQueue(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
		try {
			var url = GetURL_ServerRequestEx("get_queue_status", mac, "", "volume");
            var httpReq = new HttpRequest(url);
            httpReq.SetTag(this);
            httpReq.SetCallback(function(httpRequest, httpResponse) {
                var me = httpRequest.GetTag();
                me.ParseVolume(httpResponse.GetBody_AsXML());
            });
            httpReq.Fetch();
		} catch(err) {
			DebugQueue(this.getID() + " InternalFetch: Volume failed with error = " + err, g_DEBUG_LEVEL_ERROR);
		}
	} else
	if (kind == "entries") {
		DebugPerformance("Queue (" + this._playerMac + ") fetching '" + kind + "'...", g_DEBUG_LEVEL_INFO);
		DebugQueue(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
		try {
			var url = GetURL_ServerRequestEx("get_queue_entries", mac, "", "");
            var httpReq = new HttpRequest(url);
            httpReq.SetTag(this);
            httpReq.SetCallback(function(httpRequest, httpResponse) {
                var me = httpRequest.GetTag();
                me.ParseEntries(httpResponse.GetBody_AsXML());
            });
            httpReq.Fetch();
		} catch(err) {
			DebugQueue(this.getID() + " InternalFetch: Entries failed with error = " + err, g_DEBUG_LEVEL_ERROR);
		}
	} else {
		DebugQueue(this.getID() + " InternalFetch: Called to fetch '" + kind + "'...", g_DEBUG_LEVEL_TRACE);
	}
};




//==============> START  Methods

Queue.prototype.PlayThis = function(path, command)
{
	if ((path === undefined) || (path == null) || (path == '')) {
		DebugQueue(this.getID() + " PlayThis: Can't play an invalid path.", g_DEBUG_LEVEL_ERROR);
		return false;
	}
	if ((command === undefined) || (command == null) || (command == '')) {
		DebugQueue(this.getID() + " PlayThis: Setting undefined command to 'load'", g_DEBUG_LEVEL_WARN);
		command = 'load';
	}

	var url = GetURL_ServerRequestEx("play", this._playerMac, path, command);

	DebugQueue(this.getID() + " PlayTarget: Requesting play of URL = " + url, g_DEBUG_LEVEL_INFO);
	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.PlayFavoriteEX = function(id) // Zero based ID from GUI... complex (1.2 for folder/file)
{
	// ID : x.y.z (0 based)
	if (id === undefined) {
		DebugQueue(this.getID() + " PlayFavoriteEX: Can't play favorite because 'id' is undefined.", g_DEBUG_LEVEL_ERROR);
		return false;
	}

	var path = "/favorite=" + id;
	var url = GetURL_ServerRequestEx("play", this._playerMac, path, "load");

	DebugQueue("PlayFavoriteEX: Requesting play of URL = " + url, g_DEBUG_LEVEL_INFO);
	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}


Queue.prototype.SetTransport = function(state)
{
	// Set Transport state of Player (Queue)
	// State : play|stop | pause|unpause|toggle | prev|next

	if (state === undefined) {
		DebugQueue(this.getID() + " SetTransport: Can't set transport because 'state' is undefined.", g_DEBUG_LEVEL_ERROR);
		return false;
	}

	var url = GetURL_ServerRequestEx("set_queue_transport", this._playerMac, "", state);
	
	// START Special callout for SiriusXM... transport modification!
	if (this.path.startsWith('frsiriusxm')) {
		DebugQueue(this.getID() + " SetTransport: We need to pay attention... we're setting the transport state on SiriusXM...", g_DEBUG_LEVEL_TRACE);
		if (state == 'pause') {
			DebugQueue(this.getID() + " SetTransport: They want to pause, but we need to execute a STOP for them!", g_DEBUG_LEVEL_TRACE);
			transport = 'stop';
		} else
		if ((state == 'toggle') && (this.transport == 'play')) {
			DebugQueue(this.getID() + " SetTransport: They want to toggle, but we're playing (which means pause) so we need to execute a STOP for them!", g_DEBUG_LEVEL_TRACE);
			transport = 'stop';
		}
	}
	// END   Special callout for SiriusXM

	DebugQueue(this.getID() + " SetTransport: Requesting transport (" + state + ") using URL = " + url, g_DEBUG_LEVEL_INFO);
	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetPosition_Seconds = function(seconds)
{
	seconds = parseInt(seconds, 10);
	if (isNaN(seconds)) {
		DebugQueue(this.getID() + " SetPosition_Seconds: Can't set position because 'seconds' is not a number.", g_DEBUG_LEVEL_ERROR);
		return false;
	}

	var url = GetURL_ServerRequestEx("set_queue_position", this._playerMac, "", seconds);
	DebugQueue(this.getID() + " SetTransport: Requesting position (" + seconds + " sec) using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetPosition_Percent = function(percent)
{
	percent = parseInt(percent, 10);
	if (isNaN(percent)) {
		DebugQueue("SetPosition_Percent: Can't set position because 'percent' is not a number.", g_DEBUG_LEVEL_ERROR);
		return false;
	}

	var seconds = this.duration * percent / 100;
	DebugQueue(this.getID() + " SetPosition_Percent: Converted percent (" + percent + "%) to " + seconds + " seconds", g_DEBUG_LEVEL_TRACE);
	this.SetPosition_Seconds(seconds);
}

Queue.prototype.SetVolume = function(volume, direction) // 0 - 100, +/-
{
	if ((direction == "+") || (direction == "-")) volume = direction + volume;

	// Set Volume level of Player (Queue)
	// Volume : 0 - 100 | +/-0 - +/-100
	var url = GetURL_ServerRequestEx("set_queue_volume", this._playerMac, "", volume);
	DebugQueue(this.getID() + " SetVolume: Requesting volume (" + volume + ") using URL = " + url, g_DEBUG_LEVEL_INFO);
	
	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetMute = function(mute)
{
	// Set Mute state of Player (Queue)
	// State : mute | unmute | toggle
	var url = GetURL_ServerRequestEx("set_queue_mute", this._playerMac, "", mute);
	DebugQueue(this.getID() + " SetMute: Requesting mute (" + mute + ") using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetRepeat = function(state)
{
	var url = GetURL_ServerRequestEx("set_queue_repeat", this._playerMac, "", state);
	DebugQueue(this.getID() + " SetRepeat: Requesting repeat (" + state + ") using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetShuffle = function(state)
{
	var url = GetURL_ServerRequestEx("set_queue_shuffle", this._playerMac, "", state);
	DebugQueue(this.getID() + " SetShuffle: Requesting shuffle (" + state + ") using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetQueueIndex = function(index)
{
	var url = GetURL_ServerRequestEx("set_queue_index", this._playerMac, "", index);
	DebugQueue(this.getID() + " SetQueueIndex: Requesting play of entry (" + index + ", zero based) using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.RemoveQueueEntry = function(index)
{
	var url = GetURL_ServerRequestEx("delete_queue_item", this._playerMac, "", index);
	DebugQueue(this.getID() + " RemoveQueueEntry: Requesting removal of entry (" + index + ", zero based) using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetQueueClear = function()
{
	var url = GetURL_ServerRequestEx("clear_queue", this._playerMac, "", "");
	DebugQueue(this.getID() + " SetQueueClear: Requesting clearing of queue using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SaveQueueAs = function(name)
{
	var url = GetURL_ServerRequestEx("save_queue_as", this._playerMac, "", name);
	DebugQueue(this.getID() + " SaveQueueAs: Requesting save queue as '" + name + "' using URL = " + url, g_DEBUG_LEVEL_INFO);

    var httpReq = new HttpRequest(url);
    httpReq.Fetch();
    return true;
}

Queue.prototype.RatePositive = function()
{
	var url = GetURL_ServerRequestEx("rate_positive", this._playerMac, "", "");
	DebugQueue(this.getID() + " RatePositive: Requesting positive rating using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.RateNegative = function()
{
	var url = GetURL_ServerRequestEx("rate_negative", this._playerMac, "", "");
	DebugQueue(this.getID() + " RatePositive: Requesting negative rating using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SetPlayerPower = function(command) // on | off | cycle
{
	if (command.toLowerCase() == 'on') {
		var url = GetURL_ServerRequestEx("set_player_power", this._playerMac, "", "true");
		DebugQueue(this.getID() + " SetPlayerPower: Requesting player power on using URL = " + url, g_DEBUG_LEVEL_INFO);
	} else
	if (command.toLowerCase() == 'off') {
		var url = GetURL_ServerRequestEx("set_player_power", this._playerMac, "", "false");
		DebugQueue(this.getID() + " SetPlayerPower: Requesting player power off using URL = " + url, g_DEBUG_LEVEL_INFO);
	} else
	if (command.toLowerCase() == 'cycle') {
		var url = GetURL_ServerRequestEx("cycle_player_power", this._playerMac, "", "");
		DebugQueue(this.getID() + " SetPlayerPower: Requesting player power cycle using URL = " + url, g_DEBUG_LEVEL_INFO);
	} else {
		DebugQueue(this.getID() + " SetPlayerPower: Command (" + command + ") must be one of 'on', 'off' or 'cycle'", g_DEBUG_LEVEL_ERROR);
		return false;		
	}

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.SyncTo = function(sync_to_mac)
{
	// Sync this player to the player specified (by MAC)
	// sync_to_mac : MAC of a player to sync to, or empty to un-sync
	if ((sync_to_mac === undefined) || (sync_to_mac == null)) sync_to_mac = "";
	
	var url = GetURL_ServerRequestEx("set_player_sync", this._playerMac, "", sync_to_mac);
	DebugQueue(this.getID() + " SyncTo: Requesting this player (master) sync with " + sync_to_mac + " (slave) using URL = " + url, g_DEBUG_LEVEL_INFO);
    
    var httpReq = new HttpRequest(url);
    httpReq.Fetch();
	return true;
}

Queue.prototype.SyncAll = function()
{
	var url = GetURL_ServerRequestEx("set_player_sync_all", this._playerMac, "", "true");
	DebugQueue(this.getID() + " SyncAll: Requesting all players sync using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}

Queue.prototype.UnsyncAll = function() 
{
	var url = GetURL_ServerRequestEx("set_player_sync_all", this._playerMac, "", "false");
	DebugQueue(this.getID() + " UnsyncAll: Requesting all players un-sync using URL = " + url, g_DEBUG_LEVEL_INFO);

	var httpReq = new HttpRequest(url);
	httpReq.Fetch();
	return true;
}
//==============> END    Methods

//*************** END    Queue Object ***************
